agent-working-memory 0.13.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +200 -238
  2. package/dist/adapters/common.d.ts +6 -0
  3. package/dist/adapters/common.d.ts.map +1 -1
  4. package/dist/adapters/common.js +457 -362
  5. package/dist/adapters/common.js.map +1 -1
  6. package/dist/api/routes.d.ts.map +1 -1
  7. package/dist/api/routes.js +24 -8
  8. package/dist/api/routes.js.map +1 -1
  9. package/dist/core/alias-map.d.ts +16 -0
  10. package/dist/core/alias-map.d.ts.map +1 -0
  11. package/dist/core/alias-map.js +102 -0
  12. package/dist/core/alias-map.js.map +1 -0
  13. package/dist/core/embeddings.d.ts +17 -0
  14. package/dist/core/embeddings.d.ts.map +1 -1
  15. package/dist/core/embeddings.js +50 -1
  16. package/dist/core/embeddings.js.map +1 -1
  17. package/dist/core/recall-config.d.ts +52 -0
  18. package/dist/core/recall-config.d.ts.map +1 -0
  19. package/dist/core/recall-config.js +110 -0
  20. package/dist/core/recall-config.js.map +1 -0
  21. package/dist/core/rerank-window.d.ts +61 -0
  22. package/dist/core/rerank-window.d.ts.map +1 -0
  23. package/dist/core/rerank-window.js +153 -0
  24. package/dist/core/rerank-window.js.map +1 -0
  25. package/dist/core/rerank2.d.ts +62 -0
  26. package/dist/core/rerank2.d.ts.map +1 -0
  27. package/dist/core/rerank2.js +75 -0
  28. package/dist/core/rerank2.js.map +1 -0
  29. package/dist/core/retrieval-text.d.ts +55 -0
  30. package/dist/core/retrieval-text.d.ts.map +1 -0
  31. package/dist/core/retrieval-text.js +87 -0
  32. package/dist/core/retrieval-text.js.map +1 -0
  33. package/dist/core/temporal-query.d.ts +61 -0
  34. package/dist/core/temporal-query.d.ts.map +1 -0
  35. package/dist/core/temporal-query.js +168 -0
  36. package/dist/core/temporal-query.js.map +1 -0
  37. package/dist/core/token-budget.d.ts +75 -0
  38. package/dist/core/token-budget.d.ts.map +1 -0
  39. package/dist/core/token-budget.js +136 -0
  40. package/dist/core/token-budget.js.map +1 -0
  41. package/dist/core/whoami.d.ts +11 -0
  42. package/dist/core/whoami.d.ts.map +1 -1
  43. package/dist/core/whoami.js +10 -0
  44. package/dist/core/whoami.js.map +1 -1
  45. package/dist/core/write-pipeline.d.ts.map +1 -1
  46. package/dist/core/write-pipeline.js +6 -3
  47. package/dist/core/write-pipeline.js.map +1 -1
  48. package/dist/engine/activation.d.ts.map +1 -1
  49. package/dist/engine/activation.js +135 -32
  50. package/dist/engine/activation.js.map +1 -1
  51. package/dist/hooks/prime.d.ts +77 -0
  52. package/dist/hooks/prime.d.ts.map +1 -0
  53. package/dist/hooks/prime.js +92 -0
  54. package/dist/hooks/prime.js.map +1 -0
  55. package/dist/hooks/sidecar.d.ts.map +1 -1
  56. package/dist/hooks/sidecar.js +39 -0
  57. package/dist/hooks/sidecar.js.map +1 -1
  58. package/dist/mcp.js +134 -102
  59. package/dist/mcp.js.map +1 -1
  60. package/dist/storage/pglite.d.ts.map +1 -1
  61. package/dist/storage/pglite.js +10 -2
  62. package/dist/storage/pglite.js.map +1 -1
  63. package/dist/storage/postgres.d.ts.map +1 -1
  64. package/dist/storage/postgres.js +10 -2
  65. package/dist/storage/postgres.js.map +1 -1
  66. package/dist/storage/sqlite.d.ts.map +1 -1
  67. package/dist/storage/sqlite.js +12 -2
  68. package/dist/storage/sqlite.js.map +1 -1
  69. package/dist/types/engram.d.ts +7 -0
  70. package/dist/types/engram.d.ts.map +1 -1
  71. package/package.json +3 -2
  72. package/src/adapters/common.ts +666 -567
  73. package/src/api/routes.ts +1015 -999
  74. package/src/core/alias-map.ts +97 -0
  75. package/src/core/embeddings.ts +172 -115
  76. package/src/core/recall-config.ts +115 -0
  77. package/src/core/rerank-window.ts +158 -0
  78. package/src/core/rerank2.ts +82 -0
  79. package/src/core/retrieval-text.ts +82 -0
  80. package/src/core/temporal-query.ts +193 -0
  81. package/src/core/token-budget.ts +160 -0
  82. package/src/core/whoami.ts +110 -92
  83. package/src/core/write-pipeline.ts +6 -3
  84. package/src/engine/activation.ts +1568 -1468
  85. package/src/hooks/prime.ts +136 -0
  86. package/src/hooks/sidecar.ts +43 -0
  87. package/src/mcp.ts +1422 -1387
  88. package/src/storage/pglite.ts +10 -2
  89. package/src/storage/postgres.ts +10 -2
  90. package/src/storage/sqlite.ts +12 -2
  91. package/src/types/engram.ts +7 -0
