agent-working-memory 0.14.6 → 0.15.4

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.
@@ -0,0 +1,445 @@
1
+ ---
2
+ name: awm-memory
3
+ description: >-
4
+ How to use Agent Working Memory (AWM) — when to call memory_recall before stating a fact or
5
+ searching the filesystem, when to memory_write, how to tag a write so it can be found again,
6
+ and how to read an abstained recall. Load whenever AWM's memory tools are available and you
7
+ are about to state a fact, make a decision, finish a task, or search for something you may
8
+ already know.
9
+ ---
10
+
11
+ # Agent Working Memory
12
+
13
+ Generated from `src/adapters/common.ts` (AWM v0.15.4) — the same guidance
14
+ `awm setup` writes into CLAUDE.md. Do not edit here; edit the source and re-run
15
+ `npm run build:plugin`.
16
+
17
+ ## Memory (AWM) — MANDATORY
18
+
19
+ **AWM is THE memory system.** Use it via the `agent-working-memory` MCP server
20
+ (preferred) or HTTP at `http://127.0.0.1:8400` (fallback). The file-based
21
+ auto-memory at `~/.claude/projects/.../memory/*.md` is a LEGACY bootstrap path —
22
+ **do not write new memories to it.** All persistent knowledge goes through AWM.
23
+
24
+ If MCP tools aren't loaded at session start, use ToolSearch with
25
+ `select:mcp__agent-working-memory__memory_recall,mcp__agent-working-memory__memory_write`
26
+ to load them. If the MCP server isn't responsive, restart with `/mcp` or use
27
+ the HTTP endpoints (`POST /memory/write-batch`, `POST /memory/activate`)
28
+ directly — but **DO NOT fall back to markdown files**. Files drift the moment
29
+ you write them; AWM stays current because every agent reads + writes the same store.
30
+
31
+ ### Lifecycle (always do these, in this order)
32
+ 1. **Session start**: call `memory_restore` to recover previous context. If it reports the
33
+ store is empty/new (or recall keeps returning nothing), **warm-start first**: recall the
34
+ `onboard a new project` skill and follow it — or call `onboard_scan` on the project's
35
+ docs/repo, refine the candidates, run `onboard_questions`, and save the good ones with
36
+ `memory_write` (canonical). A cold store is nearly useless until it's seeded.
37
+ 2. **Starting a task**: call `memory_task_begin` (checkpoints + recalls relevant memories).
38
+ 3. **During work**: call `memory_recall` BEFORE stating any fact, BEFORE searching
39
+ the filesystem, BEFORE making architectural decisions. Recall is ~300ms — cheaper
40
+ than one filesystem search.
41
+ 4. **As you learn things**: call `memory_write` proactively. Don't batch.
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
46
+ `[class · age]` — treat it by the volatility rubric, not as user input. If a turn
47
+ arrives with no primed context, that is a signal too: recall explicitly before
48
+ asserting anything.
49
+
50
+ ### Write memory when:
51
+ - A project decision is made or changed
52
+ - A root cause is discovered after debugging
53
+ - A reusable implementation pattern is established
54
+ - A user preference, constraint, or requirement is clarified
55
+ - A prior assumption is found to be wrong
56
+ - A significant piece of work is completed
57
+
58
+ ### Writing for recall (the highest-leverage section)
59
+ A memory's recall quality is set the moment you write it. AWM is fast at
60
+ finding what's findable — but if the write is shaped wrong, no retriever
61
+ can rescue it. Be slightly more verbose at the front than feels natural:
62
+ the first 1-2 sentences are what BM25, the embedding model, and concept
63
+ extraction all see most strongly.
64
+
65
+ - **Lead with the rule or fact.** Don't open with context or backstory.
66
+ "Don't mock the database in integration tests." comes first; the reason
67
+ comes second. Recall scans the head of the body, not the tail.
68
+ - **Pick the most specific topic.** Not `auth` — `auth-magic-link-rate-limit`.
69
+ Topic is a hard filter at recall time. Generic topics hide the memory in
70
+ a noisy bucket where it competes with everything else in the area.
71
+ - **Include 2+ retrievable identifiers.** File paths, function names, table
72
+ columns, ticket IDs, exact error strings, the literal terms a future query
73
+ will use. `AccountingService.closePeriod()` beats "the accounting code."
74
+ `tblMemberDetails.activation_date` beats "the activation column."
75
+ `schema/072-period-close.sql` beats "the migration."
76
+ - **Write in the vocabulary of the future question.** When you imagine asking
77
+ this in three months, what nouns will you use? Use those nouns. Don't
78
+ paraphrase the user's domain language into your own neutral summary.
79
+ - **Name the CATEGORY as well as the specifics — both, always.** Specifics
80
+ make a memory precise; category words make it *reachable*. A memory that
81
+ says "private plan memory peaked 88%, scale P1v3 -> P2v3" never says
82
+ "Azure" or "App Service Plan", so a question asked in those words cannot
83
+ find it — measured on a real store, that memory was not in the top 40
84
+ candidates for "azure app service plan capacity increase". Write the
85
+ system / product / domain nouns (Azure App Service Plan, Freshdesk ticket,
86
+ MySQL connection pool, ShowConnect scoring) NEXT TO the identifiers.
87
+ Note this cuts against "pick the most specific topic" above: that rule is
88
+ about the `topic` TAG, not a licence to omit the category from the body.
89
+ Measured on an 11k-engram store: 94% of tagged memories are missing at
90
+ least one of their own topical terms from the body, and 66% of those
91
+ 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.
98
+ - **Reserve canonical for stable invariants.** Decisions, requirements,
99
+ hard facts, cross-agent shared context. Working class (default) is correct
100
+ for findings, observations, and progress notes. The canonical floor is
101
+ 0.7 salience — overusing it pollutes the canonical layer and the floor
102
+ loses meaning.
103
+ - **Include the why for feedback memories.** A rule without a reason can't
104
+ be applied to edge cases. "Don't mock the database" is brittle. "Don't
105
+ mock the database — last quarter mocked tests masked a broken migration"
106
+ is portable to new situations.
107
+
108
+ ### Tagging rules (REQUIRED — AWM's prefix-tag retrieval boost depends on these)
109
+
110
+ Every `memory_write` should pass these structured fields. AWM stores each as a
111
+ prefix-tag like `proj=`, `topic=`, `intent=`, etc. and uses them for BM25
112
+ and entity-bridge boosts at recall time.
113
+
114
+ | Field | Required? | Format | Example |
115
+ |---|---|---|---|
116
+ | `project` | **YES** | one short word matching the current project | `"EquiHub"`, `"AWM"`, `"USEA-Agent"` |
117
+ | `topic` | **YES** | one or more lowercase area words | `"database-migration"`, `"benchmarks"` |
118
+ | `intent` | **YES** | one of: `decision` / `finding` / `todo` / `question` / `context` | `"finding"` |
119
+ | `confidence_level` | **YES** | `verified` (tested) / `observed` (read in code) / `assumed` (reasoning) | `"verified"` |
120
+ | `source` | recommended | `code-reading` / `debugging` / `discussion` / `research` / `testing` / `observation` | `"testing"` |
121
+ | `memory_class` | when stable | `canonical` (source-of-truth, 0.7 floor, never staged) / `working` (default) / `ephemeral` | `"canonical"` |
122
+ | `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"]` |
124
+
125
+ **Always add identifier tags when present in the content:**
126
+ - `ticket=<id>` for Freshdesk tickets
127
+ - `member=<id>` for member IDs
128
+ - `horse=<id>` for horse_member_id
129
+ - `usef=<id>` for USEF lookups
130
+ - `date=YYYY-MM-DD` for temporal anchoring (ISO format)
131
+ - `person=<Name>` for stakeholder quotes / decisions
132
+ - `version=<X.Y.Z>` for release-specific findings
133
+
134
+ ### Entity index — exact-match recall for named things (default off)
135
+ Structured identifier tags (`ticket=`, `person=`, `horse=`, `member=`, bare 4+ digit
136
+ 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
138
+ this index even when the wording doesn't lexically match — it's a deterministic exact
139
+ lookup, immune to vocabulary mismatch. Keep identifier tags exact and consistent for
140
+ this reason, not just for the BM25 boost described above.
141
+
142
+ Off by default; opt in with `AWM_ENTITY_INDEX_FETCH=1` (bounded by
143
+ `AWM_ENTITY_INDEX_CAP`, default 12). Matched entities get no score boost — they're
144
+ guaranteed a reranker audition instead, so the cross-encoder alone decides whether they
145
+ surface. Worth trialing on identifier-heavy workloads (ticket/event numbers, named
146
+ people/things you refer to by name often); not yet the default pending evaluation.
147
+
148
+ ### Temporal validity — memories that expire or start in the future
149
+ `memory_write` accepts `valid_from` / `valid_to` (ISO dates). Use `valid_to` on
150
+ **operational** facts with a real shelf life — a deploy state, "waiting on X's reply",
151
+ a ticket status — so the memory expires instead of relying on you to remember it's
152
+ stale. Recall renders `[valid until …]` on results carrying this field. Use
153
+ `valid_from` for a fact that becomes true on a known future date (a policy change, a
154
+ season that hasn't started yet). Don't set either for durable facts — most memories
155
+ don't need them.
156
+
157
+ ### Memory classes (controls how strictly the salience filter gates the write)
158
+ - `memory_class: canonical` — source-of-truth memories. Floor 0.7 salience, never staged.
159
+ Use for: user-stated decisions, project requirements, verified architectural facts,
160
+ cross-agent shared context. **In a hive (multi-agent) setup, always use `canonical`
161
+ for writes that other agents must be able to recall** — the default `working` class
162
+ may get filtered.
163
+ - `memory_class: working` (default) — observations and findings. Salience-gated.
164
+ - `memory_class: ephemeral` — short-lived context that should decay quickly.
165
+
166
+ ### Salience auto-promotion (defense in depth)
167
+ The salience filter automatically promotes certain content patterns even if you forget
168
+ to set `memory_class` explicitly:
169
+ - **User feedback** — content starting with "Robert said…", "Katherine directed…",
170
+ "Nancy decided…" etc. auto-promotes to canonical. So quoting the user verbatim
171
+ always preserves the decision.
172
+ - **Verified operational records** — content with an action verb (Submitted, Finalized,
173
+ Completed, Reconciled, Triaged, Posted, Resolved, Stamped, Pushed, Deployed, Migrated,
174
+ Imported, Exported, Backfilled) plus 2+ concrete identifiers (ISO date `YYYY-MM-DD`,
175
+ or contextual numeric IDs like "event 18969", "ticket #18330", "USEF 341980") gets
176
+ a 0.45 salience floor. So batch summaries with real IDs survive even when topic
177
+ terms repeat.
178
+
179
+ If neither pattern applies and you want a memory to definitely survive, set
180
+ `memory_class: canonical` explicitly. Don't rely on auto-promotion for important writes.
181
+
182
+ ### Recall memory when:
183
+ - **BEFORE stating ANY fact about how a system works** — recall first; if AWM doesn't
184
+ have it, read the code. Never guess and present it as fact.
185
+ - **BEFORE searching the filesystem** — recall first; AWM is faster and has cross-session
186
+ knowledge that file search doesn't.
187
+ - Starting work on a new task or subsystem
188
+ - Re-entering code you haven't touched recently
189
+ - After a failed attempt — check if there's prior knowledge
190
+ - Before refactoring or making architectural changes
191
+ - When a topic comes up that you might have prior context on
192
+
193
+ Recall is fast (~300ms typical). Use it freely.
194
+
195
+ ### Chain recalls — one hop per call
196
+
197
+ AWM returns what matches your query. It does **not** walk from that answer to the next
198
+ fact, and it is not meant to: it is active memory, holding what you need right now.
199
+ Following a chain to the next thing is **your** job — recall, read, recall again, each
200
+ result becoming the cue for the next query.
201
+
202
+ So when a question refers to something **by its role instead of its name**, you have a
203
+ chain, not a query:
204
+
205
+ > *"What is the codename of the project owned by my scheduler?"*
206
+
207
+ That is three lookups, not one:
208
+
209
+ 1. recall `who is my scheduler` → a name
210
+ 2. recall `what project does <that name> own` → a project
211
+ 3. recall `<that project> codename` → the answer
212
+
213
+ **The failure mode is answering from the first recall.** A single query blending all
214
+ three terms returns the most *salient* related memory, not the correct one — measured,
215
+ that returns the main project's codename with complete confidence, and it is wrong.
216
+ Each hop on its own is an ordinary, reliable recall; the chain only breaks when you stop
217
+ asking.
218
+
219
+ Tells that you are looking at a chain: a possessive or relative clause naming an entity
220
+ by role rather than identity (`my scheduler`, `the owner of X`, `whoever signed
221
+ off on Y`), or a rule that operates on an attribute you have not looked up yet (`the
222
+ release tag of the project we discussed` needs that project's codename first). If
223
+ resolving the question requires a fact you would have to *derive*, recall the fact
224
+ instead.
225
+
226
+ ### Recall strategy (when one query isn't enough)
227
+ AWM's adaptive retrieval handles most query variations natively — synonym
228
+ expansion, multi-channel scoring, embedding + BM25 + reranker agreement.
229
+ A single recall is usually enough.
230
+
231
+ When it isn't:
232
+ - **If the first recall returns nothing or returns the wrong things, reformulate.**
233
+ Try a second query with different phrasing — synonyms, more specific nouns,
234
+ the exact identifier from the code rather than the conceptual name. Two or
235
+ three recalls cost less than one filesystem search.
236
+ - **Use the words a domain expert would use, not generic English.** "Period
237
+ close lock" not "accounting feature"; "magic link rate limit" not "auth issue."
238
+ - **For broad exploration, pass `mode: "exploratory"`** — wider candidate
239
+ pool, lower precision floor. For specific lookups, leave mode unset (auto).
240
+ - **Don't ensemble more than 3 reformulations.** If three different phrasings
241
+ return nothing, the memory probably isn't there — read the code instead of
242
+ burning more recalls.
243
+
244
+ ### Recall tuning — opt-in parameters
245
+ Default `memory_recall` is tuned for the common case. Four opt-in parameters change
246
+ the cost/quality tradeoff. Use them when the default doesn't match what you need.
247
+
248
+ - **`granularity: 'compact'`** — every result carries a 200-char `summary`
249
+ field with a query-aware snippet (the densest window of query terms in the
250
+ content). Use this when you expect to scan 5+ results to find one — saves
251
+ ~70% of recall output tokens. The full content stays available in
252
+ `engram.content` if you want to drill into a specific result.
253
+ - **`granularity: 'auto'`** — confidence-adaptive. If the top result is a clear
254
+ winner, it gets a longer summary while the rest are compact. If confidence
255
+ is uniform across results, everything is compact. Use when you don't know
256
+ in advance whether one result will dominate.
257
+ - **`require_confidence`** — abstention. The default (0.05) is already a light
258
+ gate: when the top results don't stand out from the rest, recall returns
259
+ nothing and the reply says **`RECALL ABSTAINED`** with the count it withheld.
260
+ An empty result *without* that line is genuine absence. The gate is on the
261
+ SHAPE of the score distribution, not relevance — so raising it silences
262
+ specific questions with one clear winner first (measured: 0.25 abstained on
263
+ two identifier queries scoring 0.26–0.38 while passing a vague one at 0.92).
264
+ Leave it alone for ordinary recall; `0.10` is the ceiling that still helps for
265
+ push-style use where nobody asked. When abstention fires, reformulate once
266
+ with the exact identifier, then read the code — don't retry with a lower
267
+ threshold to force an answer.
268
+ - **`workspace: "<name>"`** — hive-mode recall across all agents in the
269
+ workspace. Use when other agents may have written canonical knowledge you
270
+ need. Default is agent-scoped (your own memories only). Can also be set
271
+ globally via the `AWM_WORKSPACE` env var. Memories live in the agent pool
272
+ they were written to; if a session lands in an unexpected pool, `memory_whoami`
273
+ says which one before you conclude the memory is missing.
274
+
275
+ ### Keep memory fresh
276
+ - After recalling a memory, if you observe the real state is different → call
277
+ `memory_supersede` immediately with the corrected version.
278
+ - After using a recalled memory: call `memory_feedback` (useful/not-useful) **with the
279
+ `recall_id`** printed at the end of the recall output (`[recall_id: …]`). That id
280
+ joins the feedback to the recall that produced it, which is what lets AWM learn
281
+ which recalls were worth their tokens; feedback without it is a bare vote.
282
+ - If you discover a memory is factually wrong: `memory_retract` to remove it.
283
+ - **If you bypass AWM (file-memory, in-context notes, "I'll just remember"), the memory
284
+ drifts out of date. The system relies on you to keep it current. This is the #1
285
+ failure mode.**
286
+
287
+ ### Cognition recipes — YOU do the thinking, AWM keeps the result
288
+ AWM contains no LLM. When memory needs real thinking — distilling a repeatable
289
+ procedure, reflecting on a failure — AWM hands YOU a versioned recipe (prompt +
290
+ strict output shape) and you run it as a SEPARATE focused pass, then write the
291
+ result back as an ordinary memory with provenance.
292
+
293
+ - `memory_task_end` responses include the recipe invitations. Honor the gates:
294
+ skill-derivation only after a genuinely procedural task (3+ tool calls or a
295
+ delegated sub-task); friction-lesson only after a failure/retry/wrong assumption.
296
+ - Run each recipe as its own focused pass — do NOT bundle it with other
297
+ reasoning; bundled passes reliably drop the output.
298
+ - Write back exactly per the recipe's contract: `origin_class: 'recipe'` +
299
+ `recipe_id` (e.g. `skill-derivation@1`), concept prefixed `skill: ` or
300
+ `lesson: `. AWM validates the shape and rejects malformed or unknown-recipe
301
+ writes with the contract echoed back — fix and retry, don't drop the insight.
302
+ - Re-deriving the same skill name reinforces the existing memory instead of
303
+ duplicating it, so don't fear writing a skill you may have written before.
304
+
305
+ ### Content fade — write-and-forget is safe
306
+ Un-recalled engrams gradually fade their content while preserving cue pathways
307
+ (concept + tags + embedding stay intact). This is Paper 1 — storage
308
+ degradation. Practical implications:
309
+
310
+ - **Don't manually purge memories** to "save space." The system already
311
+ compresses unused content. Old memories stay findable via cue match even
312
+ when their body has decayed.
313
+ - **Don't over-pin with `memory_class: canonical`** to fight fade. Canonical
314
+ only changes salience gating at write time, not fade behavior. Fade
315
+ affects un-recalled engrams of any class.
316
+ - **Recall keeps content alive.** Every recall touches the engram and resets
317
+ its fade clock. Frequently-recalled memories stay full-fidelity automatically.
318
+ - **Supersede is the right tool for stale facts.** When you observe a memory
319
+ is outdated, call `memory_supersede` — the new version inherits the old
320
+ one's coherent associations (counter-narrative replacement) so cue
321
+ pathways carry forward to the replacement.
322
+
323
+ ### Example — good vs bad memory_write
324
+
325
+ **BAD** (no prefix tags, vague concept, can't be recalled by future queries):
326
+ ```
327
+ memory_write(
328
+ concept="found a bug",
329
+ content="The thing I was looking at was broken so I fixed it."
330
+ )
331
+ ```
332
+
333
+ **GOOD** (rich identifiers, structured metadata, prefix tags):
334
+ ```
335
+ memory_write(
336
+ 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.",
338
+ project="EquiHub",
339
+ topic="accounting",
340
+ intent="finding",
341
+ confidence_level="verified",
342
+ source="debugging",
343
+ memory_class="canonical",
344
+ tags=["ticket=18360", "person=Robert", "date=2026-05-11", "topic=period-close", "topic=security"]
345
+ )
346
+ ```
347
+
348
+ ### Also:
349
+ - To track work items: memory_task_add, memory_task_update, memory_task_list, memory_task_next
350
+ - `memory_whoami` (MCP tool) / `GET /whoami` — identify the instance you're actually
351
+ talking to: agent id, workspace, mode, backend, store path, code provenance, sibling
352
+ agent spaces sharing the store. Call this FIRST whenever you're unsure which store,
353
+ which agent identity, or which running code you're dealing with — before reasoning
354
+ about AWM's own state from a stale memory or an assumed port number.
355
+ - AWM is shared across all agents in real time. When any agent writes or supersedes a
356
+ memory, every other agent can recall it immediately — but only within the same
357
+ workspace and agent scope.
358
+
359
+ ### Output compression (token efficiency, output-only)
360
+ When a tool returns a LARGE STRUCTURED result you need to keep in context — a JSON
361
+ array of records, query rows, a log dump, an API response — pass it through
362
+ `compress_output` first. It re-encodes the data as TOON (a compact, lossless,
363
+ schema-aware tabular form of JSON), cutting ~50-65% of the tokens at no
364
+ comprehension cost. This is output-only: it never changes the data or your memories.
365
+ - Use it on big STRUCTURED outputs, not on prose. Prose is returned unchanged —
366
+ for trimming memory prose, use recall `granularity: 'compact'` instead.
367
+ - It returns a `ref`; call `retrieve_original(ref)` if you later need the exact
368
+ verbatim source (e.g. to hand it to another tool unchanged).
369
+ - Don't bother for small outputs — it only compresses when the saving is worthwhile
370
+ and falls back to plain JSON if TOON wouldn't reproduce the data exactly.
371
+
372
+ ### Backend (SQLite vs PGlite)
373
+ AWM ships two storage backends. The installer picks SQLite by default; both
374
+ are functionally equivalent for cognitive workloads, but differ in operational
375
+ guarantees:
376
+
377
+ - **SQLite** (default) — embedded, **multi-process safe** via WAL mode. Best
378
+ for single-machine setups and MCP scenarios where multiple Claude Code
379
+ sessions may open the same database concurrently.
380
+ - **PGlite** — embedded Postgres (WASM) with pgvector. **Single-process only**
381
+ — two MCP processes against the same `memory-pglite/` directory will
382
+ abort the second. Pick via `AWM_STORE_BACKEND=pglite` and
383
+ `AWM_DB_PATH=path/to/memory-pglite`.
384
+ - **Auto-detect** — if `AWM_DB_PATH` points to a directory that already
385
+ exists, AWM detects PGlite; a file → SQLite. No explicit
386
+ `AWM_STORE_BACKEND` needed when an existing DB is present.
387
+
388
+ For the comparison table (recall quality parity, BM25 vs `ts_rank_cd`,
389
+ multi-process guarantees), see `docs/pglite-feature-parity.md`.
390
+
391
+ ### 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
393
+ regression appears in your workload. `memory_whoami` prints the active fingerprint.
394
+
395
+ Retrieval (set ON by `awm setup` since 0.14.6 — the measured-good configuration):
396
+ - `AWM_RERANK2=1` — second-stage rerank by the cross-encoder's own score.
397
+ - `AWM_RERANK_WINDOW=query` — 400-char rerank window on the densest query-term
398
+ region instead of the prefix (25% → 87.5% on long memories).
399
+ - `AWM_RERANK_TAGS=1` — tags fed into the rerank passage (+7.4pp s@1).
400
+
401
+ Hook sidecar (0.14.2):
402
+ - `AWM_HOOK_PORT=8401` / `AWM_HOOK_PORT_RANGE=10` — each MCP process binds the
403
+ first free port in the range, so several sessions can run at once. The shipped
404
+ hooks find their own session's sidecar by probing the range and matching
405
+ `agentId` on `/health`; `memory_whoami` reports the port actually bound.
406
+
407
+ Recall pipeline:
408
+ - `AWM_DISABLE_POOL_FILTER=1` — disables the candidate pool reduction
409
+ pre-filter in recall. Reverts to scoring all active candidates.
410
+ - `AWM_ENTITY_INDEX_FETCH=1` — see "Entity index" above (default off; measured
411
+ no effect on the real-store benchmark, 0.14.5).
412
+ - `AWM_DISABLE_SLIM_CACHE=1` — disables the in-memory slim cache.
413
+ 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.
416
+ - `AWM_DISABLE_EXPANSION_CACHE=1` — disables the query expansion skip
417
+ heuristic + LRU cache. Forces every recall through flan-t5-small.
418
+
419
+ Write pipeline + lifecycle:
420
+ - `AWM_SLOW_WRITE_MS=250` — any write slower than this logs one stderr
421
+ line with a phase-time breakdown (embed/novelty/persist, event-loop lag,
422
+ embed-model cold-load ms). `0` disables. Useful for diagnosing why a session's
423
+ first write/recall feels slow.
424
+ - `AWM_REINFORCE_MAX_CONTENT_LEN=1500` — max chars an engram's content
425
+ can grow to via merge-on-reinforce (drop-oldest on overflow). Higher =
426
+ preserves more reinforced detail; lower = leaner recall output.
427
+ - `AWM_REINFORCE_MERGE_CONTENT=0` — disable content merge on reinforce.
428
+ Reverts to pre-0.8.5 behavior (discard new content, only bump confidence).
429
+ - `AWM_NOVELTY_EMBED=0` — disable the cosine channel in novelty
430
+ computation. BM25-only fallback. Reverts to pre-0.8.5 novelty.
431
+ - `AWM_GRANULARITY_COMPACT_LEN=200` — char budget for query-aware snippet
432
+ in `granularity: 'compact'` mode.
433
+ - `AWM_GRANULARITY_FULL_LEN=1000` — char budget for the top result in
434
+ `granularity: 'auto'` mode when there's a clear winner.
435
+
436
+ PGlite backend:
437
+ - `AWM_PGLITE_BM25_M=1` — multiplier on PGlite `ts_rank_cd` to calibrate
438
+ against SQLite FTS5 BM25 distribution. M=1 (default) is passthrough;
439
+ higher M boosts PGlite scores at the cost of recall-ranking precision
440
+ (see CHANGELOG 0.8.5 follow-up).
441
+ - `AWM_IVFFLAT_PROBES=5` — pgvector ivfflat probes per query. Higher =
442
+ more accurate, slower.
443
+
444
+ In production, leave these all unset. Use only when diagnosing a suspected
445
+ recall-quality regression.
@@ -23,7 +23,7 @@ import { request as httpRequest } from 'node:http';
23
23
  * script INSERT broke a production upload six days later, and no recall could
24
24
  * surface a cause that was never written.
25
25
  */
26
- const DB_MUTATION_HOOK_SCRIPT = `#!/usr/bin/env node
26
+ export const DB_MUTATION_HOOK_SCRIPT = `#!/usr/bin/env node
27
27
  // AWM DB-mutation reminder hook (PostToolUse on Bash|PowerShell). Installed by awm setup.
28
28
  let raw = '';
29
29
  process.stdin.on('data', (d) => (raw += d));
@@ -64,7 +64,7 @@ export const RECOMMENDED_ENV: Readonly<Record<string, string>> = {
64
64
  };
65
65
 
66
66
  /** Keys `awm setup` owns outright; everything else in an existing env block is the user's. */
67
- export const OWNED_ENV_KEYS = ['AWM_DB_PATH', 'AWM_AGENT_ID', 'AWM_HOOK_PORT', 'AWM_HOOK_PORT_RANGE', 'AWM_HOOK_SECRET'] as const;
67
+ export const OWNED_ENV_KEYS = ['AWM_DB_PATH', 'AWM_AGENT_ID', 'AWM_HOOK_PORT', 'AWM_HOOK_PORT_RANGE', 'AWM_HOOK_SECRET', 'AWM_CLIENT'] as const;
68
68
 
69
69
  /**
70
70
  * Build environment variables for the MCP server process.
@@ -80,7 +80,7 @@ export function buildEnvVars(
80
80
  hookPort: string,
81
81
  hookSecret: string,
82
82
  isWindows: boolean,
83
- opts: { hookPortRange?: string; existing?: Record<string, string> | null } = {},
83
+ opts: { hookPortRange?: string; existing?: Record<string, string> | null; client?: string } = {},
84
84
  ): Record<string, string> {
85
85
  const owned: Record<string, string> = {
86
86
  AWM_DB_PATH: isWindows ? dbPath.split('\\').join('/') : dbPath,
@@ -141,6 +141,8 @@ export function buildSetupContext(opts: {
141
141
  hookPortRange?: string;
142
142
  installPrime?: boolean;
143
143
  existingEnv?: Record<string, string> | null;
144
+ /** Which surface this install is for — stamped onto every write as a `client=` tag. */
145
+ client?: string;
144
146
  }): SetupContext {
145
147
  const cwd = process.cwd();
146
148
  const projectName = basename(cwd).toLowerCase().replace(/[^a-z0-9-]/g, '-');
@@ -158,7 +160,7 @@ export function buildSetupContext(opts: {
158
160
  const hookPort = opts.hookPort ?? existing?.AWM_HOOK_PORT ?? '8401';
159
161
  const hookPortRange = opts.hookPortRange ?? existing?.AWM_HOOK_PORT_RANGE ?? '10';
160
162
  const hookSecret = resolveHookSecret(dbPath);
161
- const envVars = buildEnvVars(dbPath, agentId, hookPort, hookSecret, isWindows, { hookPortRange, existing });
163
+ const envVars = buildEnvVars(dbPath, agentId, hookPort, hookSecret, isWindows, { hookPortRange, existing, client: opts.client });
162
164
 
163
165
  return {
164
166
  cwd,
package/src/cli.ts CHANGED
@@ -47,6 +47,7 @@ AgentWorkingMemory — Cognitive memory for AI agents
47
47
 
48
48
  Usage:
49
49
  awm setup [target] [options] Configure AWM for an AI CLI
50
+ awm plugin Install as a Claude Code plugin (local, no GitHub)
50
51
  awm doctor [target|--all] Validate AWM integrations
51
52
  awm mcp Start MCP server (stdio)
52
53
  awm serve [--port <port>] Start HTTP API server
@@ -145,7 +146,8 @@ async function setup() {
145
146
  // Build context. A previous install's values survive unless a flag overrides them
146
147
  // (0.14.6) — an upgrade must never repoint the database or rename the agent.
147
148
  const existingEnv = adapter.readExistingEnv?.(isGlobal, process.cwd()) ?? null;
148
- const ctx = buildSetupContext({ agentId, dbPath, isGlobal, hookPort, hookPortRange, installPrime, existingEnv });
149
+ // `awm setup claude-code` -> client=claude-code, cursor -> client=cursor, and so on.
150
+ const ctx = buildSetupContext({ agentId, dbPath, isGlobal, hookPort, hookPortRange, installPrime, existingEnv, client: target });
149
151
 
150
152
  // Run adapter
151
153
  const configAction = adapter.writeMcpConfig(ctx);
@@ -172,6 +174,58 @@ Next steps:
172
174
  `.trim());
173
175
  }
174
176
 
177
+ // ─── PLUGIN ──────────────────────────────────────
178
+
179
+ /**
180
+ * Print the two lines that install AWM as a Claude Code plugin FROM THIS INSTALL.
181
+ *
182
+ * Most users are on Windows and will never clone the repository, so
183
+ * `/plugin marketplace add CompleteIdeas/agent-working-memory` — which needs GitHub — is the
184
+ * wrong default for them. npm has already put a complete marketplace on their disk; they
185
+ * just have no way to know where it is. This tells them, with the real resolved path.
186
+ */
187
+ function pluginInfo(): void {
188
+ // dist/cli.js -> package root
189
+ const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
190
+ const marketplace = join(pkgRoot, '.claude-plugin', 'marketplace.json');
191
+
192
+ if (!existsSync(marketplace)) {
193
+ console.log(`
194
+ AWM ${VERSION} — plugin not found in this install
195
+
196
+ Looked for: ${marketplace}
197
+
198
+ This happens when AWM is running from a source checkout that has not been built,
199
+ or from a package published before the plugin was bundled (< 0.15.3).
200
+
201
+ From a checkout: npm run build && npm run build:plugin
202
+ Otherwise: npm install -g agent-working-memory@latest
203
+ `.trimStart());
204
+ return;
205
+ }
206
+
207
+ console.log(`
208
+ AWM ${VERSION} — install as a Claude Code plugin
209
+
210
+ Paste these two lines into Claude Code:
211
+
212
+ /plugin marketplace add ${pkgRoot}
213
+ /plugin install awm@agent-working-memory
214
+
215
+ Then restart Claude Code. You get 19 memory tools, the session hooks, and the
216
+ usage guidance as a skill.
217
+
218
+ No GitHub access needed — the marketplace is this npm install, already on disk.
219
+
220
+ The store defaults to the same file \`awm setup --global\` uses, so a plugin
221
+ install and a CLI install share one memory rather than quietly keeping two.
222
+
223
+ Check it worked: awm doctor claude-code
224
+ More: https://github.com/CompleteIdeas/agent-working-memory/blob/master/docs/plugin.md
225
+ `.trimStart());
226
+ }
227
+
228
+
175
229
  // ─── DOCTOR ──────────────────────────────────────
176
230
 
177
231
  async function doctor() {
@@ -803,6 +857,9 @@ switch (command) {
803
857
  case 'setup':
804
858
  await setup();
805
859
  break;
860
+ case 'plugin':
861
+ pluginInfo();
862
+ break;
806
863
  case 'doctor':
807
864
  await doctor();
808
865
  break;
@@ -491,6 +491,24 @@ async function createNewEngram(
491
491
 
492
492
  const tags = [...(input.tags ?? [])];
493
493
  if (isLowSalience && !tags.includes('low-salience')) tags.push('low-salience');
494
+
495
+ // Which CLIENT wrote this: `client=claude-code`, `client=claude-desktop`, `client=cursor`.
496
+ //
497
+ // Deliberately not called `surface`: memory_whoami already reports a `surface` field
498
+ // meaning the TRANSPORT (mcp | http), and one word for two concepts is how you lose an
499
+ // hour six months from now. Transport is how the request arrived; client is which
500
+ // application it arrived from.
501
+ // AWM_CLIENT is set by the thing that launches the server (the Claude Code plugin, the
502
+ // Desktop extension, `awm setup`), never by the caller, so it records where a memory came
503
+ // from rather than what the writer claims.
504
+ //
505
+ // Captured by default rather than behind a flag, because provenance is one-way: a tag can
506
+ // be ignored or stripped later, but the origin of a memory written without one is gone for
507
+ // good. An explicit `client=` in the caller's tags always wins.
508
+ const client = (process.env.AWM_CLIENT ?? '').trim();
509
+ if (client && !tags.some(t => t.toLowerCase().startsWith('client='))) {
510
+ tags.push(`client=${client}`);
511
+ }
494
512
 
495
513
  // Auto-tag meta-tags (default-OFF, AWM_AUTOTAG=1). extractMetaTags emits
496
514
  // `entity:<ProperNoun>` and `cat:<category>` tags from concept+content. These