agent-working-memory 0.15.8 → 0.16.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 (81) hide show
  1. package/AGENTS.md +36 -0
  2. package/README.md +24 -12
  3. package/dist/adapters/common.d.ts +1 -1
  4. package/dist/adapters/common.d.ts.map +1 -1
  5. package/dist/adapters/common.js +33 -20
  6. package/dist/adapters/common.js.map +1 -1
  7. package/dist/core/embeddings.d.ts +1 -1
  8. package/dist/core/embeddings.js +1 -1
  9. package/dist/core/entity-extract.js +1 -1
  10. package/dist/core/query-expander.d.ts +4 -2
  11. package/dist/core/query-expander.d.ts.map +1 -1
  12. package/dist/core/query-expander.js +4 -2
  13. package/dist/core/query-expander.js.map +1 -1
  14. package/dist/core/recall-config.d.ts +1 -1
  15. package/dist/core/recall-config.d.ts.map +1 -1
  16. package/dist/core/recall-config.js +6 -1
  17. package/dist/core/recall-config.js.map +1 -1
  18. package/dist/core/recall-telemetry.d.ts +15 -0
  19. package/dist/core/recall-telemetry.d.ts.map +1 -0
  20. package/dist/core/recall-telemetry.js +108 -0
  21. package/dist/core/recall-telemetry.js.map +1 -0
  22. package/dist/core/rerank-window.d.ts +5 -4
  23. package/dist/core/rerank-window.d.ts.map +1 -1
  24. package/dist/core/rerank-window.js +5 -4
  25. package/dist/core/rerank-window.js.map +1 -1
  26. package/dist/core/reranker.d.ts +2 -1
  27. package/dist/core/reranker.d.ts.map +1 -1
  28. package/dist/core/reranker.js +23 -3
  29. package/dist/core/reranker.js.map +1 -1
  30. package/dist/core/salience.js +1 -1
  31. package/dist/core/salience.js.map +1 -1
  32. package/dist/core/token-budget.d.ts +2 -2
  33. package/dist/core/token-budget.js +2 -2
  34. package/dist/core/whoami.d.ts +1 -1
  35. package/dist/core/write-pipeline.d.ts.map +1 -1
  36. package/dist/core/write-pipeline.js +19 -0
  37. package/dist/core/write-pipeline.js.map +1 -1
  38. package/dist/engine/activation.d.ts +30 -13
  39. package/dist/engine/activation.d.ts.map +1 -1
  40. package/dist/engine/activation.js +159 -50
  41. package/dist/engine/activation.js.map +1 -1
  42. package/dist/index.js +1 -1
  43. package/dist/index.js.map +1 -1
  44. package/dist/mcp.js +98 -98
  45. package/dist/mcp.js.map +1 -1
  46. package/dist/storage/pglite.d.ts +28 -0
  47. package/dist/storage/pglite.d.ts.map +1 -1
  48. package/dist/storage/pglite.js +55 -0
  49. package/dist/storage/pglite.js.map +1 -1
  50. package/dist/storage/postgres.d.ts +28 -0
  51. package/dist/storage/postgres.d.ts.map +1 -1
  52. package/dist/storage/postgres.js +55 -0
  53. package/dist/storage/postgres.js.map +1 -1
  54. package/dist/storage/sqlite.d.ts +28 -0
  55. package/dist/storage/sqlite.d.ts.map +1 -1
  56. package/dist/storage/sqlite.js +56 -0
  57. package/dist/storage/sqlite.js.map +1 -1
  58. package/llms.txt +437 -0
  59. package/package.json +7 -1
  60. package/plugin/.claude-plugin/plugin.json +1 -1
  61. package/plugin/README.md +1 -1
  62. package/plugin/bin/awm-mcp-launcher.cjs +2 -2
  63. package/plugin/skills/awm-memory/SKILL.md +33 -20
  64. package/src/adapters/common.ts +33 -20
  65. package/src/core/embeddings.ts +1 -1
  66. package/src/core/entity-extract.ts +1 -1
  67. package/src/core/query-expander.ts +4 -2
  68. package/src/core/recall-config.ts +6 -1
  69. package/src/core/recall-telemetry.ts +131 -0
  70. package/src/core/rerank-window.ts +159 -158
  71. package/src/core/reranker.ts +24 -3
  72. package/src/core/salience.ts +1 -1
  73. package/src/core/token-budget.ts +160 -160
  74. package/src/core/whoami.ts +120 -120
  75. package/src/core/write-pipeline.ts +36 -18
  76. package/src/engine/activation.ts +159 -51
  77. package/src/index.ts +1 -1
  78. package/src/mcp.ts +1475 -1475
  79. package/src/storage/pglite.ts +49 -0
  80. package/src/storage/postgres.ts +49 -0
  81. package/src/storage/sqlite.ts +51 -0