package/README.md CHANGED
@@ -137,96 +137,90 @@ Two structural advantages a file or a flat vector store cannot match:
137
137
  Two kinds of tests, both reproducible (see [Testing & Evaluation](#testing--evaluation)).
138
138
  First, **recall quality** — does the pipeline return the right memory? Second,
139
139
  **behavior under stress** — does it stay honest, filter noise, and hold up as the
140
- store grows and ages? Numbers below were last re-run on the 0.9-staged line
141
- (2026-06-17) — the retrieval pipeline itself is unchanged since, but re-run these
142
- yourself (`npm run eval`) if you want current-build numbers; 0.12.x added
143
- reliability/telemetry/entity-index work on top, not retrieval-scoring changes.
144
- See [`docs/gauntlet-baseline-2026-07-30.md`](docs/gauntlet-baseline-2026-07-30.md)
145
- for the newer end-to-end memory-ablation acceptance test (74%±5pp memory-dependent
146
- vs 0% no-memory control).
147
-
148
- ### 1 · Recall quality (eval harness)
149
-
150
- Each suite has a pass threshold; all four pass.
151
-
152
- | Suite | Score | Threshold | What it measures |
153
- |-------|-------|-----------|------------------|
154
- | Retrieval | **Recall@5 = 0.980** | 0.80 | 200 facts, 50 queries does the BM25 + vector + reranker pipeline surface the right fact in the top 5? |
155
- | Associative | **success@10 = 1.000** | 0.70 | 20 multi-hop causal chains does the graph walk find non-obvious connections? |
156
- | Redundancy | **dedup F1 = 0.966** | ≥ 0.80 | 50 clusters × 4 paraphrases — does consolidation merge duplicates without losing the original? |
157
- | Temporal | **Spearman = 0.932** | 0.75 | 25 facts with controlled age/access — does ACT-R decay rank recent/used memories ahead of stale ones? |
158
-
159
- ### 2 · Behavior under stress & adversarial conditions
160
-
161
- These are graded suites (not pass/fail). The headline risk they guard against is a
162
- memory system that confidently returns the *wrong* thing so the weakest area is
163
- called out, not hidden.
164
-
165
- | Suite | Score | What it measures |
166
- |-------|-------|------------------|
167
- | `test:run` (unit) | **569 / 569** | Salience, decay, Hebbian, supersession, coordination, scheduler |
168
- | `test:self` | **93.9% (EXCELLENT)** | Every cognitive subsystem end-to-end; weakest = exact-topic retrieval |
169
- | `test:workday` | **85.4% (GOOD)** | A realistic mixed day — 43 memories across 4 projects, cross-cutting queries; weakest = noise filtering |
170
- | `test:edge` | **~32 / 34** | Named failure modes: identity collision, contradiction trapping, bridge overshoot, false generalization |
171
- | `test:ab` | **AWM 10 / 11 vs keyword baseline 8 / 11** | Where the cognitive pipeline beats plain keyword search |
172
- | `test:pilot` | **14 / 15** (5/5 noise rejected) | Production-like queries that must reject planted distractors |
173
- | `test:locomo` | **25.7%** | LoCoMo conversational-memory benchmark (a *chatbot* benchmark — see note) |
174
- | `test:mcp` | **5 / 5** | MCP protocol smoke: write, recall, feedback, retract, stats |
175
-
176
- > **On LoCoMo (25.7%):** LoCoMo measures *chatbot* recall ("what did we say about X"
177
- > across long conversations). It is not the workload AWM is tuned for (productivity /
178
- > engineering, staying on topic, rejecting noise), and ~66% of AWM's misses there are
179
- > retriever-coverage (the gold turn isn't in the top-10), not extraction. The 0.9 recall
180
- > work lifted it from 22.7% with every category up. We report it for comparability, not
181
- > as the headline.
182
-
183
- ### 3 · The sleep cycle (consolidation)
184
-
185
- The **sleep cycle** is AWM's offline maintenance pass (the term is borrowed from how
186
- human memory consolidates during sleep). On each cycle it **clusters** related
187
- memories, builds **cross-topic bridges**, **strengthens** co-used edges, **decays**
188
- unused ones, and **prunes** duplicates. You run it so the association graph stays
189
- *healthy and navigable* as the store grows without it, edges accumulate into noise.
190
-
191
- > **Reading the score:** `test:sleep` = **78.6%** is a *consolidation-quality* score
192
- > it asks "after the maintenance pass, is recall at least as good and is the structure
193
- > better?" **It is not recall falling to 78.6%.** In this fixture recall is held flat
194
- > across three cycles (78.6% before = 78.6% after) while the graph reorganizes. The
195
- > scaling picture is the real proof:
196
-
197
- | Under a 100-cycle stress run | Observed |
198
- |---|---|
199
- | Recall across cycles | **holds 90–100%** (no catastrophic forgetting) |
200
- | Cross-topic recall | **~80%**, stable |
201
- | Graph self-pruning | edges grow to ~2,300 then prune back to ~1,500 as unused links decay |
202
- | Clusters / bridges per cycle | ~10 clusters, bridges formed early then settle |
203
-
204
- So consolidation *protects* recall over the long run — the per-cycle score measures the
205
- health of the maintenance, and the stress run shows recall doesn't degrade.
206
-
207
- ### 4 · Token economics honest
208
-
209
- The win that matters is **structural**: at the scale AWM targets you can't carry the
210
- project at all (see [Why it matters at scale](#why-it-matters-at-scale)). On real coding
211
- sessions, scoped recall costs **9.8× less in aggregate** than the Read/Grep/Glob
212
- rediscovery it replaces (`scripts/measure-claude-vs-awm.ts`).
213
-
214
- The per-turn micro-benchmark (`test:tokens`) reports against **two** baselines, because the
215
- baseline you pick *is* the result:
216
-
217
- - **vs carrying the full history** (what a memoryless agent must actually do it can't know
218
- which past turn matters): **+67% savings at 97.5% recall accuracy.** This is the honest,
219
- apples-to-apples number.
220
- - **vs an oracle that pre-scoped context to the exactly-relevant task**: **≈ −13%.** A
221
- deliberately brutal barit gives the baseline the very scoping that retrieval exists to do —
222
- and on a tiny 6–8-turn task a fixed top-5 recall is break-even-to-negative *by construction*.
223
-
224
- An earlier build reported ~56% on the oracle bar, but that was an **artifact**: pre-v0.8.5,
225
- reinforce-on-duplicate silently *discarded* memory content, so recalls were artificially tiny.
226
- v0.8.5 fixed the data loss (accuracy ~72% → 97.5%); better recall now fills all five slots,
227
- which *lowers* the oracle-bar number while *raising* correctness. Net: the at-scale structural
228
- win above is the real story; the oracle bar shows AWM roughly matches perfect manual scoping
229
- even on a corpus far too small to play to its strengths.
140
+ store grows and ages?
141
+
142
+ ### 1 · The 0.13.x retrieval wins
143
+
144
+ Three changes shipped in 0.13.4–0.13.6. Each row names **the corpus it was measured
145
+ on** — they are not the same, and the difference matters. Enable all three together:
146
+
147
+ ```bash
148
+ AWM_RERANK2=1 AWM_RERANK_WINDOW=query AWM_RERANK_TAGS=1
149
+ ```
150
+
151
+ | Change | Flag | Measured result | Measured on |
152
+ |---|---|---|---|
153
+ | **Second-stage rerank** — let the cross-encoder's own score decide final order, instead of a blend that capped its vote at 70% | `AWM_RERANK2=1` | **+9.7pp success@1** (37.8 → 47.6), p<0.001, paired McNemar. Costs no extra inference — the scores already existed and were being partly discarded. | 616 LoCoMo probes — ⚠ the benchmark retired below |
154
+ | **Query-aware rerank window** spend the same 400-char budget on the window densest in query terms instead of the prefix | `AWM_RERANK_WINDOW=query` | **25.0% → 87.5%** long-memory success@1 (3.5×), at **+0.07%** CPU and **zero** added tokens. | Generated long-memory corpus, calibrated to real-store statistics, answer planted at a controlled offset |
155
+ | **Tags into the rerank passage** — put words that exist only as tags in front of the component that decides | `AWM_RERANK_TAGS=1` | **+7.4pp success@1** (56.4 63.8) on category queries. | 450 probes on a **frozen real-store snapshot** |
156
+
157
+ **Combined, on the real store** (450 category probes, frozen 11,294-engram snapshot):
158
+ **s@1 56.4 → 63.8%**, **s@5 66.2 → 68.4%**, **MRR 60.6 → 66.0%** — with adversarial
159
+ abstention held at **90.0%** in every arm. Selectivity was not traded away to buy accuracy.
160
+
161
+ > **On the +9.7pp figure.** It comes from LoCoMo, which this page retires two sections
162
+ > below. Reported as-measured rather than quietly dropped, because the provenance is part
163
+ > of the story: LoCoMo's short passages are exactly why it could not see the 400-char
164
+ > truncation, and that blind spot is what made the standalone `AWM_RERANK2`
165
+ > recommendation wrong. The combined real-store number above is the one to trust.
166
+
167
+ > **End-to-end, this did not move the acceptance test.** See the gauntlet row below.
168
+
169
+ > **⚠ Enable them together.** `AWM_RERANK2` *alone* regresses long-memory s@5 from
170
+ > 91.7% to 25.0%. BM25 over full content had been quietly compensating for the
171
+ > reranker's 400-char blindness; making a blind reranker authoritative removes that
172
+ > cover. Ship both, or neither.
173
+
174
+ The finding underneath all three: **a memory is unreachable when a word it needs was
175
+ never written into its body.** On this store, 66.2% of topical tag terms never appear
176
+ in the text at all. Three other approaches to the same defect were tested and
177
+ rejected re-embedding with tags (+0.3pp), mined dialect aliases (−0.2pp), and a
178
+ larger 768d embedder (+0.7pp alone, −1.1pp combined). You cannot recover a word that
179
+ was never written; you can only put the word that *is* recorded in front of the ranker.
180
+ That is also why 0.13.6 rewrote the **writing guidance**, not just the ranker.
181
+
182
+ Full evidence, protocol, and the rejected arms: [`docs/archive/`](docs/archive/README.md).
183
+
184
+ ### 2 · Everything else, in one table
185
+
186
+ | What | Result | Detail |
187
+ |---|---|---|
188
+ | **Eval harness** (retrieval / associative / redundancy / temporal) | Recall@5 **0.980** · success@10 **1.000** · dedup F1 **0.966** · Spearman **0.932** — all four above threshold | [`docs/benchmarks.md`](docs/benchmarks.md) |
189
+ | **Unit + subsystem** | `test:run` **715/715** · `test:self` **93.9%** · `test:edge` **~32/34** · `test:mcp` **5/5** | [`docs/benchmarks.md`](docs/benchmarks.md) |
190
+ | **Adversarial / noise rejection** | `test:pilot` **14/15** (5/5 distractors rejected) · `test:ab` **AWM 10/11 vs keyword 8/11** | [`docs/benchmarks.md`](docs/benchmarks.md) |
191
+ | **End-to-end ablation** (the gauntlet) | **74%±5pp memory-dependent vs 0% no-memory control** (0.11.x baseline); only the memory substrate varies. Both arms complete at k=10: baseline **74.0%** vs **81.0%** with the flags (**+7.0pp**, Fisher p=0.31 not significant, but directionally matching the +7.4pp fixture result). **All 10 probes flip between identical runs**, and `multihop` has never passed at any k | [`gauntlet-baseline`](docs/archive/gauntlet-baseline-2026-07-30.md) |
192
+ | **Consolidation under stress** | Recall **holds 90–100%** across 100 cycles; edges grow to ~2,300 then self-prune to ~1,500 | [`docs/benchmarks.md`](docs/benchmarks.md) |
193
+ | **Token economics** | **9.8× lower** aggregate cost than the Read/Grep/Glob rediscovery it replaces | [`docs/benchmarks.md`](docs/benchmarks.md) |
194
+
195
+ **The retrieval gains above have not yet shown up end-to-end, and the reason is now
196
+ understood.** Both arms now run complete at k=10: baseline **74.0%** against **81.0%**
197
+ with the flags **+7.0pp**, Fisher exact **p = 0.31**. Not significant, but the direction
198
+ and magnitude match the fixture-level +7.4pp, so this is consistent with the gains
199
+ converting rather than evidence that they do. Resolving it is hard because **all 10 probes
200
+ flip between identical runs**, with `composite` passing 5/10 under an unchanged configuration, and `multihop` moving 2/10 to 6/10 with the flags — half of those passes at 2 steps or fewer, i.e. better ranking rather than the agent chaining. Raising k narrows the interval
201
+ around an unstable mean; it does not make the suite able to resolve a few-point
202
+ difference. The next step is probe determinism, not more repetitions. See
203
+ [`docs/benchmarks.md`](docs/benchmarks.md).
204
+
205
+ Two other numbers are easy to misread, so they are stated plainly:
206
+
207
+ - **`test:sleep` = 78.6% is a consolidation-*quality* score**, not recall falling to
208
+ 78.6%. It asks "after the maintenance pass, is recall at least as good and the
209
+ structure better?" Recall is held flat across three cycles while the graph reorganizes.
210
+ - **Token savings depend entirely on the baseline you pick.** vs carrying the full
211
+ history (what a memoryless agent must actually do): **+67% at 97.5% accuracy**. vs an
212
+ oracle that pre-scoped context to the exactly-relevant task: **≈ −13%** — a
213
+ deliberately brutal bar that hands the baseline the very scoping retrieval exists to
214
+ do. 0.13.x added a third and stricter measure, **sufficiency**: does the delivered
215
+ text actually *contain* the answer, or merely point at it?
216
+
217
+ > **LoCoMo was retired in 0.13.x.** It was useful for learning how to benchmark this
218
+ > product but does not represent it: median 115-char passages against a real store's
219
+ > 1,965; seeded in one shot, so decay, Hebbian weights and salience contribute nothing;
220
+ > no supersession, no cross-session use; it *rewards* indiscriminate retention, so the
221
+ > salience filterthe product caps its score regardless of ranking quality; and it
222
+ > is structurally blind to the 400-char truncation that turned out to affect 79% of real
223
+ > ground-truth identifiers. `tests/realstore-eval/` replaces it.
230
224
 
231
225
  ---
232
226
 
@@ -361,71 +355,32 @@ awm serve # From npm install
361
355
  npx tsx src/index.ts # From source
362
356
  ```
363
357
 
364
- Write a memory:
365
-
366
- ```bash
367
- curl -X POST http://localhost:8400/memory/write \
368
- -H "Content-Type: application/json" \
369
- -d '{
370
- "agentId": "my-agent",
371
- "concept": "Express error handling",
372
- "content": "Use centralized error middleware as the last app.use()",
373
- "eventType": "causal",
374
- "surprise": 0.5,
375
- "causalDepth": 0.7
376
- }'
377
- ```
378
-
379
- Recall:
380
-
381
358
  ```bash
382
- curl -X POST http://localhost:8400/memory/activate \
383
- -H "Content-Type: application/json" \
384
- -d '{
385
- "agentId": "my-agent",
386
- "context": "How should I handle errors in my Express API?"
387
- }'
388
- ```
389
-
390
- ### Substrate primitives (new in 0.8)
391
-
392
- For long-running structured projects — novels, codebases, investigations,
393
- design docs — where the agent needs to track typed state across hundreds
394
- of writes without polluting cognitive retrieval. Full reference at
395
- [`docs/reference.md`](https://github.com/CompleteIdeas/agent-working-memory/blob/master/docs/reference.md).
396
-
397
- ```bash
398
- # "Latest emotional state per character" — one round trip
399
- curl -X POST http://localhost:8400/memory/latest-by-tag -d '{
400
- "agentId": "novel-x", "tagKey": "character=",
401
- "scopeTagsAll": ["topic=emotional-state"], "sortBy": "sequence"
359
+ # Write
360
+ curl -X POST http://localhost:8400/memory/write -H "Content-Type: application/json" -d '{
361
+ "agentId": "my-agent",
362
+ "concept": "Express error handling",
363
+ "content": "Use centralized error middleware as the last app.use()",
364
+ "eventType": "causal", "surprise": 0.5, "causalDepth": 0.7
402
365
  }'
403
366
 
404
- # "Top 40 active promises by weight, excluding resolved" — filter + sort native
405
- curl -X POST http://localhost:8400/memory/top-by -d '{
406
- "agentId": "novel-x", "sortField": "weight=", "order": "desc",
407
- "filterTagsAll": ["topic=promise", "state=active"],
408
- "filterTagsNone": ["kind=advancement"], "limit": 40
367
+ # Recall
368
+ curl -X POST http://localhost:8400/memory/activate -H "Content-Type: application/json" -d '{
369
+ "agentId": "my-agent",
370
+ "context": "How should I handle errors in my Express API?"
409
371
  }'
