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.
- package/AGENTS.md +36 -0
- package/README.md +24 -12
- package/dist/adapters/common.d.ts +1 -1
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +33 -20
- package/dist/adapters/common.js.map +1 -1
- package/dist/core/embeddings.d.ts +1 -1
- package/dist/core/embeddings.js +1 -1
- package/dist/core/entity-extract.js +1 -1
- package/dist/core/query-expander.d.ts +4 -2
- package/dist/core/query-expander.d.ts.map +1 -1
- package/dist/core/query-expander.js +4 -2
- package/dist/core/query-expander.js.map +1 -1
- package/dist/core/recall-config.d.ts +1 -1
- package/dist/core/recall-config.d.ts.map +1 -1
- package/dist/core/recall-config.js +6 -1
- package/dist/core/recall-config.js.map +1 -1
- package/dist/core/recall-telemetry.d.ts +15 -0
- package/dist/core/recall-telemetry.d.ts.map +1 -0
- package/dist/core/recall-telemetry.js +108 -0
- package/dist/core/recall-telemetry.js.map +1 -0
- package/dist/core/rerank-window.d.ts +5 -4
- package/dist/core/rerank-window.d.ts.map +1 -1
- package/dist/core/rerank-window.js +5 -4
- package/dist/core/rerank-window.js.map +1 -1
- package/dist/core/reranker.d.ts +2 -1
- package/dist/core/reranker.d.ts.map +1 -1
- package/dist/core/reranker.js +23 -3
- package/dist/core/reranker.js.map +1 -1
- package/dist/core/salience.js +1 -1
- package/dist/core/salience.js.map +1 -1
- package/dist/core/token-budget.d.ts +2 -2
- package/dist/core/token-budget.js +2 -2
- package/dist/core/whoami.d.ts +1 -1
- package/dist/core/write-pipeline.d.ts.map +1 -1
- package/dist/core/write-pipeline.js +19 -0
- package/dist/core/write-pipeline.js.map +1 -1
- package/dist/engine/activation.d.ts +30 -13
- package/dist/engine/activation.d.ts.map +1 -1
- package/dist/engine/activation.js +159 -50
- package/dist/engine/activation.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +98 -98
- package/dist/mcp.js.map +1 -1
- package/dist/storage/pglite.d.ts +28 -0
- package/dist/storage/pglite.d.ts.map +1 -1
- package/dist/storage/pglite.js +55 -0
- package/dist/storage/pglite.js.map +1 -1
- package/dist/storage/postgres.d.ts +28 -0
- package/dist/storage/postgres.d.ts.map +1 -1
- package/dist/storage/postgres.js +55 -0
- package/dist/storage/postgres.js.map +1 -1
- package/dist/storage/sqlite.d.ts +28 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +56 -0
- package/dist/storage/sqlite.js.map +1 -1
- package/llms.txt +437 -0
- package/package.json +7 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/README.md +1 -1
- package/plugin/bin/awm-mcp-launcher.cjs +2 -2
- package/plugin/skills/awm-memory/SKILL.md +33 -20
- package/src/adapters/common.ts +33 -20
- package/src/core/embeddings.ts +1 -1
- package/src/core/entity-extract.ts +1 -1
- package/src/core/query-expander.ts +4 -2
- package/src/core/recall-config.ts +6 -1
- package/src/core/recall-telemetry.ts +131 -0
- package/src/core/rerank-window.ts +159 -158
- package/src/core/reranker.ts +24 -3
- package/src/core/salience.ts +1 -1
- package/src/core/token-budget.ts +160 -160
- package/src/core/whoami.ts +120 -120
- package/src/core/write-pipeline.ts +36 -18
- package/src/engine/activation.ts +159 -51
- package/src/index.ts +1 -1
- package/src/mcp.ts +1475 -1475
- package/src/storage/pglite.ts +49 -0
- package/src/storage/postgres.ts +49 -0
- 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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
44
|
-
each prompt and injects what clears confidence (or nothing),
|
|
45
|
-
compaction and session end (plus a 15-min timer)
|
|
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. `
|
|
75
|
+
will use. `SettlementService.closeRun()` beats "the accounting code."
|
|
74
76
|
`tblMemberDetails.activation_date` beats "the activation column."
|
|
75
|
-
`schema/072-
|
|
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.**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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=
|
|
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
|
-
- `
|
|
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
|
|
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/(
|
|
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=
|
|
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
|
|
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
|
-
- `
|
|
415
|
-
|
|
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
|
|