The extra-context layer for AI agents — L1 eviction, sticky L2/L3 context pools, context paging, cold-tier compression.
Direction (locked): stop treating the extra-context layer as a search index that returns evidence. Make it a context virtualizer that materializes an exact, ordered, budgeted window from archived context, with a seam for transformer context-specialist variants.
Retrieval answers “which page is relevant?”. A real context answers a different question: “what exact ordered content should occupy the model’s window right now?”
The difference is measurable. In our tests:
tool results rather than resident context; canonical context event log
(exact role, payload, tool schema, token offsets, attachments, sequence)
│
context pager / resident-set manager
(L1 budget, pinning, eviction, page-in policy, KV hints)
│
materializer ─────────────────────────────────────────────┐
(deterministic text layout, stable prefix, chronological) │
│ │
host prompt / KV-cache pager ← transformer context specialist
packages/memex-core/lib/context-materializer.js.These are separate specialist components around the model, not a replacement of the base model. All three share the same materializer interface, so the heuristic can be swapped for a learned variant.
title/summary/body/coldAnchor/...).materializeContext selection/ordering heuristics.1. Canonical event capture
context-events.js: role/type/content/tool-call/tool-result/attachment envelope, deterministic checksum, DSH adapter, materializer-page adapter.context_events store table + appendContextEvents/listContextEvents; seam/default no-op, local backend forwarding.2. Pager + materializer + runtime injection
context-materializer.js: chronology, stable prefix/hash, budget, pinned pages, omitted ids, variants.context-pager.js + AgentMemory.materializeContext().materializer: 'off' | 'timeline-exact' | 'edge-attention' (default off for compatibility). Enabled by default in the v0.14 bundle (materializer: timeline-exact); set materializer: off to restore catalog+recall. The pre-step injects a materialized resident window instead of catalog+recall; capacity notices/receipts stay. U28 covers pager replay.3. Transformer context encoder (trained reference)
training/context_encoder.py trains a tiny real transformer encoder (token embeddings, 2-head self-attention, FFN, mean pooling, relevance head) on synthetic context-page/query data.docs/assets/benchmarks/context-encoder-weights.json + metrics; context-transformer.js loads the weights and reproduces the Python logits exactly in pure JS (U31 parity + ranking).4. KV/prefix host (runnable reference)
context-kv-host.js: PagedKvContextHost with real bounded paged blocks, LRU eviction, prefix cache and page table; context-kv.js capability negotiation + fail-closed fallback.scripts/context-kv-host.mjs: HTTP reference host (/health, /capabilities, /prefix, /page-in, /page-out, /stats).5. Structural fidelity evaluation
scripts/context-fidelity-eval.mjs → docs/assets/benchmarks/context-fidelity.json: event round-trip, chronology, stable prefix, pager canonical coverage, KV capacity/paging. All checks pass.Updated default (v0.14): the DSH bundle now enables materializer: timeline-exact by default. Still external: production training on live traces/GPUs and native vLLM/SGLang/DSH KV-cache integration; the interfaces and reference implementations are in place, but no inference runtime with real KV tensors is available in this environment.
A first A-route milestone ran on the GPU host:
--enable-prefix-cachingResult from vLLM /metrics:
prefix_cache_queries_total = 4997
prefix_cache_hits_total = 2480
second-request prefix hit ratio ≈ 2480 / 2499 = 99.2%
overall hit ratio ≈ 49.6%
The second request reused the materialized stable prefix through vLLM’s native paged-KV prefix cache. This is the first real-runtime confirmation of the materializer’s stable-prefix contract.
Raw: vllm-prefix-cache.json; probe: training/vllm_prefix_cache_eval.py.
Boundary: this is vLLM-native prefix caching, not yet a custom external paged-KV connector that maps materializer spans to hand-managed KV blocks. Production training and the custom connector remain next steps.
On the Blackwell GPU host we ran a real KV-tensor paging probe with Qwen2.5-0.5B (HuggingFace, 24 layers):
DynamicCache from blocks, decode the query, compare against direct cache reuse and full-context forwardResults (docs/assets/benchmarks/kv-block-paging.json):
we)This validates the span → KV block → page-in mechanics on real KV tensors. It is not vLLM connector integration yet: vLLM 0.10.2 uses its own paged-attention allocator, and the custom connector (mapping materializer spans to vLLM KV blocks) remains the next integration step.
Raw: kv-block-paging.json; probe: training/kv_block_probe.py.
A prototype MemexKVConnector for vLLM 0.10.2 now maps a materialized prefix to externally stored KV cache:
SharedStorageConnector (Apache-2.0), keyed by a configured materialized-prefix token length instead of the full prompt;/root/memex-kv as safetensors (~29MB);Key logs:
MEMEX store KV tokens=1488 layer=model.layers.0..23
MEMEX match prefix_len=1488 folder=/root/memex-kv/...
MEMEX EXTERNAL CACHE HIT prefix_tokens=1500
MEMEX inject KV tokens=1488
Raw: vllm-memex-kv-connector.json; connector: training/memex_kv_connector.py.
Boundary: prototype uses a fixed prefix length, local-disk safetensors, and vLLM’s debug connector semantics; block eviction/compression, multi-tenant isolation, and production hardening remain.
Following the single-prefix validation, the same MemexKVConnector was exercised with 8 independent materialized prefixes (2 requests each, different final questions):
Raw: vllm-memex-kv-connector-multiprefix.json.
A larger single-layer GPU reference (DIM 128 / 4 heads / vocab 2048 / hard negatives) was trained first; then a 2-layer version:
docs/assets/benchmarks/context-encoder-gpu2-weights.json (14MB); report: context-encoder-gpu2.jsonThis is a GPU-trained reference specialist, not a production per-family model; real traces and larger training remain.
The same MemexKVConnector was run with Qwen2.5-3B-Instruct (36 layers) on the 96GB GPU:
Raw: vllm-memex-kv-connector-3b.json.
Boundary: prototype connector, fixed prefix length, disk safetensors, no eviction/compression; 3B model, single process.
This closes the loop: a real context-materializer output (13 pages, 7,585 chars, stable hash 1498be8fc3ceb951) was used as the prompt prefix and passed through MemexKVConnector:
docs/assets/benchmarks/materialized-window-e2e.txt; report: vllm-materialized-window-e2e.jsonThis is the first end-to-end materializer → connector → vLLM KV load validation. The tiny random Qwen2 model avoids network downloads; connector mechanics are model-size independent (the 3B run covers scale).
MemexKVConnector now supports max_cache_mib and LRU prefix-folder eviction. Probe with a 2MiB cap:
Raw: docs/assets/benchmarks/vllm-memex-kv-connector-eviction.json.
Boundary: disk safetensors, no compression yet; tiny model.
MemexKVConnector (compress: true).Real 7B weight downloads kept failing at the CDN, so the connector was exercised with the Qwen2.5-7B architecture and dummy random weights (--load-format dummy):
This validates 7B-scale architecture/memory/KV connector mechanics, not model quality. Real AWQ weights remained in download.
Raw: docs/assets/benchmarks/vllm-memex-kv-connector-7b-dummy.json.
On the 7B dummy-weight server (28 layers, ~91% GPU):
context-materializer output (2,037 prompt tokens) through the connector: store 0.204s, second request external hit 0.088s.Raw: vllm-memex-kv-connector-7b-concurrency.json.
On the 7B dummy-weight server:
.gz layer file → manifest validation detected it, evicted the invalid prefix, request returned HTTP 200 (recomputed) instead of HTTP 500.Raw: vllm-memex-kv-connector-7b-hardening.json.
Boundary: dummy weights; gzip level not tuned; single process.
tenant_id and validates the tenant in each manifest.json. Switching from tenantA to tenantB did not reuse tenantA folders; tenantB stored then hit its own prefix (first store 5.99s, second request 0.565s).Raw: vllm-memex-kv-connector-hardening2.json.
Boundary: dummy weights; gzip latency not production-tuned; single process.
The real 7B-AWQ weights finally downloaded and passed safetensors validation. vLLM 0.10.2 + MemexKVConnector:
Raw: vllm-memex-kv-connector-real-7b.json.
Boundary: synthetic prefix workload, single process, prototype connector; model quality of generated answers not evaluated here.
Same real Qwen2.5-7B-AWQ, 8 prefixes ×2 requests, 1,856-token prefix:
| strategy | first | second |
|---|---|---|
| no cache | 0.0907s | 0.0924s |
| native vLLM prefix cache | 0.0922s | 0.0187s |
| Memex external KV | 0.1468s | 0.0583s |
Raw: kv-strategy-comparison.json.
64 requests / 8 prefixes / 1,856-token prefix:
| concurrency | throughput | p50 | p95 |
|---|---|---|---|
| 1 | 13.1 rps | 75.6ms | 82.3ms |
| 8 | 18.9 rps | 419.9ms | 444.0ms |
| 16 | 19.1 rps | 826.7ms | 842.6ms |
| 32 | 18.9 rps | 1637.4ms | 1779.4ms |
Throughput saturates around 19 rps; latency scales linearly with concurrency, indicating a single-process/CPU/disk bottleneck. Raw: production-throughput-benchmark.json.
local-memex to the desktop profile, baseURL http://127.0.0.1:18000/v1, served by a tunnel to the remote vLLM connector.local-memex/qwen-7b-awq (backup profile YAML exists).num_computed_tokens > 0; native prefix cache serves hot prefixes. Cold unique 2,933-token prefix: first external hit 0.363s, second native 0.024s; short prefixes stay native 0.013–0.020s.ThreadPoolExecutor(8) before injection.min_prefix_tokens (tested 2000) keeps short prompts on native cache; long/cold prompts can use external KV.--gpu-memory-utilization 0.5: GPU used 50,103/97,887 MiB, cold external 0.385s, subsequent native 0.028s.Raw: six-optimizations.json.
Boundary: single process/GPU; batch load has no isolated A/B benchmark yet.
Real Qwen2.5-7B-AWQ, unique random prefixes, 2 requests per length, 50% GPU KV reservation:
| prefix tokens | baseline 2nd | native 2nd | external connector 2nd | hybrid 2nd |
|---|---|---|---|---|
| 545 | 0.0385s | 0.0175s | 0.0339s | 0.0169s |
| 1,057 | 0.0618s | 0.0197s | 0.0380s | 0.0187s |
| 1,889 | 0.0981s | 0.0228s | 0.0623s | 0.0220s |
| 2,833 | 0.1392s | 0.0249s | 0.1020s | 0.0238s |
kv-length-crossover.json.Raw: kv-cold-fallback-batch-load.json.
Boundary: single-run timings, API overhead included; gzip cold-load comparison incomplete.
To move beyond the scratch encoder, BGE-small was fine-tuned as a cross-encoder on the real archive dataset (36 records, 108 query groups, templated queries):
| model | sample accuracy | pairwise ranking |
|---|---|---|
| BGE bi-encoder + linear | 0.5926 | 0.5926 |
| BGE cross-encoder fine-tuned | 0.963 | 0.963 |
The jump is real on this split, but the split shares records and query templates; it is evidence of learnability, not production generalization. Next: real user queries, record-disjoint splits, and a query-aware materializer policy.
Raw: real-trace-cross-encoder.json; code: training/real_trace_cross_encoder.py.
Boundary: 36-record corpus, templated queries, no JS/DSH integration yet.
A query-aware selector was evaluated against the current rule/budget materializer on the real archive dataset (27 held-out query groups, 500-token budget):
| selector | positive recall | avg selected tokens | latency |
|---|---|---|---|
| current rule materializer | 0.5185 | 326.9 | — |
| learned BGE cross-encoder | 0.963 | 387.8 | 8.64ms/query |
Raw: learned-materializer-eval.json; code: training/learned_materializer_eval.py.
Extracted real user queries from DSH session logs, manually labeled to 36 real archive records, with positive records disjoint from train for validation:
This is a negative generalization result: the earlier 0.963 result came from a templated, record-shared split and does not survive real-query, record-disjoint validation. The rule materializer currently beats the learned selector on this tiny real set.
Raw: real-query-materializer-eval.json.
Boundary: only 4 val groups; labels noisy; this is a warning, not a final verdict.
Learned ranking alone is not reliable on real queries, so we evaluated a hybrid policy: keep rule-selected pages and add the learned top-1 candidate.
| dataset | val groups | rule recall | learned top-1 | rule ∪ learned |
|---|---|---|---|---|
| real session queries (nearest-message labels) | 18 | 0.222 | 0.500 | 0.667 |
| manual real-query labels | 4 | 0.500 | 0.250 | 0.750 |
Takeaway: rule-only is a weak baseline; learned alone is unstable; hybrid union improves recall while keeping the rule path as a safety net. Next: integrate the learned reranker as an additive candidate and validate at larger real-query scale.
Raw: hybrid-materializer-eval.json.
Boundary: small/noisy labels; union measured offline; not yet integrated in JS.
materializeContext now accepts optional query + learnedRerank; when present it adds the learned top candidate before chronological fill. On 18 held-out real session-query groups:
| budget | rule recall | hybrid recall |
|---|---|---|
| 500 | 0.222 | 0.333 |
| 1,000 | 0.722 | 0.833 |
| 1,500 | 0.778 | 0.889 |
| 2,400 | 0.778 | 0.889 |
The learned reranker is a lexical-feature logistic model (exported lexical-reranker.json); the default rule path is unchanged. U36 verifies the hook. Raw: js-hybrid-materializer.json.
Boundary: noisy nearest-query labels; lexical model; optional path only.
The learned rerank hook is now wired through the production materializer API path:
context-pager.materializeStoreContext({ query, learnedRerank })engine.materializeContext({ query, learnedRerank })This makes the hybrid path callable by DSH/adapters without changing the default rule path. Boundary: DSH’s active checkpoint call does not yet pass a live query; enabling it there is the next deployment step.
The active DSH memory bundle was wired for optional query-aware hybrid rerank:
agent/pre-step extracts the newest direct user messagequery into memory.materializeContextmaterializerHybridRerank: true, passes the learned reranker weightsmaterializerHybridRerank: trueRollback:
backups/dsh-memory-next-prehybrid-*dsh-memory-next/cordis.patch.yml.bak-prehybrid-*materializerHybridRerank: false and restart DSHExtracted 99 real DSH session queries; labeled 52 to real archive records with an LLM judge (top-3 relevance). Record-disjoint validation: 29 train / 23 val groups (6 val records).
| budget | rule recall | hybrid recall |
|---|---|---|
| 500 | 0.217 | 0.087 |
| 1,000 | 0.478 | 0.435 |
| 1,500 | 0.478 | 0.478 |
| 1,800 | 0.522 | 0.522 |
| 2,400 | 0.348 | 0.783 |
Conclusion: budget-sensitive. Hybrid is not safe as default at the current 1,800-token budget; it becomes clearly better only at 2,400. Needs more labels and budget-aware reranking before enabling by default.
Raw: llm-labeled-real-query-eval.json.
Boundary: LLM labels not human-verified; small val; lexical reranker; active DSH config unchanged.
Deployed the LLM-labeled real-query setup to the active DSH bundle:
lexical-reranker.json replaced with the LLM-labeled-trained weightsmaterializerMaxTokens raised 1,800 → 2,400materializerHybridRerank: true remains enabledRollback:
dsh-memory-next/cordis.patch.yml.bak-pre2400-*modules/memory-core/lib/lexical-reranker.json.bak-*materializerHybridRerank: falseThe active checkpoint now records both selections for every hybrid materialization:
form: materializer-abquery, budgetTokensruleIds, hybridIdsruleTokens, hybridTokensaddedIds, removedIdsThis double materialization is best-effort and does not change the injected window. Analyze with:
python3 scripts/materializer-ab-report.py
Next: restart DSH once, accumulate real turns, then compare rule vs hybrid page/token deltas online.
Controlled synthetic long-horizon benchmark (60 archived decisions, recent tail only; 10 probes for facts only present in the archive) with two real API models:
| model | condition | correct | accuracy | avg prompt tokens |
|---|---|---|---|---|
| claude-haiku-4-5 | baseline: recent tail only | 0/10 | 0.0 | 3,222 |
| claude-haiku-4-5 | memex materialized archive | 10/10 | 1.0 | 5,532 |
| claude-haiku-4-5 | decoy archive (same size, facts replaced) | 0/10 | 0.0 | 5,566 |
| gpt-6-astra | baseline: recent tail only | 0/10 | 0.0 | 4,317 |
| gpt-6-astra | memex materialized archive | 10/10 | 1.0 | 7,905 |
| gpt-6-astra | decoy archive (same size, facts replaced) | 0/10 | 0.0 | 7,649 |
Raw: long-horizon-memex-ab.json; script: training/long_horizon_memex_ab.py.
Boundary: synthetic facts, all-fact archive, API models, not a real DSH session; tests 133/133.
Real memory pages, multi-topic query, rule vs hybrid contexts, two answer models, blind judge with two answer orders:
| answerer | order | judge winner |
|---|---|---|
| deepseek-flash | forward | hybrid |
| deepseek-flash | reverse | hybrid |
| claude-haiku-4-5 | forward | hybrid |
| claude-haiku-4-5 | reverse | hybrid |
quality-ab-materializer.json; aggregate only; real memory content was not committed.The previous quality A/B showed both contexts missing KV/DSH facts. Three real memory records were written (KV 7B results, DSH default model, next priorities), then the hybrid context was rebuilt with those records pinned.
deepseek-flash, claude-haiku-4-5; blind gpt-6-astra judge; both answer orders.Result: updated context won both orders for both models.
Raw: quality-ab-updated-memory.json.
Boundary: one query; pinned updated context is not a production selector; aggregates only.
Added optional learnedTopK to materializer (multiple learned candidates before chronology); U38 covers it.
Normal selection (41 real records, budget 1,800, no pinning), rule vs hybrid with learnedTopK=8, APIMaster answerers and blind judge:
| answerer | forward | reverse |
|---|---|---|
| deepseek-flash | hybrid (coverage 7 vs 0) | hybrid (coverage 6 vs 0) |
| claude-haiku-4-5 | hybrid (coverage 6 vs 0) | hybrid (coverage 6 vs 0) |
learnedTopK is prototype; not yet synced to the active DSH bundle.Raw: quality-ab-normal-selection.json; harness 134/134.
Active memory bundle is now wired with:
materializer: timeline-exactmaterializerMaxTokens: 2400materializerHybridRerank: truematerializerLearnedTopK: 8Active memory tests: 98/98; repo harness 134/134. Requires DSH restart to load the new materializer/checkpoint code and config.
Definition: hybrid is not a model. It is the materializer selection policy: rule/budget/pinned selection as the safety net, plus query-aware learned top-K candidates under the same token budget.
Tested 41 real records, 5 query types, budgets 1,200/1,800/2,400 and learnedTopK 1/4/8/16. Key-record coverage is non-monotonic:
Active config was therefore set to 1,800 + topK8, not 2,400 + topK8.
Optimization directions: budget-aware reranking with per-page caps; core-fact always-include tier; diversity/coverage constraints; semantic/BGE reranker or query routing; multi-query human-verified benchmark; validate with real DSH trace.
Raw: selection-matrix.json, selection-matrix-optimization.json.
Implemented:
alwaysIncludeIds guarantees architecture, KV 7B, DSH default model, next-priorities summaries before ordinary selection.learnedPageMaxTokens.Multi-query matrix on 41 real records, optimized config (1,800 budget, topK8, page cap 700, core tier): all core facts hit; tokens 1,428–1,753.
Optimized answer quality (multi query, 1,496-token context): DeepSeek Flash coverage 7 / correctness 6 / focus 7; Claude Haiku 7 / 6 / 9.
Not yet done: BGE/semantic reranker (current lexical + core tier), full answer-quality A/B on all five query types, human-verified labels.
Active bundle config is synced; DSH restart required. Tests: repo 137/137, active 98/98.
Raw: all-optimizations-materializer.json.
Budget now covers page content + index prefix (plus reserveTokens headroom) and is checked on every candidate addition. Results expose:
budgetStatus: ok | empty | empty-budget | exceededoverBudgetBy, overBudgetReason: core | pinned | forcedforcedIds, omittedIdsbudgetBreakdown: page tokens, prefix tokens, reserved tokens, total, tokenizer, chars, estimate error notetokenCounter / tokenizerName for exact model tokenizer; default is chars/4 heuristicTests U42/U43 cover empty budget, forced-over-budget status, and estimator metadata. A 60-combination regression (5 queries x 3 budgets x 4 topK) now reports 0 silent over-budget results.
alwaysIncludeIds, experimental/configurable — not a universal hardcode).learnedPageMaxTokens skips oversized candidates.selection-matrix.json as a regression case.Lexical reranker now uses one tokenizer for ASCII words plus CJK unigrams/bigrams/trigrams (U44). Weights have not yet been retrained on CJK-specific data; that remains a validation item.
Active checkpoint A/B trace now includes budgetStatus, overBudgetBy, overBudgetReason, omittedIds, tokenizer, and configVersion. Materialized-context trace includes the same budget metadata. Privacy: traces contain query/memory IDs and local content; retain locally, do not ship to third parties.
Active config remains 1,800 / topK8 / core IDs / page cap 700 / total enforcement; DSH restart required.
After adding the core-fact tier, five query types were tested with optimized selection (1,800, topK8), APIMaster DeepSeek/Claude, blind GPT-6 judge, both orders:
Conclusion: core-fact tier is the main coverage win; hybrid topK8 adds noise in several queries and is not consistently preferred. Rule + core tier is the safer default; hybrid should be opt-in until a selective/query-aware gate is validated. This also shows why selection metrics alone are insufficient.
Raw: multiquery-answer-quality.json.
Boundary: one context per query, no human labels, judge variance; real memory content sent to APIMaster (aggregate only retained).
Budget is now evaluated on the final chronological rendered text, not the order in which pages were selected. A custom order-sensitive tokenizer regression (U47) confirms no silent over-budget result. Non-forced overflow reason is budget, not forced; forced core/pinned reasons remain explicit.
The learned candidate order is now: highest relevance score first; ties prefer the cheaper page. Equal negative scores therefore do not favor larger pages. U48 asserts the cheaper equal-negative page is selected under a tight budget; U46 covers negative/tied scores and partial budgets.
U44 now uses a budget that fits only one page. The Chinese query selects the KV page; the no-query control selects a different chronology page. This demonstrates query-driven selection, not just “function executes”.
training/quality_ab_protocol.py now checks:
--exploratory--authorize-real-memory for real memoryProcess started 22:20:49; active config loaded; final sorting/budget materializer has mtime 23:06:34 and is not loaded until DSH restart. No default strategy change.
Repo 144/144 offline, 36/36 SQLite, active 98/98.
0.51 and lacked formatTokens/renderedChars because the checkpoint trace fields were updated after that process start.configVersion: 0.54 and to include formatTokens, renderedChars from the materializer breakdown.=== Remaining evaluation requirements ===
formatTokens is now computed from the final rendered text minus body and prefix tokens; the previous loaded version could clamp it to 0. Code + active file updated.formatTokens=0 in the latest trace came from the pre-fix breakdown. One restart is required to observe the fixed field.Raw: quality-protocol-mock.json; no default strategy change.
User authorized real memory content to APIMaster for quality A/B (option A). Five real queries, rule vs hybrid contexts, DeepSeek Flash and Claude Haiku answerers, GPT-6 judge, both answer orders. Labels are provisional (verified=false), so this is exploratory only.
Findings:
formatTokens=334, rule 1794 / hybrid 1759 tokens, status ok.Conclusion: rule + core-fact tier remains the safer provisional default; hybrid is optional/experimental. Human-verified labels and a real cost model are still required before any default change.
Raw aggregate: real-memory-quality-exploratory.json; no raw answers committed.
role, tool, payload, offsets, attachments).kvPaging, attentionSinks, prefixCache, materializerVersion) to family profiles; model-specific variants can select different assemblers.The honest goal is not to claim “true context” for API-only agents. It is to make the extra-context layer structurally exact and functionally indistinguishable where the host permits, and explicitly mark the KV/attention gap where it does not.