410
-
411
- # Atomic write-and-supersede by concept match (Form B)
412
- curl -X POST http://localhost:8400/memory/supersede -d '{
413
- "agentId": "novel-x",
414
- "matchConcept": "Mara's deferred disclosure",
415
- "newEngram": {
416
- "concept": "Mara's disclosure — RESOLVED in Ch 3",
417
- "content": "...", "memory_class": "structural"
418
- }
419
- }'
420
-
421
- # Race-free chronology
422
- curl http://localhost:8400/memory/sequence/novel-x/next
423
372
  ```
424
373
 
425
- New `memory_class: "structural"` keeps high-volume system-written records
426
- (chapter analyses, promise advancements, commit logs) out of cognitive
427
- `/activate` while preserving them with canonical-level salience. See the
428
- [CHANGELOG entry for 0.8.0](https://github.com/CompleteIdeas/agent-working-memory/blob/master/CHANGELOG.md) for the full design.
374
+ **Substrate primitives (0.8+)** for long-running structured projects (novels,
375
+ codebases, investigations) where an agent tracks typed state across hundreds of writes
376
+ without polluting cognitive retrieval: `/memory/latest-by-tag` (latest per tag key),
377
+ `/memory/top-by` (native filter + sort), `/memory/supersede` (atomic write-and-supersede
378
+ by concept match), `/memory/sequence/:agentId/next` (race-free chronology). The
379
+ `memory_class: "structural"` class keeps high-volume system-written records out of
380
+ cognitive `/activate` while preserving them at canonical salience.
381
+
382
+ Every endpoint, with request/response schemas and worked examples:
383
+ [`docs/reference.md`](https://github.com/CompleteIdeas/agent-working-memory/blob/master/docs/reference.md).
429
384
 
430
385
  ---
431
386
 
@@ -491,76 +446,75 @@ For detailed architecture including pipeline phases, database schema, and system
491
446
 
492
447
  ## Testing & Evaluation
493
448
 
494
- ### Unit Tests
495
-
496
449
  ```bash
