7.5 KiB
7.5 KiB
M3.8.1 — Context Optimizer: pre-LLM compression pipeline
| Field | Value |
|---|---|
| Phase | M3.8 — Context optimization |
| Size | L — 3–5 days |
| Status | 🟡 Phase 1 complete |
| Flags | — |
| Spec | docs/CONTEXT_OPTIMIZER.md |
| Blocks | M3.8.4 |
| Depends | M3.7.7 (lesson.rs patterns), M3.7.8 (stop words), magika crate |
Goal
Build a 4-stage pre-LLM pipeline that compresses retrieved evidence chunks before they enter the GRU-Mem prompt. Search indexes stay untouched.
Deliverables
Phase 1: ContentRouter + LogCompressor (day 1) ✅
Files:
crates/mem-core/src/optimizer/mod.rs— orchestratorcrates/mem-core/src/optimizer/router.rs— content type detectioncrates/mem-core/src/optimizer/log.rs— log compression
ContentRouter uses Google Magika (ML) + regex fallback:
// Primary: Magika ML classifier (ONNX, <1ms per classification)
let magika = magika::Session::new()?;
let result = magika.identify_content_sync(content.as_bytes())?;
let label = result.info().label; // "json", "python", "shell", "yaml", etc.
// Map Magika labels → our compressor types
// Fallback to regex heuristics if Magika confidence < threshold
| Type | Magika Labels | Regex Fallback |
|---|---|---|
| Json | json, jsonl |
starts with { or [, valid parse |
| Log | txt + log heuristics |
timestamp patterns, error:, npm ERR! |
| Diff | diff |
---/+++/@@ markers |
| Code | python, javascript, rust, go, typescript, shell |
import/use/fn/def |
| Config | yaml, toml, ini, xml |
key-value patterns |
| Text | fallback | default |
LogCompressor reuses M3.7.7 lesson.rs:
markers()for error line detectionis_cascade()for noise suppressionstrip_ansi()for cleanup- Keep: error lines, stack traces, exit codes
- Drop: INFO/DEBUG noise, passing tests, repeated patterns
Tests (17, all passing ✅):
Unit tests (7):
optimizer::tests::test_token_estimateoptimizer::tests::test_optimizer_passthrough_when_disabledoptimizer::router::tests::test_json_detectionoptimizer::router::tests::test_json_array_detectionoptimizer::router::tests::test_invalid_json_rejectedoptimizer::router::tests::test_code_detectionoptimizer::router::tests::test_diff_detection
Content Router + Fallback (4):
optimizer::router::tests::test_log_detection_with_error_leveloptimizer::router::tests::test_log_detection_with_npm_erroptimizer::router::tests::test_text_detection_fallback
LogCompressor (6):
optimizer::log::tests::test_error_line_detectionoptimizer::log::tests::test_stack_line_detectionoptimizer::log::tests::test_strip_ansioptimizer::log::tests::test_cascade_removaloptimizer::log::tests::test_compress_npm_error_logoptimizer::log::tests::test_compress_rust_error_logoptimizer::log::tests::test_compression_ratio
Integration tests (written, compiled):
- 10 scenarios in
tests/it_context_optimizer.rs- JSON detection, Log detection, Code detection, Diff detection
- NPM log compression, JSON array handling
- Passthrough when disabled, compression disabled mode
- Token counting, Real-world Cargo error scenario
Phase 2: JsonCrusher + DiffCompressor (day 2)
Files:
crates/mem-core/src/optimizer/json.rscrates/mem-core/src/optimizer/diff.rs
JsonCrusher:
- Parse JSON array → analyse field variance per key
- Allocation: 30% start (schema), 15% end (recency), 55% importance
- Keep: all keys, structure, error/null/boolean fields, boundary items
- Drop: mid-array homogeneous elements, long string values
DiffCompressor:
- Keep:
+/-lines (actual changes), hunk headers (@@) - Drop: unchanged context lines, file mode changes
- Preserve enough context for the model to understand the change
Tests (10):
json_keeps_keys,json_drops_mid_array,json_keeps_boundariesjson_preserves_errors,json_ratio_70_90pctdiff_keeps_changes,diff_drops_context,diff_keeps_headersdiff_preserves_additions,diff_ratio_60_80pct
Phase 3: CacheAligner + CCR Store (day 3)
Files:
crates/mem-core/src/optimizer/cache_align.rscrates/mem-core/src/optimizer/ccr.rs
CacheAligner:
- Detect dynamic patterns in prompt prefix (timestamps, UUIDs, temp paths, SHAs)
- Reuse
lesson.rsnormalise() regex patterns - Move dynamic content to tail, keep static prefix stable
- Report drift metrics
CCR Store:
- LRU cache (bounded, default 1000 entries)
store(content) → hash/retrieve(hash) → content- TTL-based expiry (default 1hr, matches gate run duration)
- Inject retrieval hint:
<!-- CCR:hash -->
Tests (10):
align_moves_timestamps_to_tail,align_moves_uuids_to_tailalign_preserves_static_prefix,align_stable_across_callsalign_drift_metrics_reportedccr_store_and_retrieve,ccr_lru_eviction,ccr_ttl_expiryccr_hash_deterministic,ccr_inject_hint
Phase 4: TextCompressor + Integration (day 4)
Files:
crates/mem-core/src/optimizer/text.rs- Update
crates/mem-core/src/prompt.rs— wire optimizer intobuild_cache_aligned
TextCompressor:
- Reuse M3.7.8 stop words for low-value token detection
- Keep: high-entropy tokens (IDs, hashes, error codes, numbers)
- Drop: filler words, repeated phrases, low-information prose
Integration:
ContextOptimizer::optimize(chunk, config) → OptimizedChunk- Called inside
PromptBuilder::build_cache_aligned()before assembly - Config: enable/disable per-compressor, token budget, CCR toggle
- Env var:
MEM_CONTEXT_OPTIMIZER=on|off(default: on)
Tests (10):
text_keeps_high_entropy,text_drops_filler,text_ratio_30_50pctoptimizer_end_to_end_json,optimizer_end_to_end_logoptimizer_skips_under_budget,optimizer_respects_budgetoptimizer_off_passthrough,optimizer_with_ccrprompt_builder_uses_optimizer
Verify
Integration test — tests/it_context_optimizer.rs:
a1_log_compression— 50-line npm error log compressed to <10 lines, all error lines preserved, ratio >80%.a2_json_compression— 100-element JSON array compressed to <20 items, keys preserved, boundary items present, ratio >70%.a3_diff_compression— 200-line unified diff compressed to changes only, context lines dropped, ratio >60%.a4_cache_prefix_stable— Same query across 20 chunks produces identical cache prefix (system + query) after alignment.a5_ccr_roundtrip— Compressed chunk has CCR hash, retrieve returns original byte-identical content.a6_search_untouched— Verify optimizer is NOT called during indexing, only during prompt assembly.a7_under_budget_passthrough— Chunk already under token budget passes through unmodified (zero overhead).a8_no_false_negatives— Error lines, stack traces, and exit codes survive compression across all content types.
Command: cargo test -p mem-core optimizer
False pass:
- Compression ratio measured on already-small input. Fixtures must be realistic size (50+ lines for logs, 100+ elements for JSON arrays).
- Testing CCR retrieve without first compressing. The store must be populated by the compression step, not manually seeded.
Acceptance
- Log compression: >80% ratio, zero error lines lost
- JSON compression: >70% ratio, all keys preserved
- Diff compression: >60% ratio, all change lines preserved
- Cache prefix stable across chunks within a run
- CCR retrieval returns byte-identical originals
- Search indexes never see compressed content
MEM_CONTEXT_OPTIMIZER=offdisables entirely (passthrough)