package/llms.txt ADDED
@@ -0,0 +1,437 @@
1
+ # AWM — agent-working-memory
2
+
3
+ > Persistent, local-first memory for AI coding agents. One Node process, one
4
+ > SQLite file, no cloud and no API keys. It decides what is worth keeping,
5
+ > returns the two or three memories that matter, and returns nothing when
6
+ > nothing fits.
7
+
8
+ **If you are a language model, read this file first.** It is written to be
9
+ ingested whole. It gives you the mental model, the setup for each situation,
10
+ the configuration that actually matters, and — most importantly — what breaks
11
+ when AWM is installed but not configured well. That last part is where almost
12
+ every disappointing result comes from.
13
+
14
+ Repository: https://github.com/CompleteIdeas/agent-working-memory
15
+ Package: `agent-working-memory` on npm · Apache-2.0 · Node >= 22
16
+ Docs site: https://completeideas.github.io/agent-working-memory/
17
+
18
+ Every number in this file is measured and reproducible from the repository.
19
+ Where a claim has a source, the source is named. Where something is unknown or
20
+ weak, it says so.
21
+
22
+ ---
23
+
24
+ ## 1. What it is, and what it is not
25
+
26
+ **It is** a retrieval store with a salience filter in front of it and a decay
27
+ model underneath. An agent writes short notes as it works; AWM decides whether
28
+ each is worth keeping, embeds and indexes it, and hands back the few that match
29
+ a later query.
30
+
31
+ **It is not** a vector database, and it is not a transcript log. It does not
32
+ store your conversation. It does not traverse a graph to answer multi-hop
33
+ questions. It will not make an agent remember things it never wrote down.
34
+
35
+ **The honest version of who it suits:** projects with accumulated decisions
36
+ that outlive a single session, where an agent re-deriving the same context is a
37
+ real cost. On a small or short-lived project the overhead does not pay back.
38
+ `docs/known-limitations.md` and the "Is this for you?" section of `README.md`
39
+ both say this plainly; believe them.
40
+
41
+ ---
42
+
43
+ ## 2. How retrieval actually works
44
+
45
+ This is the single most useful thing for you to understand, because the common
46
+ failure mode is writing memories that no channel can see.
47
+
48
+ A recall runs **three scoring channels**, then a reranker decides the order:
49
+
50
+ 1. **Embedding** — `Xenova/bge-small-en-v1.5`, 384 dimensions, ONNX, downloaded
51
+ on first use and then run locally. The vector is built from
52
+ **`concept` + `content`**.
53
+ 2. **BM25** — SQLite FTS5 over `concept`, `content` and `tags`. This is the
54
+ only channel that sees tags.
55
+ 3. **Cross-encoder reranker** — `Xenova/ms-marco-MiniLM-L-6-v2`. It makes the
56
+ **final ordering**. Its passage is built from `concept` + `content`, plus
57
+ tags when `AWM_RERANK_TAGS=1`.
58
+
59
+ Then two things that matter less than they sound:
60
+
61
+ - **The association graph** contributes a score to about **85%** of top-3
62
+ results but changes the top-3 **set in 0%** of them. The reranker overrules
63
+ it; the graph's boost is capped and never wins. It is retained for the state
64
+ it accumulates — supersede chains, causal links, co-recall history — not for
65
+ today's ranking. (`tests/realstore-eval/graph-contribution-probe.ts`.)
66
+ - **ACT-R decay** scores every memory by age, so recall results shift as a
67
+ store ages. Benchmarks must pin the clock or they drift day to day.
68
+
69
+ ### The three consequences you should act on
70
+
71
+ - **A word that exists only as a tag is invisible to two of the three
72
+ channels.** Tag it *and* write it in the body. Measured on a real 30k store:
73
+ **66%** of the topical terms a memory is tagged with never appear in its
74
+ text.
75
+ - **The reranker reads a 400-character window.** Anything past it is not seen
76
+ by the stage that decides final order. This is not a rounding error — on the
77
+ public benchmark corpus (`npm run bench:public`, 400 probes), `s@1` is
78
+ **97.3%** when the answer-bearing identifier sits inside the window and
79
+ **46.8%** when it sits beyond it. Put the decisive identifier **early**.
80
+ - **Multi-hop is your job.** "Who owns the table that the failing report
81
+ reads?" is two recalls that you make, not one traversal AWM performs. One hop
82
+ per call.
83
+
84
+ ---
85
+
86
+ ## 3. Configuration that is not optional
87
+
88
+ `awm setup` installs three retrieval flags. **If they are missing, retrieval is
89
+ materially worse and nothing warns you.** This is the number one cause of "AWM
90
+ didn't find it."
91
+
92
+ ```
93
+ AWM_RERANK2=1 # second-stage rerank by the cross-encoder's own score
94
+ AWM_RERANK_WINDOW=query # 400-char window centered on the densest query terms,
95
+ # instead of truncating from the start of the body
96
+ AWM_RERANK_TAGS=1 # tags joined into the rerank passage (+7.4pp s@1)
97
+ ```
98
+
99
+ `AWM_RERANK_WINDOW=query` is the difference between the reranker reading the
100
+ first 400 characters and reading the 400 characters that actually contain the
101
+ query terms. On long memories — and real memories are long, median ~1,965
102
+ characters — that is the difference between finding the fact and missing it.
103
+
104
+ **Check yours before diagnosing anything else:**
105
+
106
+ ```bash
107
+ awm doctor # reports config, live sidecars, store, and what is missing
108
+ ```
109
+
110
+ If you installed via something other than `awm setup` — hand-written MCP
111
+ config, a vendored copy, a container — assume these are unset until you have
112
+ seen otherwise.
113
+
114
+ ---
115
+
116
+ ## 4. Setup, by scenario
117
+
118
+ Pick the one that matches. All of them end up at the same store by default:
119
+ **`~/.awm/memory.db`**.
120
+
121
+ > **Upgrade note.** Before 0.15.6 the default store lived *inside* the
122
+ > installed npm package, where `npm update` deleted it. If you are coming from
123
+ > an older version, find the old file before upgrading and move it to
124
+ > `~/.awm/memory.db`. 0.15.7 corrected the docs that still taught the old path.
125
+
126
+ ### A. Claude Code, global (the normal case)
127
+
128
+ ```bash
129
+ npm install -g agent-working-memory
130
+ awm setup --global
131
+ ```
132
+
133
+ Restart Claude Code. **19 tools** appear. `awm setup` also writes agent
134
+ instructions into your global `CLAUDE.md` and installs hooks that checkpoint on
135
+ compaction and session end.
136
+
137
+ Flags: `--no-instructions` (skip the CLAUDE.md edit), `--no-hooks`, `--force`,
138
+ and `--no-prime`, which skips the **UserPromptSubmit prime hook** — the thing
139
+ that injects relevant memories on *every prompt*. Without it, as `awm doctor`
140
+ says, "memory reaches the agent only when it chooses to call memory_recall."
141
+ If an agent seems not to be using its memory, check this first.
142
+
143
+ ### B. Separate pools per project
144
+
145
+ Not a different install — a different **agent id**. One database file, isolation
146
+ by agent. Give each project its own `.mcp.json` with a distinct `AWM_AGENT_ID`:
147
+
148
+ ```
149
+ ~/work/project-a/.mcp.json → AWM_AGENT_ID: "project-a"
150
+ ~/work/project-b/.mcp.json → AWM_AGENT_ID: "project-b"
151
+ ```
152
+
153
+ Each then gets its own namespace in the same store. See
154
+ `docs/claude-code-setup.md` ("Separate Memory Pools").
155
+
156
+ ### C. Claude Code plugin
157
+
158
+ ```bash
159
+ awm plugin # prints where it is and how to install it
160
+ ```
161
+
162
+ See `docs/plugin.md`. **Upgrading a plugin is not automatic** — a directory
163
+ marketplace does not pull new versions for you. This has bitten people; the doc
164
+ says so explicitly.
165
+
166
+ ### D. Claude Desktop (MCPB bundle)
167
+
168
+ `npm run pack:mcpb` produces the bundle, or take it from a release. Install it
169
+ by double-clicking the `.mcpb`, dragging it onto the Claude Desktop window, or
170
+ **Settings → Extensions → Advanced settings → Install Extension…**. Desktop then
171
+ asks for two settings: **Memory database** (default `${HOME}/.awm/memory.db` —
172
+ leave it to share one store with Claude Code) and **Memory pool** (default
173
+ `work`). Restart Desktop and you should have nineteen tools.
174
+
175
+ **Sharing the store with your CLI install is the point** — Desktop and Claude
176
+ Code then see each other's memories. See `docs/desktop.md`.
177
+
178
+ ### E. Server / container
179
+
180
+ ```bash
181
+ awm serve --port 8400
182
+ ```
183
+
184
+ HTTP API, `GET /health`, Docker recipe and Railway / Fly / Render notes in
185
+ `docs/deployment.md`. **Agent isolation on the HTTP API is by convention, not
186
+ enforcement** — any caller can claim any `agentId`. Do not expose it to
187
+ untrusted callers.
188
+
189
+ ### F. Multi-agent / shared pool
190
+
191
+ Several processes against **one SQLite file** is supported and is the
192
+ interesting case: agent A writes, agent B recalls it on its next call, with no
193
+ network and no central service. SQLite WAL makes this multi-process safe.
194
+
195
+ **The thing that silently breaks it: unpinned agent identity.** If a session
196
+ does not set an explicit agent id, it lands in a per-directory fallback space
197
+ that scoped recall never sees. The memory is written and is simply invisible to
198
+ everyone else. Always set the identity explicitly, and call `memory_whoami` if
199
+ you are unsure which space you are in.
200
+
201
+ Cross-agent recall is opt-in per call via the `workspace` parameter; the
202
+ default is agent-scoped.
203
+
204
+ ### G. Embedded in your own app
205
+
206
+ Import the store and engine directly — see `examples/` and
207
+ `docs/architecture.md`. `tests/longmem-eval/runner.ts` is a compact, working
208
+ example of seeding a store through the real write pipeline and querying it.
209
+
210
+ ### Backend choice
211
+
212
+ - **SQLite** (default) — multi-process safe via WAL. Use this.
213
+ - **PGlite** — embedded Postgres with pgvector. **Single-process only**: a
214
+ second process against the same directory aborts. Only for single-process
215
+ embedding.
216
+ - **Networked Postgres** — a genuinely shared backend across machines.
217
+
218
+ Auto-detected: if `AWM_DB_PATH` points at an existing directory it is PGlite,
219
+ a file is SQLite.
220
+
221
+ ---
222
+
223
+ ## 5. The 19 tools
224
+
225
+ **Lifecycle** — `memory_restore` (session start), `memory_task_begin`,
226
+ `memory_task_end`, `memory_checkpoint`.
227
+
228
+ **Core** — `memory_write`, `memory_recall`.
229
+
230
+ **Correction** — `memory_supersede` (replace a stale fact; the new version
231
+ inherits the old one's associations), `memory_retract` (it was wrong),
232
+ `memory_feedback` (useful / not-useful, after you used a result).
233
+
234
+ **Tasks** — `memory_task_add`, `memory_task_update`, `memory_task_list`,
235
+ `memory_task_next`.
236
+
237
+ **Introspection** — `memory_whoami` (identity, store, backend, ports, sibling
238
+ agent spaces — call this *first* whenever anything about AWM's own state is in
239
+ question), `memory_stats`.
240
+
241
+ **Onboarding** — `onboard_scan`, `onboard_questions`.
242
+
243
+ **Output control** — `compress_output`, `retrieve_original`.
244
+
245
+ ### Recall parameters worth knowing
246
+
247
+ - `granularity: 'compact'` — every result carries a ~200-char query-aware
248
+ snippet. Saves roughly 70% of recall output tokens when you expect to scan
249
+ several results. `'auto'` is confidence-adaptive.
250
+ - `require_confidence` — abstain rather than return noise. Default is a light
251
+ `0.05`. **Do not raise it for ordinary recall**: a miss is expensive because
252
+ you then go and read the codebase instead (~2,106 tokens, measured), and
253
+ thresholds of 0.20+ cut net tokens saved by 25% in `tests/abstention-eval`.
254
+ Raise it only for push-style use where nobody asked.
255
+ - `workspace` — cross-agent recall, opt-in.
256
+ - `limit` — default **3**, and this is a measured choice, not a guess. On 400
257
+ real-store probes k=3 is the only **token-positive** setting: **+115 tokens
258
+ per recall, against -680 at k=5**. It is also 41% faster at p50, because
259
+ `limit` sizes the cross-encoder rerank pool and reranking is ~90% of warm
260
+ recall time. The cost is ~0.5pp `success@1` versus k=5 — about 3 queries in
261
+ 400, inside noise. Raise it deliberately when you want breadth for
262
+ orientation; leave it alone when you want a specific fact.
263
+
264
+ ---
265
+
266
+ ## 6. Writing so that recall can find it
267
+
268
+ Recall quality is decided when you write. Be slightly more verbose at the front
269
+ than feels natural: the head of the body is what all three channels weight most.
270
+
271
+ - **Lead with the fact or rule.** Not the backstory. "Don't mock the database
272
+ in integration tests" first; the reason second.
273
+ - **Put the decisive identifier early** — before character 400 if you can. See
274
+ §2.
275
+ - **Include two or more retrievable identifiers**: file paths, function names,
276
+ column names, error strings, ticket ids. `SettlementService.closeRun()`
277
+ beats "the accounting code".
278
+ - **Name the category as well as the specifics.** A memory about scaling an
279
+ Azure App Service Plan that never contains the words "Azure" or "App Service
280
+ Plan" cannot be found by them — a real miss, and the reason this rule exists.
281
+ - **Write in the vocabulary of the future question**, not your own summary of
282
+ it.
283
+ - **Say why**, for any rule. A rule without a reason cannot be applied to a
284
+ case you did not anticipate.
285
+
286
+ ### Memory classes
287
+
288
+ - `canonical` — source-of-truth. 0.7 salience floor, never staged. Use for
289
+ decisions, requirements, and anything other agents must be able to recall.
290
+ - `working` — the default. Findings and observations. Salience-gated, so a
291
+ weak write may land in staging rather than active.
292
+ - `ephemeral` — decays fast.
293
+
294
+ ### Content fade
295
+
296
+ Un-recalled memories gradually fade their **content** while keeping their cue
297
+ pathways — concept, tags and embedding stay intact, so they remain findable.
298
+ Recall resets the clock. Practical upshot: **do not purge memories to save
299
+ space, and do not mark everything canonical to fight fade** (canonical changes
300
+ salience gating at write time, not fade). Use `memory_supersede` for stale
301
+ facts.
302
+
303
+ ---
304
+
305
+ ## 7. Gotchas, in the order they bite
306
+
307
+ 1. **Retrieval flags unset.** §3. Silent, and the largest single effect.
308
+ 2. **The decisive identifier sits past character 400.** 97.3% -> 46.8% on the
309
+ public corpus.
310
+ 3. **Terms that exist only in tags.** Invisible to the embedding and (without
311
+ `AWM_RERANK_TAGS=1`) to the reranker.
312
+ 4. **Unpinned agent identity.** Writes vanish into a per-directory UUID space.
313
+ `memory_whoami`.
314
+ 5. **A running MCP connection never reloads code.** Rebuilding AWM on disk has
315
+ zero effect on a session whose connection already started — a child process
316
+ keeps the code it loaded at spawn. Restart the session, or reconnect, then
317
+ confirm with `memory_whoami`. Do not infer the running version from
318
+ `package.json`.
319
+ 6. **Cold start is ~1.5s; warm recall is ~480ms** (measured, k=3, 11k-engram
320
+ store, `npm run profile:recall`) — and on a genuinely fresh install, after a
321
+ one-time **~600 MB** model download, because **nothing is bundled**: no ONNX
322
+ ships in the npm tarball or the MCPB bundle, and all three models fetch lazily
323
+ into `AWM_CACHE_DIR` > `HF_HOME` > `<packageRoot>/data/models`. The
324
+ cross-encoder is **83%** of the warm figure, and its first inference is
325
+ 450-670ms of the cold one — so cold start is model load, not a slow store, and
326
+ it is paid once per process. Never put a cold recall on a latency-critical
327
+ path. `AWM_RERANKER_DTYPE=q8` cuts total latency **26-33%** on three suites
328
+ for **one query in 450** of accuracy (re-measured 2026-10-09 on the shipped
329
+ pipeline: identifier success@1 unchanged at 93.0%, topic 92.2% -> 92.0%;
330
+ correct silence unmoved on all three suites, success@5 unmoved on both
331
+ private suites and +0.5pp on the public corpus, inside its 4pp noise floor) and it shrinks the reranker
332
+ ONNX file it loads from 91 MB to 23 MB. fp32 is still the shipped default:
333
+ q8 is a documented **latency-first operating mode**, not a hidden tuning
334
+ flag, and it CHANGES RANKING OUTCOMES rather than being a free speedup. It is
335
+ read once at module import and the model is then cached, so it needs a
336
+ PROCESS RESTART — setting it against a running AWM does nothing, and an MCP
337
+ session must be restarted or reconnected. To check latency on your own store
338
+ from any install, set `AWM_PROFILE_RECALL=1`; checking ACCURACY needs derived
339
+ ground truth and therefore a repo checkout, because scripts/ and tests/ are
340
+ not in the npm tarball. See docs/deployment.md "Operating modes". `q4` is
341
+ dominated — same accuracy, only -3% latency, 6.1s cold start. See
342
+ docs/recall-latency.md.
343
+ 7. **The embedding model is fixed per store.** Changing `AWM_EMBED_MODEL` or
344
+ `AWM_EMBED_DIMS` against an existing corpus leaves half the store scoring 0
345
+ on the vector channel. AWM warns once per process; re-embed the whole store
346
+ or revert.
347
+ 8. **Invocation depends on the harness, not on AWM.** AWM cannot make an agent
348
+ call it. If the agent is not writing memories, the instructions are the
349
+ problem, not the store.
350
+ 9. **PGlite is single-process.** §4.
351
+ 10. **HTTP agent isolation is advisory.** §4E.
352
+ 11. **The feedback loop has almost no signal yet** — overwhelmingly
353
+ `useful=1`, so do not expect `memory_feedback` to be visibly improving
354
+ ranking today.
355
+ 12. **Consolidation summaries are concatenations**, not synthesis. Do not read
356
+ them as insight.
357
+ 13. **The salience filter is tuned for engineering work.** Other domains will
358
+ need different tuning.
359
+ 14. **Schema migrations are additive, not versioned.** Downgrading is not
360
+ supported.
361
+
362
+ `docs/known-limitations.md` is the maintained version of this list, with
363
+ evidence for each item and a section of things that are **no longer true**, so
364
+ they are not re-reported. Read it before filing anything.
365
+
366
+ ---
367
+
368
+ ## 8. Troubleshooting
369
+
370
+ **Nothing is recalled, or the wrong things are.**
371
+ `awm doctor` → are the three flags from §3 set? Then: does the memory's body
372
+ contain the words you are querying with (§2)? Then: are you in the agent space
373
+ that holds it (`memory_whoami`)? Then: reformulate once or twice with the
374
+ domain's nouns rather than generic English. Three empty reformulations means
375
+ the memory probably is not there — go read the code.
376
+
377
+ **Memories are written but nobody else sees them.** Unpinned agent identity.
378
+ Gotcha 4.
379
+
380
+ **A code change had no effect.** Gotcha 5. Restart the session.
381
+
382
+ **"EMBEDDING DIMENSION MISMATCH" in the logs.** Gotcha 7.
383
+
384
+ **It was working and now the store is empty after an upgrade.** You were on
385
+ <0.15.6 with the store inside the package directory. §4.
386
+
387
+ Deeper: `docs/troubleshooting.md`, `docs/faq.md`, `docs/unknowns.md`.
388
+
389
+ ---
390
+
391
+ ## 9. Reproducing the measurements
392
+
393
+ Two benchmark paths, and the difference matters:
394
+
395
+ - **`npm run bench`** — the published numbers in `docs/benchmarks-current.md`.
396
+ Runs against a frozen snapshot of a real ~30,000-memory store. **That corpus
397
+ is private and is not in the repository**, so this command only works for the
398
+ maintainer. The generators are published; the data is not.
399
+
400
+ - **`npm run bench:public:build` then `npm run bench:public`** — fully
401
+ reproducible by anyone. Builds a synthetic corpus from a seeded generator
402
+ (`tests/realstore-eval/public-corpus.mjs`), derives ground truth with **the
403
+ same hold-out code** as the private path, and scores with the same runner.
404
+
405
+ **Mind the noise floor.** Two full builds differing only in corpus seed gave
406
+ `s@1` 56.3% and 52.0% — the corpus *shape* reproduces to 0.2pp and every
407
+ qualitative result holds, but the absolute moves about 4 points between draws.
408
+ A 2pp move is not a result; confirm on a second seed.
409
+
410
+ **Do not quote public-benchmark numbers as the published ones.** The synthetic
411
+ corpus scores *lower*, because generated prose is more self-similar than real
412
+ prose and same-domain neighbors are harder to separate. Use it for the method
413
+ and for deltas, not for absolutes.
414
+
415
+ Ground truth is derived by **unique-identifier hold-out**: an identifier that
416
+ appears in exactly one active memory — verified through FTS, the real retrieval
417
+ path — gives a mechanically checkable right answer. Adversarial probes are
418
+ included so that **correct silence scores positively**, which is the opposite
419
+ of what LoCoMo does. `docs/benchmarks.md` explains why LoCoMo was retired.
420
+
421
+ ---
422
+
423
+ ## 10. Further reading
424
+
425
+ | For | Read |
426
+ |---|---|
427
+ | Deciding whether to adopt | `docs/for-decision-makers.md` |
428
+ | The mechanism, walked through | `docs/walkthrough.md` |
429
+ | Architecture | `docs/architecture.md` |
430
+ | Cognitive model (ACT-R, Hebbian, fade) | `docs/cognitive-model.md` |
431
+ | Recall quality at scale | `docs/using-awm-at-scale.md` |
432
+ | Weaknesses, with evidence | `docs/known-limitations.md` |
433
+ | What we do not know yet | `docs/unknowns.md` |
434
+ | Benchmark method and history | `docs/benchmarks.md` |
435
+ | Current scores, machine-generated | `docs/benchmarks-current.md` |
436
+ | Agent-facing instructions | `AGENTS.md`, `plugin/skills/awm-memory/SKILL.md` |
437
+ | Setup guides | `docs/quickstart.md`, `docs/claude-code-setup.md`, `docs/team-setup-guide.md`, `docs/plugin.md`, `docs/desktop.md`, `docs/deployment.md` |
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "agent-working-memory",
3
- "version": "0.15.8",
3
+ "version": "0.16.0",
4
+ "mcpName": "io.github.CompleteIdeas/agent-working-memory",
4
5
  "description": "Persistent, local-first memory for AI agents. Selective by design: filters what's worth keeping, links what's related, and says nothing when it doesn't know. SQLite + ONNX, no cloud, no API keys.",
5
6
  "keywords": [
6
7
  "ai-agents",
@@ -53,6 +54,9 @@
53
54
  "test:all": "vitest run && tsx tests/mcp-smoke.ts",
54
55
  "check:release": "node scripts/check-release.mjs",
55
56
  "bench": "node scripts/bench-current.mjs",
57
+ "bench:public:build": "tsx tests/realstore-eval/build-public-snapshot.ts",
58
+ "bench:public": "node scripts/bench-public.mjs",
59
+ "profile:recall": "node scripts/profile-recall.mjs",
56
60
  "test:docker": "bash scripts/docker-release-test.sh",
57
61
  "test:linux": "bash scripts/docker-release-test.sh linux",
58
62
  "test:desktop": "bash scripts/docker-release-test.sh desktop"
@@ -68,6 +72,8 @@
68
72
  ".claude-plugin/",
69
73
  "package.json",
70
74
  "README.md",
75
+ "AGENTS.md",
76
+ "llms.txt",
71
77
  "LICENSE"
72
78
  ],
73
79
  "dependencies": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "awm",
3
3
  "displayName": "Agent Working Memory",
4
- "version": "0.15.8",
4
+ "version": "0.16.0",
5
5
  "description": "Persistent, local-first memory for AI agents. Selective by design: filters what's worth keeping, links what's related, and says nothing when it doesn't know. SQLite + ONNX, no cloud, no API keys.",
6
6
  "author": {
7
7
  "name": "Robert Winter"
package/plugin/README.md CHANGED
@@ -22,7 +22,7 @@ The launcher defaults `AWM_DB_PATH` to `~/.awm/memory.db` — **the same file
22
22
  than quietly starting an empty one. Set `AWM_DB_PATH` yourself to override.
23
23
 
24
24
  It prefers an installed copy of the package (global, or a local `node_modules`) and falls
25
- back to `npx -p agent-working-memory@0.15.8`. The npx path rebuilds
25
+ back to `npx -p agent-working-memory@0.16.0`. The npx path rebuilds
26
26
  `better-sqlite3` on a cold cache, which is slow enough to look like a hang, so a real
27
27
  install is worth having:
28
28
 
@@ -6,7 +6,7 @@
6
6
  //
7
7
  // This is a REAL source file, not a string inside the generator, because it is real code:
8
8
  // it gets syntax-checked, linted and tested like everything else. `npm run build:plugin`
9
- // copies it to plugin/bin/ and substitutes 0.15.8.
9
+ // copies it to plugin/bin/ and substitutes 0.16.0.
10
10
  //
11
11
  // Two jobs a bare `mcpServers` entry in plugin.json cannot do:
12
12
  //
@@ -95,7 +95,7 @@ if (found) {
95
95
  const isWin = process.platform === 'win32';
96
96
  const child = spawn(
97
97
  isWin ? 'npx.cmd' : 'npx',
98
- ['-y', '-p', 'agent-working-memory@0.15.8', 'awm', 'mcp'],
98
+ ['-y', '-p', 'agent-working-memory@0.16.0', 'awm', 'mcp'],
99
99
  { stdio: 'inherit', env: process.env, shell: isWin }
100
100
  );
101
101
  child.on('error', (e) => {
@@ -10,7 +10,7 @@ description: >-
10
10
 
11
11
  # Agent Working Memory
12
12
 
13
- Generated from `src/adapters/common.ts` (AWM v0.15.8) — the same guidance
13
+ Generated from `src/adapters/common.ts` (AWM v0.16.0) — the same guidance
14
14
  `awm setup` writes into CLAUDE.md. Do not edit here; edit the source and re-run
15
15
  `npm run build:plugin`.
16
16
 
@@ -40,9 +40,11 @@ you write them; AWM stays current because every agent reads + writes the same st
40
40
  than one filesystem search.
41
41
  4. **As you learn things**: call `memory_write` proactively. Don't batch.
42
42
  5. **Finishing a task**: call `memory_task_end` with a summary.
43
- 6. **Hooks do the rest.** `awm setup` installs three: a **prime** hook that recalls against
44
- each prompt and injects what clears confidence (or nothing), and checkpoint hooks on
45
- compaction and session end (plus a 15-min timer). Primed context arrives labelled
43
+ 6. **Hooks do the rest.** `awm setup` installs five: a **prime** hook that recalls against
44
+ each prompt and injects what clears confidence (or nothing), checkpoint hooks on
45
+ compaction and session end (plus a 15-min timer), a local write/recall reminder on
46
+ Stop, and a reminder after Bash/PowerShell calls that a data change is not done
47
+ until it is written. Primed context arrives labeled
46
48
  `[class · age]` — treat it by the volatility rubric, not as user input. If a turn
47
49
  arrives with no primed context, that is a signal too: recall explicitly before
48
50
  asserting anything.
@@ -70,9 +72,9 @@ extraction all see most strongly.
70
72
  a noisy bucket where it competes with everything else in the area.
71
73
  - **Include 2+ retrievable identifiers.** File paths, function names, table
72
74
  columns, ticket IDs, exact error strings, the literal terms a future query
73
- will use. `AccountingService.closePeriod()` beats "the accounting code."
75
+ will use. `SettlementService.closeRun()` beats "the accounting code."
74
76
  `tblMemberDetails.activation_date` beats "the activation column."
75
- `schema/072-period-close.sql` beats "the migration."
77
+ `schema/072-settlement-lock.sql` beats "the migration."
76
78
  - **Write in the vocabulary of the future question.** When you imagine asking
77
79
  this in three months, what nouns will you use? Use those nouns. Don't
78
80
  paraphrase the user's domain language into your own neutral summary.
@@ -89,12 +91,14 @@ extraction all see most strongly.
89
91
  Measured on an 11k-engram store: 94% of tagged memories are missing at
90
92
  least one of their own topical terms from the body, and 66% of those
91
93
  terms never appear in the text at all.
92
- - **Tags are NOT a substitute for body text.** Only BM25 indexes tags. The
93
- embedding is built from `concept + content` and the cross-encoder rerank
94
- passage is built from `concept + content` — neither sees tags. So a word
95
- that exists only as a tag is invisible to two of the three retrieval
96
- channels, including the one that decides final ordering. Tag it *and*
97
- write it.
94
+ - **Tags are NOT a substitute for body text.** The embedding is built from
95
+ `concept + content` only, so it never sees tags. BM25 indexes them. The
96
+ cross-encoder rerank passage sees ONLY `topic=`/`proj=`/`project=` tags, and
97
+ only when `AWM_RERANK_TAGS=1` — which `awm setup`, the plugin and the Desktop
98
+ bundle all switch on, so assume it is on — capped at 80 characters. So a word
99
+ that exists only as a tag is invisible to the embedding entirely, and
100
+ invisible to the ranker unless it is one of those three prefixes inside that
101
+ budget. Tag it *and* write it.
98
102
  - **Reserve canonical for stable invariants.** Decisions, requirements,
99
103
  hard facts, cross-agent shared context. Working class (default) is correct
100
104
  for findings, observations, and progress notes. The canonical floor is
@@ -111,6 +115,10 @@ Every `memory_write` should pass these structured fields. AWM stores each as a
111
115
  prefix-tag like `proj=`, `topic=`, `intent=`, etc. and uses them for BM25
112
116
  and entity-bridge boosts at recall time.
113
117
 
118
+ (“Required” below means *required for good recall* — the MCP schema accepts every
119
+ one of these as optional, so a write without them succeeds and simply retrieves
120
+ worse.)
121
+
114
122
  | Field | Required? | Format | Example |
115
123
  |---|---|---|---|
116
124
  | `project` | **YES** | one short word matching the current project | `"EquiHub"`, `"AWM"`, `"USEA-Agent"` |
@@ -120,12 +128,12 @@ and entity-bridge boosts at recall time.
120
128
  | `source` | recommended | `code-reading` / `debugging` / `discussion` / `research` / `testing` / `observation` | `"testing"` |
121
129
  | `memory_class` | when stable | `canonical` (source-of-truth, 0.7 floor, never staged) / `working` (default) / `ephemeral` | `"canonical"` |
122
130
  | `session_id` | recommended | current conversation ID for entity-bridge boost | autogenerated |
123
- | `tags` | when applicable | extra prefix-tags for IDs and dates | `["ticket=18360", "date=2026-05-11"]` |
131
+ | `tags` | when applicable | extra prefix-tags for IDs and dates | `["ticket=10001", "date=2026-05-11"]` |
124
132
 
125
133
  **Always add identifier tags when present in the content:**
126
134
  - `ticket=<id>` for Freshdesk tickets
127
135
  - `member=<id>` for member IDs
128
- - `horse=<id>` for horse_member_id
136
+ - `asset=<id>` for a domain record id
129
137
  - `usef=<id>` for USEF lookups
130
138
  - `date=YYYY-MM-DD` for temporal anchoring (ISO format)
131
139
  - `person=<Name>` for stakeholder quotes / decisions
@@ -134,7 +142,7 @@ and entity-bridge boosts at recall time.
134
142
  ### Entity index — exact-match recall for named things (default off)
135
143
  Structured identifier tags (`ticket=`, `person=`, `horse=`, `member=`, bare 4+ digit
136
144
  ids, etc.) feed a dedicated entity inverted index, separate from BM25/embedding scoring.
137
- A query naming an entity ("ticket 19252", "Kaleigh Collett") can reach the memory through
145
+ A query naming an entity ("ticket 10003", "Dana Whitfield") can reach the memory through
138
146
  this index even when the wording doesn't lexically match — it's a deterministic exact
139
147
  lookup, immune to vocabulary mismatch. Keep identifier tags exact and consistent for
140
148
  this reason, not just for the BM25 boost described above.
@@ -334,14 +342,14 @@ memory_write(
334
342
  ```
335
343
  memory_write(
336
344
  concept="EquiHub period-close BLOCKED check missing server-side",
337
- content="apps/web/app/(accounting)/accounting/period-close/page.tsx had client-only BLOCKED enforcement. Fixed by adding server-side check in AccountingService.closePeriod() per schema/072-period-close.sql. Without server-side check a malicious request could bypass via direct API call.",
345
+ content="apps/web/app/(billing)/billing/settlement-lock/page.tsx had client-only BLOCKED enforcement. Fixed by adding server-side check in SettlementService.closeRun() per schema/072-settlement-lock.sql. Without server-side check a malicious request could bypass via direct API call.",
338
346
  project="EquiHub",
339
347
  topic="accounting",
340
348
  intent="finding",
341
349
  confidence_level="verified",
342
350
  source="debugging",
343
351
  memory_class="canonical",
344
- tags=["ticket=18360", "person=Robert", "date=2026-05-11", "topic=period-close", "topic=security"]
352
+ tags=["ticket=10001", "person=Robert", "date=2026-05-11", "topic=period-close", "topic=security"]
345
353
  )
346
354
  ```
347
355
 
@@ -389,7 +397,7 @@ For the comparison table (recall quality parity, BM25 vs `ts_rank_cd`,
389
397
  multi-process guarantees), see `docs/pglite-feature-parity.md`.
390
398
 
391
399
  ### Diagnostics / escape hatches (env vars, only if you know why)
392
- Each optimisation is gated by an env var so it can be disabled for A/B testing if a
400
+ Each optimization is gated by an env var so it can be disabled for A/B testing if a
393
401
  regression appears in your workload. `memory_whoami` prints the active fingerprint.
394
402
 
395
403
  Retrieval (set ON by `awm setup` since 0.14.6 — the measured-good configuration):
@@ -411,8 +419,13 @@ Recall pipeline:
411
419
  no effect on the real-store benchmark, 0.14.5).
412
420
  - `AWM_DISABLE_SLIM_CACHE=1` — disables the in-memory slim cache.
413
421
  Reverts to per-recall SQL fetch + Buffer→Float32Array conversion.
414
- - `AWM_DISABLE_RERANK_SKIP=1` — disables the cross-encoder skip on
415
- clear-winner queries. Forces every recall through the reranker.
422
+ - `AWM_FEEDBACK_BM25=1` — re-enables Rocchio pseudo-relevance feedback
423
+ (phase 3.5), a third BM25 pass over the query plus five terms harvested from
424
+ the top 3 results. Default OFF since 2026-10-09: measured at 6.7–7.0% of every
425
+ warm recall for no measurable accuracy — 3 gold ranks moved in 950 queries
426
+ across three suites, two of them the wrong way. On the public corpus — the only
427
+ suite with recall headroom left — it is inert per query, not just in aggregate:
428
+ 0 of 200 queries differ in rank, top-1 or score.
416
429
  - `AWM_DISABLE_EXPANSION_CACHE=1` — disables the query expansion skip
417
430
  heuristic + LRU cache. Forces every recall through flan-t5-small.
418
431