497
- npx vitest run # 77 tests (salience, decay, hebbian, supersession)
450
+ npx vitest run # Unit: salience, decay, hebbian, supersession
451
+ npm run eval # 4 benchmark suites
452
+ npm run eval -- --suite=retrieval # One suite
453
+ npm run eval -- --bm25-only # Ablation: isolate a channel's contribution
498
454
  ```
499
455
 
500
- ### Eval Harness (v0.6.0)
456
+ ### Real-store benchmark (0.13.x — replaces LoCoMo)
457
+
458
+ Measures the pipeline against a **frozen snapshot of a real store**, so passage lengths,
459
+ decay, supersession and Hebbian weights are all real rather than synthetic. Ground truth
460
+ is a unique-identifier hold-out verified through FTS, so it needs no hand labeling. And
461
+ correct **abstention scores positively** — selectivity is the product, so a benchmark
462
+ that punishes silence is measuring the wrong system.
501
463
 
502
464
  ```bash
503
- npm run eval # All 4 benchmark suites
504
- npm run eval -- --suite=retrieval # Single suite
505
- npm run eval -- --bm25-only # Ablation: BM25 only
506
- npm run eval -- --no-graph-walk # Ablation: disable graph walk
465
+ node tests/realstore-eval/snapshot.mjs # freeze a copy of the live store
466
+ npx tsx tests/realstore-eval/runner.ts # identifier fixture (regression guard)
467
+ REALSTORE_FIXTURE=fixture-category.json \
468
+ npx tsx tests/realstore-eval/runner.ts # category fixture (retrievability)
469
+ bash tests/realstore-eval/campaign/full-comparison.sh # baseline vs recommended, all suites
507
470
  ```
508
471
 
509
- Suites: retrieval (Recall@5), associative (multi-hop), redundancy (dedup F1), temporal (Spearman vs ACT-R). Ablation flags isolate each pipeline component's contribution.
472
+ Each run works on a **copy** activation mutates access counts, and a benchmark must
473
+ not drift the thing it measures. The runner prints the active flag fingerprint, so every
474
+ result records which configuration produced it.
475
+
476
+ Per-suite methodology, scoring, and the remaining `test:*` scripts:
477
+ [`docs/benchmarks.md`](docs/benchmarks.md).
510
478
 
511
- ### Full Test Suite
479
+ ---
480
+
481
+ ## Environment Variables
482
+
483
+ **Recommended recall configuration (0.13.x)** — default-OFF, but measured wins. Enable
484
+ all three together (see [Benchmarks](#benchmarks)):
512
485
 
513
486
  ```bash
514
- npm run test:mcp # MCP protocol smoke test (5/5)
515
- npm run test:self # Pipeline component checks (94.1%)
516
- npm run test:edge # 9 adversarial failure modes
517
- npm run test:stress # 500 memories, 100 consolidation cycles (96.2%)
518
- npm run test:workday # 4-session production simulation (93.3%)
519
- npm run test:ab # AWM vs baseline comparison
520
- npm run test:sleep # Consolidation impact measurement
521
- npm run test:tokens # Token savings analysis (56.3% savings)
522
- npm run test:pilot # Production-like query validation (14/15)
523
- npm run test:locomo # LoCoMo industry benchmark (28.2%)
487
+ AWM_RERANK2=1 AWM_RERANK_WINDOW=query AWM_RERANK_TAGS=1
524
488
  ```
525
489
 
526
- ---
490
+ | Variable | Effect |
491
+ |---|---|
492
+ | `AWM_RERANK2=1` | Second-stage reorder of the returned window by cross-encoder score alone, after the abstention gate. **+9.7pp s@1.** Must be paired with `AWM_RERANK_WINDOW=query` |
493
+ | `AWM_RERANK_WINDOW=query` | Spend the rerank char budget on the window densest in query terms instead of the first N chars. **25% → 87.5%** long-memory s@1 |
494
+ | `AWM_RERANK_TAGS=1` | Append structured tags to the rerank passage, so category words that exist only as tags reach the deciding stage. **+7.4pp s@1** |
527
495
 
528
- ## Environment Variables
496
+ Verify what a running process actually has — `memory_whoami` prints a `Recall config:`
497
+ line and `GET /health` reports the same fingerprint. A submodule bump can report a new
498
+ version while the flags never reached the process; on version alone that looks like success.
499
+
500
+ **Core settings:**
529
501
 
530
502
  | Variable | Default | Purpose |
531
503
  |----------|---------|---------|
532
- | `AWM_PORT` | `8400` | HTTP server port |
533
- | `AWM_DB_PATH` | `memory.db` | SQLite database path |
534
- | `AWM_AGENT_ID` | `claude-code` | Agent ID (memory namespace) |
535
- | `AWM_EMBED_MODEL` | `Xenova/bge-small-en-v1.5` | Embedding model (retrieval-optimized) |
536
- | `AWM_EMBED_DIMS` | `384` | Embedding dimensions |
537
- | `AWM_RERANKER_MODEL` | `Xenova/ms-marco-MiniLM-L-6-v2` | Reranker model |
538
- | `AWM_HOOK_PORT` | `8401` | Hook sidecar port |
539
- | `AWM_HOOK_SECRET` | *(none)* | Bearer token for hook auth |
540
- | `AWM_API_KEY` | *(none)* | Bearer token for HTTP API auth |
541
- | `AWM_INCOGNITO` | *(unset)* | Set to `1` to disable all tools |
542
- | `AWM_COORDINATION` | *(unset)* | Set to `true` to enable hive coordination endpoints |
543
- | `AWM_DISABLE_POOL_FILTER` | *(unset)* | Set to `1` to disable the candidate pool reduction (0.7.7+). Reverts recall to scoring all active candidates — slower but useful for A/B testing if a recall regression appears |
544
- | `AWM_DISABLE_SLIM_CACHE` | *(unset)* | Set to `1` to disable the in-memory slim cache (0.7.10+). Reverts to per-recall SQL fetch — slower but useful if cache invariants are suspected of drift |
545
- | `AWM_DISABLE_RERANK_SKIP` | *(unset)* | Set to `1` to disable the reranker skip on clear-winner queries (0.7.10+). Forces every recall through the cross-encoder |
546
- | `AWM_DISABLE_EXPANSION_CACHE` | *(unset)* | Set to `1` to disable the query expansion skip heuristic + LRU cache (0.7.11+). Forces every recall through the flan-t5-small expander |
504
+ | `AWM_DB_PATH` | `memory.db` (SQLite) / `./memory-pglite` (PGlite) | Storage path file for SQLite, directory for PGlite |
505
+ | `AWM_STORE_BACKEND` | `sqlite` | `sqlite` (WAL, multi-process safe) · `pglite` (single-process) · `postgres` (networked, **experimental**) |
506
+ | `AWM_AGENT_ID` | `claude-code` | Agent id — the memory namespace. Pin it explicitly; an unpinned session lands in a per-directory UUID space nothing else can recall |
547
507
  | `AWM_WORKSPACE` | *(unset)* | Default workspace for cross-agent recall in hive setups |
548
- | `AWM_SLOW_WRITE_MS` | `250` | Slow-write telemetry threshold in ms; any write over this logs one stderr line with a phase-time breakdown (embed/novelty/persist, event-loop lag, cold-load ms). `0` disables (v0.12.0) |
549
- | `AWM_ENTITY_INDEX_FETCH` | *(unset)* | Set to `1` to let query-named entities ("ticket 19252", a person's name) resolve through the entity inverted index for a guaranteed reranker audition, including alias hops. Default off pending broader eval (v0.12.0) |
550
- | `AWM_ENTITY_INDEX_CAP` | `12` | Max entity-index candidates injected per recall when `AWM_ENTITY_INDEX_FETCH=1` (v0.12.0) |
551
- | `AWM_STORE_BACKEND` | `sqlite` | `sqlite` (better-sqlite3 + FTS5), `pglite` (PGlite + pgvector + pgroonga), or `postgres` (node-postgres + pgvector, networked/multi-connection **experimental**, 0.10.0). |
552
- | `AWM_DB_PATH` | `memory.db` (SQLite) / `./memory-pglite` (PGlite) | Storage path. Directory for PGlite, file for SQLite. Ignored for `postgres` (uses `AWM_DATABASE_URL`). |
553
- | `AWM_DATABASE_URL` | *(unset)* | Postgres connection string when `AWM_STORE_BACKEND=postgres` (0.10.0). |
554
- | `AWM_CONF_SHARPNESS_W` | `0.4` | Weight of `top1 / mean(top5)` in recall confidence (PR-1, v0.8.5) |
555
- | `AWM_CONF_CLIFF_W` | `0.3` | Weight of `(top1 - top10) / top1` in recall confidence (PR-1, v0.8.5) |
556
- | `AWM_CONF_FLOOR_W` | `0.3` | Weight of `top1` absolute score in recall confidence (PR-1, v0.8.5) |
557
- | `AWM_FADE_DAYS_SINCE_ACCESS` | `45` | Days without access before a stale active engram fades (v0.8.5) |
558
- | `AWM_FADE_KEEP_CHARS` | `150` | Chars retained in faded engram content (v0.8.5) |
559
- | `AWM_FADE_MIN_CONTENT_LEN` | `250` | Don't fade engrams shorter than this — nothing to trim (v0.8.5) |
560
- | `AWM_FADE_MAX_PER_CYCLE` | `25` | Max engrams faded per consolidation cycle — gradual, not sudden (v0.8.5) |
561
- | `AWM_GRANULARITY_COMPACT_LEN` | `200` | Char cap for `granularity: 'compact'` summaries (v0.8.5) |
562
- | `AWM_GRANULARITY_FULL_LEN` | `1000` | Char cap for top result under `granularity: 'auto'` when confidence ≥ threshold (v0.8.5) |
563
- | `AWM_GRANULARITY_AUTO_THRESHOLD` | `0.4` | Recall-confidence threshold above which `'auto'` granularity gives the top result a long-form summary (v0.8.5) |
508
+ | `AWM_PORT` / `AWM_HOOK_PORT` | `8400` / `8401` | HTTP server and hook sidecar ports |
509
+ | `AWM_API_KEY` / `AWM_HOOK_SECRET` | *(none)* | Bearer tokens. Binding beyond loopback without an API key fails closed |
510
+ | `AWM_INCOGNITO` | *(unset)* | `1` disables all tools |
511
+ | `AWM_EMBED_MODEL` / `AWM_EMBED_DIMS` | `Xenova/bge-small-en-v1.5` / `384` | `cosineSimilarity` returns **0** on dimension mismatch migrate the whole corpus or not at all |
512
+
513
+ Every variable including the salience, decay, fade, confidence, granularity and
514
+ diagnostic knobs, each with its measured effect and the **rejected** experiments and why
515
+ they lost is documented in [`docs/reference.md`](https://github.com/CompleteIdeas/agent-working-memory/blob/master/docs/reference.md).
516
+
517
+ ---
564
518
 
565
519
  ## Tech Stack
566
520
 
@@ -579,44 +533,50 @@ npm run test:locomo # LoCoMo industry benchmark (28.2%)
579
533
 
580
534
  All three ML models run locally via ONNX. No external API calls for retrieval. The entire system is a single SQLite file + a Node.js process.
581
535
 
582
- ## What's New in v0.12.x (latest)
583
-
584
- Three releases (0.12.0-0.12.2), eval-driven. All additive, no breaking API changes.
585
-
586
- - **Instance identity (`memory_whoami`)** — agent id, workspace, mode, backend, store
587
- path, code provenance, sibling agent spaces. Call it first whenever you're unsure
588
- which store or which running code you're talking to.
589
- - **Entity inverted index** structured identifier tags (`ticket=`, `person=`, bare
590
- ids) feed an exact-match index. A query naming an entity resolves through it even
591
- when the wording doesn't lexically match. Default off (`AWM_ENTITY_INDEX_FETCH=1`),
592
- guaranteed reranker audition, no score boost.
593
- - **Local-first security defaults** HTTP binds `127.0.0.1` by default; widening
594
- beyond loopback without `AWM_API_KEY` fails closed.
595
- - **Write-path telemetry** always-on slow-write attribution (`AWM_SLOW_WRITE_MS`,
596
- default 250ms) names the phase, event-loop lag, and cold-load cost on any write
597
- that's slow enough to matter.
598
- - **Memory spine (provenance)** `origin_class`, `recipe_id`, `valid_from`/`valid_to`
599
- on every write. `valid_to` expires operational facts instead of relying on the
600
- reader to notice they're stale; recall renders `[valid until ...]` on results
601
- carrying it.
602
- - **Cognition recipes** AWM contains no LLM. When memory needs real thinking
603
- (distilling a procedure, reflecting on a failure), `memory_task_end` hands the host
604
- agent a versioned prompt+contract pair (`skill-derivation@1`, `friction-lesson@1`)
605
- to run as its own focused pass, then validates the write-back.
606
- - **Recall results carry engram ids** (`[id: <uuid>]`) feed a recalled memory
607
- straight into `memory_supersede`/`memory_feedback` with no separate lookup.
608
- - **Eager warm at MCP startup + sidecar warm recall** — model load overlaps session
609
- start instead of your first message; the hook sidecar gained `POST /memory/activate`
610
- for ~0.8s warm recall from a hook, with no standing server needed.
611
- - **Gauntlet baseline** end-to-end memory ablation now anchors acceptance:
612
- **74%±5pp memory-dependent vs 0% no-memory control**, six of nine probes at 100%.
613
- See [`docs/gauntlet-baseline-2026-07-30.md`](docs/gauntlet-baseline-2026-07-30.md).
536
+ ## What's New in v0.14.0 (latest)
537
+
538
+ Retrieval-quality releases, all additive. Corpus provenance differs per result and is
539
+ named in [Benchmarks](#benchmarks) — only the tags result and the combined figure come
540
+ from a real-store snapshot.
541
+
542
+ - **Second-stage rerank (`AWM_RERANK2`)** final order was a blend that capped the
543
+ cross-encoder at 70% of the vote. It disagrees with that blend about rank 1 on 38.6%
544
+ of queries, and where the disagreement is decidable **the cross-encoder is right 77%
545
+ of the time**. Re-sorting by its score: **+9.7pp s@1**, no added inference. Placed
546
+ after the abstention gate so it cannot affect selectivity — predicted 0 broken / 0
547
+ fixed on adversarial, and measured exactly that.
548
+ - **Query-aware rerank window (`AWM_RERANK_WINDOW=query`)** truncating passages to
549
+ the first 400 chars is necessary (cross-encoders pad to the longest passage in a
550
+ batch), but a *prefix* is the wrong 400. Real canonical memories are median 1,965
551
+ chars, 98.7% exceed 400, and **99.9%** of long ones carry their identifiers only
552
+ past char 400. Same budget, densest window: **25% → 87.5%**.
553
+ - **`memory_whoami` reports the effective recall config** version alone does not
554
+ answer "what am I actually running". This caught a real deployment failure the day
555
+ it shipped: a project-level `.mcp.json` was overriding the config being edited, so
556
+ the new version reported success while the flags never reached the process.
557
+ - **Tags into the rerank passage (`AWM_RERANK_TAGS`)** **+7.4pp s@1**, with no
558
+ re-embed, no new model and no write-path change; existing corpora benefit immediately
559
+ because the tags are already stored.
560
+ - **Writing guidance corrected at the source** the shipped advice was *causing* the
561
+ problem it warned about. "Pick the most specific topic" pushed authors away from
562
+ category words, reliably producing memories that are maximally specific and
563
+ categorically anonymous. Two new rules: **name the CATEGORY as well as the
564
+ specifics**, and **tags are not a substitute for body text** (only BM25 indexes tags —
565
+ the embedding and the rerank passage are both built from `concept + content`, so a
566
+ tag-only word is invisible to two of three channels, including the one that now
567
+ decides ordering).
568
+
569
+ ### Previously, in v0.12.x
570
+
571
+ `memory_whoami` instance identity · entity inverted index (`AWM_ENTITY_INDEX_FETCH=1`,
572
+ opt-in) · local-first security defaults (loopback bind, fail-closed without an API key)
573
+ · write-path slow-write telemetry · memory-spine provenance (`origin_class`,
574
+ `valid_from`/`valid_to`) · cognition recipes at `memory_task_end` · engram ids in
575
+ recall results · eager warm at MCP startup + sidecar warm recall.
614
576
 
615
577
  Full version-by-version history — every release back to v0.6.0, including the
616
578
  0.7.6→0.7.14 latency work (11s→300ms) and the 0.8.5 recall-quality hardening pass —
617
- lives in the changelog, not here:
618
-
619
- See [CHANGELOG.md](https://github.com/CompleteIdeas/agent-working-memory/blob/master/CHANGELOG.md) for full details.
579
+ lives in [CHANGELOG.md](https://github.com/CompleteIdeas/agent-working-memory/blob/master/CHANGELOG.md).
620
580
 
621
581
  ## Integrations
622
582
 
@@ -661,7 +621,7 @@ gotchas (incl. the Windows CRLF/s6 clone fix) — is in
661
621
 
662
622
  ## Project Status
663
623
 
664
- AWM is in active development (v0.12.2). The core memory pipeline, consolidation
624
+ AWM is in active development (v0.14.0). The core memory pipeline, consolidation
665
625
  system, multi-agent coordination, and MCP integration are stable and used
666
626
  daily in production coding workflows.
667
627
 
@@ -681,6 +641,8 @@ daily in production coding workflows.
681
641
  - Backend-agnostic `import`/`export` (embeddings included, cross-backend port): **stable** (v0.10.0)
682
642
  - Instance identity (`memory_whoami`), local-first security defaults, write-path telemetry, memory-spine provenance (`origin_class`/`valid_from`/`valid_to`), cognition recipes: **stable** (v0.12.0)
683
643
  - Entity inverted index + guarded index-backed retrieval: **stable, opt-in** (`AWM_ENTITY_INDEX_FETCH=1`, default off pending broader eval) (v0.12.0)
644
+ - Second-stage rerank, query-aware rerank window, tags-into-rerank: **stable, opt-in** (`AWM_RERANK2=1 AWM_RERANK_WINDOW=query AWM_RERANK_TAGS=1` — enable together) (v0.13.4-0.13.6)
645
+ - Real-store benchmark (`tests/realstore-eval/`), replacing LoCoMo: **stable** (v0.13.x)
684
646
 
685
647
  See [CHANGELOG.md](https://github.com/CompleteIdeas/agent-working-memory/blob/master/CHANGELOG.md) for version history.
686
648
 
@@ -44,9 +44,15 @@ export declare function homedir(): string;
44
44
  *
45
45
  * Returns a short human-readable status string for the setup command output.
46
46
  */
47
+ /** Opening marker for the generated block. Everything between the markers is replaced
48
+ * on upgrade; anything outside them is preserved. The text is deliberately addressed to
49
+ * whoever opens the file, because that is who needs to know. */
50
+ export declare const AWM_GEN_BEGIN: string;
51
+ export declare const AWM_GEN_END = "<!-- AWM:GENERATED:END -->";
47
52
  export declare function upsertAwmSection(filePath: string, newContent: string, options?: {
48
53
  titleIfNew?: string;
49
54
  suffix?: string;
55
+ force?: boolean;
50
56
  }): string;
51
57
  export declare const AWM_INSTRUCTION_CONTENT: string;
52
58
  //# sourceMappingURL=common.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/adapters/common.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK/C,gEAAgE;AAChE,wBAAgB,kBAAkB,IAAI,MAAM,CAG3C;AAED,2EAA2E;AAC3E,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAOnF;AAED,8CAA8C;AAC9C,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAUxD;AAED,8DAA8D;AAC9D,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,OAAO,GACjB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOxB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,GAAG;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAkBA;AAED,uDAAuD;AACvD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,YAAY,CA6Bf;AAED,sBAAsB;AACtB,wBAAgB,OAAO,IAAI,MAAM,CAEhC;AAID;;;GAGG;AACH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GACrD,MAAM,CA8CR;AAED,eAAO,MAAM,uBAAuB,QAsWvB,CAAC"}
1
+ {"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/adapters/common.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK/C,gEAAgE;AAChE,wBAAgB,kBAAkB,IAAI,MAAM,CAG3C;AAED,2EAA2E;AAC3E,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAOnF;AAED,8CAA8C;AAC9C,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAUxD;AAED,8DAA8D;AAC9D,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,OAAO,GACjB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOxB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,GAAG;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAkBA;AAED,uDAAuD;AACvD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,YAAY,CA6Bf;AAED,sBAAsB;AACtB,wBAAgB,OAAO,IAAI,MAAM,CAEhC;AAID;;;GAGG;AACH;;;;;;;;;;;;;GAaG;AACH;;iEAEiE;AACjE,eAAO,MAAM,aAAa,QAEsD,CAAC;AACjF,eAAO,MAAM,WAAW,+BAA+B,CAAC;AAOxD,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GACtE,MAAM,CAkFR;AAED,eAAO,MAAM,uBAAuB,QAwZvB,CAAC"}