agent-working-memory 0.10.0 → 0.12.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 (136) hide show
  1. package/README.md +118 -19
  2. package/dist/adapters/claude-code.d.ts.map +1 -1
  3. package/dist/adapters/claude-code.js +63 -3
  4. package/dist/adapters/claude-code.js.map +1 -1
  5. package/dist/adapters/common.d.ts.map +1 -1
  6. package/dist/adapters/common.js +329 -302
  7. package/dist/adapters/common.js.map +1 -1
  8. package/dist/api/routes.d.ts.map +1 -1
  9. package/dist/api/routes.js +31 -8
  10. package/dist/api/routes.js.map +1 -1
  11. package/dist/cli/migrate.js +29 -29
  12. package/dist/cli.js +82 -2
  13. package/dist/cli.js.map +1 -1
  14. package/dist/coordination/circuit-breaker.js +23 -23
  15. package/dist/coordination/routes.d.ts.map +1 -1
  16. package/dist/coordination/routes.js +174 -170
  17. package/dist/coordination/routes.js.map +1 -1
  18. package/dist/core/embeddings.d.ts.map +1 -1
  19. package/dist/core/embeddings.js +3 -0
  20. package/dist/core/embeddings.js.map +1 -1
  21. package/dist/core/entity-extract.d.ts +3 -0
  22. package/dist/core/entity-extract.d.ts.map +1 -0
  23. package/dist/core/entity-extract.js +47 -0
  24. package/dist/core/entity-extract.js.map +1 -0
  25. package/dist/core/salience.d.ts.map +1 -1
  26. package/dist/core/salience.js +14 -2
  27. package/dist/core/salience.js.map +1 -1
  28. package/dist/core/whoami.d.ts +24 -0
  29. package/dist/core/whoami.d.ts.map +1 -0
  30. package/dist/core/whoami.js +66 -0
  31. package/dist/core/whoami.js.map +1 -0
  32. package/dist/core/write-pipeline.d.ts +9 -0
  33. package/dist/core/write-pipeline.d.ts.map +1 -1
  34. package/dist/core/write-pipeline.js +109 -68
  35. package/dist/core/write-pipeline.js.map +1 -1
  36. package/dist/core/write-telemetry.d.ts +33 -0
  37. package/dist/core/write-telemetry.d.ts.map +1 -0
  38. package/dist/core/write-telemetry.js +110 -0
  39. package/dist/core/write-telemetry.js.map +1 -0
  40. package/dist/engine/activation.d.ts +22 -12
  41. package/dist/engine/activation.d.ts.map +1 -1
  42. package/dist/engine/activation.js +133 -17
  43. package/dist/engine/activation.js.map +1 -1
  44. package/dist/engine/consolidation-scheduler.d.ts +1 -1
  45. package/dist/engine/consolidation-scheduler.js +1 -1
  46. package/dist/engine/consolidation.d.ts +1 -0
  47. package/dist/engine/consolidation.d.ts.map +1 -1
  48. package/dist/engine/consolidation.js +18 -0
  49. package/dist/engine/consolidation.js.map +1 -1
  50. package/dist/engine/eval.d.ts.map +1 -1
  51. package/dist/engine/eval.js +5 -1
  52. package/dist/engine/eval.js.map +1 -1
  53. package/dist/index.js +21 -2
  54. package/dist/index.js.map +1 -1
  55. package/dist/mcp.d.ts +2 -1
  56. package/dist/mcp.d.ts.map +1 -1
  57. package/dist/mcp.js +212 -97
  58. package/dist/mcp.js.map +1 -1
  59. package/dist/onboard/index.d.ts +68 -0
  60. package/dist/onboard/index.d.ts.map +1 -0
  61. package/dist/onboard/index.js +265 -0
  62. package/dist/onboard/index.js.map +1 -0
  63. package/dist/recipes/index.d.ts +57 -0
  64. package/dist/recipes/index.d.ts.map +1 -0
  65. package/dist/recipes/index.js +81 -0
  66. package/dist/recipes/index.js.map +1 -0
  67. package/dist/storage/pglite-schema.d.ts.map +1 -1
  68. package/dist/storage/pglite-schema.js +170 -143
  69. package/dist/storage/pglite-schema.js.map +1 -1
  70. package/dist/storage/pglite.d.ts +5 -0
  71. package/dist/storage/pglite.d.ts.map +1 -1
  72. package/dist/storage/pglite.js +180 -138
  73. package/dist/storage/pglite.js.map +1 -1
  74. package/dist/storage/postgres.d.ts +5 -0
  75. package/dist/storage/postgres.d.ts.map +1 -1
  76. package/dist/storage/postgres.js +44 -2
  77. package/dist/storage/postgres.js.map +1 -1
  78. package/dist/storage/sqlite.d.ts +9 -0
  79. package/dist/storage/sqlite.d.ts.map +1 -1
  80. package/dist/storage/sqlite.js +394 -326
  81. package/dist/storage/sqlite.js.map +1 -1
  82. package/dist/types/engram.d.ts +14 -0
  83. package/dist/types/engram.d.ts.map +1 -1
  84. package/dist/types/engram.js.map +1 -1
  85. package/dist/version.d.ts +2 -0
  86. package/dist/version.d.ts.map +1 -0
  87. package/dist/version.js +27 -0
  88. package/dist/version.js.map +1 -0
  89. package/package.json +9 -1
  90. package/src/adapters/claude-code.ts +66 -3
  91. package/src/adapters/common.ts +538 -511
  92. package/src/api/index.ts +3 -3
  93. package/src/api/routes.ts +999 -970
  94. package/src/cli/migrate.ts +307 -307
  95. package/src/cli.ts +77 -2
  96. package/src/coordination/circuit-breaker.ts +83 -83
  97. package/src/coordination/failure-modes.ts +50 -50
  98. package/src/coordination/routes.ts +2155 -2150
  99. package/src/core/decay.ts +63 -63
  100. package/src/core/embeddings.ts +113 -110
  101. package/src/core/entity-extract.ts +47 -0
  102. package/src/core/index.ts +5 -5
  103. package/src/core/logger.ts +36 -36
  104. package/src/core/ml-worker-entry.ts +194 -194
  105. package/src/core/ml-worker.ts +281 -281
  106. package/src/core/query-expander.ts +122 -122
  107. package/src/core/reranker.ts +119 -119
  108. package/src/core/salience.ts +529 -514
  109. package/src/core/whoami.ts +92 -0
  110. package/src/core/write-pipeline.ts +60 -8
  111. package/src/core/write-telemetry.ts +131 -0
  112. package/src/engine/activation.ts +1468 -1369
  113. package/src/engine/confidence.ts +120 -120
  114. package/src/engine/consolidation-scheduler.ts +242 -242
  115. package/src/engine/consolidation.ts +887 -869
  116. package/src/engine/eval.ts +107 -102
  117. package/src/engine/eviction.ts +101 -101
  118. package/src/engine/index.ts +8 -8
  119. package/src/engine/retraction.ts +366 -366
  120. package/src/engine/staging.ts +74 -74
  121. package/src/index.ts +248 -226
  122. package/src/mcp.ts +1341 -1211
  123. package/src/onboard/index.ts +298 -0
  124. package/src/recipes/index.ts +125 -0
  125. package/src/storage/index.ts +3 -3
  126. package/src/storage/pglite-schema.ts +193 -166
  127. package/src/storage/pglite.ts +1420 -1372
  128. package/src/storage/postgres.ts +50 -2
  129. package/src/storage/sqlite.ts +1936 -1861
  130. package/src/storage/store.ts +80 -80
  131. package/src/types/agent.ts +67 -67
  132. package/src/types/checkpoint.ts +46 -46
  133. package/src/types/engram.ts +22 -0
  134. package/src/types/eval.ts +100 -100
  135. package/src/types/index.ts +6 -6
  136. package/src/version.ts +26 -0
@@ -1,511 +1,538 @@
1
- // Copyright 2026 Robert Winter / Complete Ideas
2
- // SPDX-License-Identifier: Apache-2.0
3
-
4
- /**
5
- * Shared utilities for CLI adapters.
6
- *
7
- * Extracted from the original setup() in cli.ts — path resolution, secrets,
8
- * environment variables, MCP command building, and the AWM instruction snippet.
9
- */
10
-
11
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
12
- import { resolve, join, dirname, basename } from 'node:path';
13
- import { randomBytes } from 'node:crypto';
14
- import { homedir as osHomedir } from 'node:os';
15
- import { fileURLToPath } from 'node:url';
16
- import type { SetupContext } from './types.js';
17
-
18
- const __filename = fileURLToPath(import.meta.url);
19
- const __dirname = dirname(__filename);
20
-
21
- /** Resolve the AWM package root (where src/ and dist/ live). */
22
- export function resolvePackageRoot(): string {
23
- // __dirname is src/adapters/ at dev time, dist/adapters/ at build time
24
- return resolve(__dirname, '..', '..');
25
- }
26
-
27
- /** Resolve the database path — default to <packageRoot>/data/memory.db. */
28
- export function resolveDbPath(packageRoot: string, explicit?: string | null): string {
29
- const dbPath = explicit ?? join(packageRoot, 'data', 'memory.db');
30
- const dbDir = dirname(dbPath);
31
- if (!existsSync(dbDir)) {
32
- mkdirSync(dbDir, { recursive: true });
33
- }
34
- return dbPath;
35
- }
36
-
37
- /** Read or generate the hook secret token. */
38
- export function resolveHookSecret(dbPath: string): string {
39
- const secretPath = join(dirname(dbPath), '.awm-hook-secret');
40
- if (existsSync(secretPath)) {
41
- const existing = readFileSync(secretPath, 'utf-8').trim();
42
- if (existing) return existing;
43
- }
44
- const secret = randomBytes(32).toString('hex');
45
- mkdirSync(dirname(secretPath), { recursive: true });
46
- writeFileSync(secretPath, secret + '\n');
47
- return secret;
48
- }
49
-
50
- /** Build environment variables for the MCP server process. */
51
- export function buildEnvVars(
52
- dbPath: string,
53
- agentId: string,
54
- hookPort: string,
55
- hookSecret: string,
56
- isWindows: boolean,
57
- ): Record<string, string> {
58
- return {
59
- AWM_DB_PATH: isWindows ? dbPath.replace(/\\/g, '/') : dbPath,
60
- AWM_AGENT_ID: agentId,
61
- AWM_HOOK_PORT: hookPort,
62
- AWM_HOOK_SECRET: hookSecret,
63
- };
64
- }
65
-
66
- /**
67
- * Resolve the MCP server command + args.
68
- *
69
- * Prefers absolute path to dist/mcp.js (works from any cwd).
70
- * Falls back to npx tsx src/mcp.ts for dev mode.
71
- */
72
- export function resolveMcpCommand(ctx: SetupContext): {
73
- command: string;
74
- args: string[];
75
- } {
76
- if (ctx.hasDist) {
77
- return {
78
- command: 'node',
79
- args: [ctx.mcpDist.replace(/\\/g, '/')],
80
- };
81
- }
82
- // Dev fallback
83
- if (ctx.isWindows) {
84
- return {
85
- command: 'cmd',
86
- args: ['/c', 'npx', 'tsx', ctx.mcpScript.replace(/\\/g, '/')],
87
- };
88
- }
89
- return {
90
- command: 'npx',
91
- args: ['tsx', ctx.mcpScript],
92
- };
93
- }
94
-
95
- /** Build a full SetupContext from parsed CLI flags. */
96
- export function buildSetupContext(opts: {
97
- agentId?: string;
98
- dbPath?: string | null;
99
- isGlobal: boolean;
100
- hookPort: string;
101
- }): SetupContext {
102
- const cwd = process.cwd();
103
- const projectName = basename(cwd).toLowerCase().replace(/[^a-z0-9-]/g, '-');
104
- const packageRoot = resolvePackageRoot();
105
- const mcpScript = join(packageRoot, 'src', 'mcp.ts');
106
- const mcpDist = join(packageRoot, 'dist', 'mcp.js');
107
- const hasDist = existsSync(mcpDist);
108
- const isWindows = process.platform === 'win32';
109
-
110
- const agentId = opts.agentId ?? (opts.isGlobal ? 'claude' : projectName);
111
- const dbPath = resolveDbPath(packageRoot, opts.dbPath);
112
- const hookSecret = resolveHookSecret(dbPath);
113
- const envVars = buildEnvVars(dbPath, agentId, opts.hookPort, hookSecret, isWindows);
114
-
115
- return {
116
- cwd,
117
- projectName,
118
- agentId,
119
- dbPath,
120
- packageRoot,
121
- mcpDist,
122
- mcpScript,
123
- hasDist,
124
- hookSecret,
125
- hookPort: opts.hookPort,
126
- isGlobal: opts.isGlobal,
127
- isWindows,
128
- envVars,
129
- };
130
- }
131
-
132
- /** Home directory. */
133
- export function homedir(): string {
134
- return osHomedir();
135
- }
136
-
137
- // ─── Instruction content ────────────────────────────────
138
-
139
- /**
140
- * Core AWM instruction snippet — shared across all adapters.
141
- * Each adapter wraps this in the appropriate file format.
142
- */
143
- /**
144
- * Upsert the AWM section into an instruction file (CLAUDE.md, AGENTS.md, .cursorrules).
145
- *
146
- * Behavior:
147
- * - File doesn't exist -> create with title + AWM_INSTRUCTION_CONTENT
148
- * - Section absent -> append
149
- * - Section present + identical -> skip
150
- * - Section present + stale -> REPLACE in place, preserve content above/below
151
- *
152
- * Section is bounded by `## Memory (AWM)` (with optional trailing modifier) at the
153
- * start, and the next `## ` heading or EOF at the end.
154
- *
155
- * Returns a short human-readable status string for the setup command output.
156
- */
157
- export function upsertAwmSection(
158
- filePath: string,
159
- newContent: string,
160
- options: { titleIfNew?: string; suffix?: string } = {},
161
- ): string {
162
- const fname = basename(filePath);
163
- const suffix = options.suffix ?? '';
164
-
165
- if (!existsSync(filePath)) {
166
- const title = options.titleIfNew ?? `# ${basename(dirname(filePath))}`;
167
- mkdirSync(dirname(filePath), { recursive: true });
168
- writeFileSync(filePath, `${title}\n\n${newContent}${suffix}`);
169
- return `${fname}: created with AWM workflow section`;
170
- }
171
-
172
- const existing = readFileSync(filePath, 'utf-8');
173
-
174
- // Find section bounds: `## Memory (AWM)` (possibly with ` — MANDATORY` etc.) until next `## ` or EOF
175
- const startRegex = /^## Memory \(AWM\)[^\n]*$/m;
176
- const startMatch = startRegex.exec(existing);
177
-
178
- if (!startMatch) {
179
- // Section not present — append
180
- writeFileSync(filePath, existing.trimEnd() + '\n\n' + newContent + suffix);
181
- return `${fname}: appended AWM workflow section`;
182
- }
183
-
184
- // Find end: next `## ` heading after the section start, or EOF
185
- const afterStart = startMatch.index + startMatch[0].length;
186
- const nextHeadingRegex = /^## (?!Memory \(AWM\))/m;
187
- nextHeadingRegex.lastIndex = afterStart;
188
- const tail = existing.slice(afterStart);
189
- const nextMatch = nextHeadingRegex.exec(tail);
190
- const sectionEnd = nextMatch ? afterStart + nextMatch.index : existing.length;
191
-
192
- const currentSection = existing.slice(startMatch.index, sectionEnd).trimEnd();
193
- const desiredSection = (newContent + suffix).trimEnd();
194
-
195
- if (currentSection === desiredSection) {
196
- return `${fname}: AWM section already up-to-date (skipped)`;
197
- }
198
-
199
- const before = existing.slice(0, startMatch.index).trimEnd();
200
- const after = existing.slice(sectionEnd).replace(/^\s*\n/, '');
201
- const rebuilt =
202
- (before ? before + '\n\n' : '') +
203
- desiredSection +
204
- (after ? '\n\n' + after : '\n');
205
- writeFileSync(filePath, rebuilt);
206
- return `${fname}: AWM section updated (preserved surrounding content)`;
207
- }
208
-
209
- export const AWM_INSTRUCTION_CONTENT = `
210
- ## Memory (AWM) — MANDATORY
211
-
212
- **AWM is THE memory system.** Use it via the \`agent-working-memory\` MCP server
213
- (preferred) or HTTP at \`http://127.0.0.1:8400\` (fallback). The file-based
214
- auto-memory at \`~/.claude/projects/.../memory/*.md\` is a LEGACY bootstrap path —
215
- **do not write new memories to it.** All persistent knowledge goes through AWM.
216
-
217
- If MCP tools aren't loaded at session start, use ToolSearch with
218
- \`select:mcp__agent-working-memory__memory_recall,mcp__agent-working-memory__memory_write\`
219
- to load them. If the MCP server isn't responsive, restart with \`/mcp\` or use
220
- the HTTP endpoints (\`POST /memory/write-batch\`, \`POST /memory/activate\`)
221
- directly — but **DO NOT fall back to markdown files**. Files drift the moment
222
- you write them; AWM stays current because every agent reads + writes the same store.
223
-
224
- ### Lifecycle (always do these, in this order)
225
- 1. **Session start**: call \`memory_restore\` to recover previous context.
226
- 2. **Starting a task**: call \`memory_task_begin\` (checkpoints + recalls relevant memories).
227
- 3. **During work**: call \`memory_recall\` BEFORE stating any fact, BEFORE searching
228
- the filesystem, BEFORE making architectural decisions. Recall is ~300ms — cheaper
229
- than one filesystem search.
230
- 4. **As you learn things**: call \`memory_write\` proactively. Don't batch.
231
- 5. **Finishing a task**: call \`memory_task_end\` with a summary.
232
- 6. **Auto-checkpoint** is handled by hooks (compaction, session-end, 15-min timer). No action needed.
233
-
234
- ### Write memory when:
235
- - A project decision is made or changed
236
- - A root cause is discovered after debugging
237
- - A reusable implementation pattern is established
238
- - A user preference, constraint, or requirement is clarified
239
- - A prior assumption is found to be wrong
240
- - A significant piece of work is completed
241
-
242
- ### Writing for recall (the highest-leverage section)
243
- A memory's recall quality is set the moment you write it. AWM is fast at
244
- finding what's findable but if the write is shaped wrong, no retriever
245
- can rescue it. Be slightly more verbose at the front than feels natural:
246
- the first 1-2 sentences are what BM25, the embedding model, and concept
247
- extraction all see most strongly.
248
-
249
- - **Lead with the rule or fact.** Don't open with context or backstory.
250
- "Don't mock the database in integration tests." comes first; the reason
251
- comes second. Recall scans the head of the body, not the tail.
252
- - **Pick the most specific topic.** Not \`auth\` — \`auth-magic-link-rate-limit\`.
253
- Topic is a hard filter at recall time. Generic topics hide the memory in
254
- a noisy bucket where it competes with everything else in the area.
255
- - **Include 2+ retrievable identifiers.** File paths, function names, table
256
- columns, ticket IDs, exact error strings, the literal terms a future query
257
- will use. \`AccountingService.closePeriod()\` beats "the accounting code."
258
- \`tblMemberDetails.activation_date\` beats "the activation column."
259
- \`schema/072-period-close.sql\` beats "the migration."
260
- - **Write in the vocabulary of the future question.** When you imagine asking
261
- this in three months, what nouns will you use? Use those nouns. Don't
262
- paraphrase the user's domain language into your own neutral summary.
263
- - **Reserve canonical for stable invariants.** Decisions, requirements,
264
- hard facts, cross-agent shared context. Working class (default) is correct
265
- for findings, observations, and progress notes. The canonical floor is
266
- 0.7 salience overusing it pollutes the canonical layer and the floor
267
- loses meaning.
268
- - **Include the why for feedback memories.** A rule without a reason can't
269
- be applied to edge cases. "Don't mock the database" is brittle. "Don't
270
- mock the database last quarter mocked tests masked a broken migration"
271
- is portable to new situations.
272
-
273
- ### Tagging rules (REQUIRED AWM's prefix-tag retrieval boost depends on these)
274
-
275
- Every \`memory_write\` should pass these structured fields. AWM stores each as a
276
- prefix-tag like \`proj=\`, \`topic=\`, \`intent=\`, etc. and uses them for BM25
277
- and entity-bridge boosts at recall time.
278
-
279
- | Field | Required? | Format | Example |
280
- |---|---|---|---|
281
- | \`project\` | **YES** | one short word matching the current project | \`"EquiHub"\`, \`"AWM"\`, \`"USEA-Agent"\` |
282
- | \`topic\` | **YES** | one or more lowercase area words | \`"database-migration"\`, \`"benchmarks"\` |
283
- | \`intent\` | **YES** | one of: \`decision\` / \`finding\` / \`todo\` / \`question\` / \`context\` | \`"finding"\` |
284
- | \`confidence_level\` | **YES** | \`verified\` (tested) / \`observed\` (read in code) / \`assumed\` (reasoning) | \`"verified"\` |
285
- | \`source\` | recommended | \`code-reading\` / \`debugging\` / \`discussion\` / \`research\` / \`testing\` / \`observation\` | \`"testing"\` |
286
- | \`memory_class\` | when stable | \`canonical\` (source-of-truth, 0.7 floor, never staged) / \`working\` (default) / \`ephemeral\` | \`"canonical"\` |
287
- | \`session_id\` | recommended | current conversation ID for entity-bridge boost | autogenerated |
288
- | \`tags\` | when applicable | extra prefix-tags for IDs and dates | \`["ticket=18360", "date=2026-05-11"]\` |
289
-
290
- **Always add identifier tags when present in the content:**
291
- - \`ticket=<id>\` for Freshdesk tickets
292
- - \`member=<id>\` for member IDs
293
- - \`horse=<id>\` for horse_member_id
294
- - \`usef=<id>\` for USEF lookups
295
- - \`date=YYYY-MM-DD\` for temporal anchoring (ISO format)
296
- - \`person=<Name>\` for stakeholder quotes / decisions
297
- - \`version=<X.Y.Z>\` for release-specific findings
298
-
299
- ### Memory classes (controls how strictly the salience filter gates the write)
300
- - \`memory_class: canonical\` source-of-truth memories. Floor 0.7 salience, never staged.
301
- Use for: user-stated decisions, project requirements, verified architectural facts,
302
- cross-agent shared context. **In a hive (multi-agent) setup, always use \`canonical\`
303
- for writes that other agents must be able to recall** the default \`working\` class
304
- may get filtered.
305
- - \`memory_class: working\` (default) observations and findings. Salience-gated.
306
- - \`memory_class: ephemeral\` short-lived context that should decay quickly.
307
-
308
- ### Salience auto-promotion (defense in depth)
309
- The salience filter automatically promotes certain content patterns even if you forget
310
- to set \`memory_class\` explicitly:
311
- - **User feedback** — content starting with "Robert said…", "Katherine directed…",
312
- "Nancy decided…" etc. auto-promotes to canonical. So quoting the user verbatim
313
- always preserves the decision.
314
- - **Verified operational records** — content with an action verb (Submitted, Finalized,
315
- Completed, Reconciled, Triaged, Posted, Resolved, Stamped, Pushed, Deployed, Migrated,
316
- Imported, Exported, Backfilled) plus 2+ concrete identifiers (ISO date \`YYYY-MM-DD\`,
317
- or contextual numeric IDs like "event 18969", "ticket #18330", "USEF 341980") gets
318
- a 0.45 salience floor. So batch summaries with real IDs survive even when topic
319
- terms repeat.
320
-
321
- If neither pattern applies and you want a memory to definitely survive, set
322
- \`memory_class: canonical\` explicitly. Don't rely on auto-promotion for important writes.
323
-
324
- ### Recall memory when:
325
- - **BEFORE stating ANY fact about how a system works** recall first; if AWM doesn't
326
- have it, read the code. Never guess and present it as fact.
327
- - **BEFORE searching the filesystem** — recall first; AWM is faster and has cross-session
328
- knowledge that file search doesn't.
329
- - Starting work on a new task or subsystem
330
- - Re-entering code you haven't touched recently
331
- - After a failed attemptcheck if there's prior knowledge
332
- - Before refactoring or making architectural changes
333
- - When a topic comes up that you might have prior context on
334
-
335
- Recall is fast (~300ms typical). Use it freely.
336
-
337
- ### Recall strategy (when one query isn't enough)
338
- AWM's adaptive retrieval handles most query variations natively — synonym
339
- expansion, multi-channel scoring, embedding + BM25 + reranker agreement.
340
- A single recall is usually enough.
341
-
342
- When it isn't:
343
- - **If the first recall returns nothing or returns the wrong things, reformulate.**
344
- Try a second query with different phrasing — synonyms, more specific nouns,
345
- the exact identifier from the code rather than the conceptual name. Two or
346
- three recalls cost less than one filesystem search.
347
- - **Use the words a domain expert would use, not generic English.** "Period
348
- close lock" not "accounting feature"; "magic link rate limit" not "auth issue."
349
- - **For broad exploration, pass \`mode: "exploratory"\`** wider candidate
350
- pool, lower precision floor. For specific lookups, leave mode unset (auto).
351
- - **Don't ensemble more than 3 reformulations.** If three different phrasings
352
- return nothing, the memory probably isn't there read the code instead of
353
- burning more recalls.
354
-
355
- ### Recall tuning (0.8.x opt-in parameters for higher-quality recall)
356
- Default \`memory_recall\` is tuned for the common case. The 0.8.x recall pipeline
357
- exposes four opt-in parameters that change the cost/quality tradeoff. Use them
358
- when the default doesn't match what you actually need.
359
-
360
- - **\`granularity: 'compact'\`** every result carries a 200-char \`summary\`
361
- field with a query-aware snippet (the densest window of query terms in the
362
- content). Use this when you expect to scan 5+ results to find one — saves
363
- ~70% of recall output tokens. The full content stays available in
364
- \`engram.content\` if you want to drill into a specific result.
365
- - **\`granularity: 'auto'\`** — confidence-adaptive. If the top result is a clear
366
- winner, it gets a longer summary while the rest are compact. If confidence
367
- is uniform across results, everything is compact. Use when you don't know
368
- in advance whether one result will dominate.
369
- - **\`require_confidence: 0.10 | 0.25 | 0.40\`** opt-in abstention. AWM
370
- returns \`[]\` instead of low-confidence noise. Use when you're about to ACT
371
- on the recalled fact (grounding a decision, citing the memory verbatim,
372
- contradicting a prior assumption). Thresholds: \`0.10\` strict — only abstain
373
- on garbage; \`0.25\` balanced; \`0.40\` aggressive prefer "I don't know"
374
- over "best of bad." When abstention fires (empty result), treat it as a
375
- signal either the memory genuinely isn't there (read the code) or your
376
- query missed (reformulate). Don't retry without the threshold.
377
- - **\`workspace: "<name>"\`** hive-mode recall across all agents in the
378
- workspace. Use when other agents may have written canonical knowledge you
379
- need. Default is agent-scoped (your own memories only). Can also be set
380
- globally via the \`AWM_WORKSPACE\` env var.
381
-
382
- ### Keep memory fresh
383
- - After recalling a memory, if you observe the real state is different → call
384
- \`memory_supersede\` immediately with the corrected version.
385
- - After using a recalled memory: call \`memory_feedback\` (useful/not-useful) so the
386
- activation engine learns what's valuable.
387
- - If you discover a memory is factually wrong: \`memory_retract\` to remove it.
388
- - **If you bypass AWM (file-memory, in-context notes, "I'll just remember"), the memory
389
- drifts out of date. The system relies on you to keep it current. This is the #1
390
- failure mode.**
391
-
392
- ### Content fade write-and-forget is safe (0.8.x)
393
- Un-recalled engrams gradually fade their content while preserving cue pathways
394
- (concept + tags + embedding stay intact). This is Paper 1 — storage
395
- degradation. Practical implications:
396
-
397
- - **Don't manually purge memories** to "save space." The system already
398
- compresses unused content. Old memories stay findable via cue match even
399
- when their body has decayed.
400
- - **Don't over-pin with \`memory_class: canonical\`** to fight fade. Canonical
401
- only changes salience gating at write time, not fade behavior. Fade
402
- affects un-recalled engrams of any class.
403
- - **Recall keeps content alive.** Every recall touches the engram and resets
404
- its fade clock. Frequently-recalled memories stay full-fidelity automatically.
405
- - **Supersede is the right tool for stale facts.** When you observe a memory
406
- is outdated, call \`memory_supersede\` the new version inherits the old
407
- one's coherent associations (counter-narrative replacement, 0.8.x) so cue
408
- pathways carry forward to the replacement.
409
-
410
- ### Examplegood vs bad memory_write
411
-
412
- **BAD** (no prefix tags, vague concept, can't be recalled by future queries):
413
- \`\`\`
414
- memory_write(
415
- concept="found a bug",
416
- content="The thing I was looking at was broken so I fixed it."
417
- )
418
- \`\`\`
419
-
420
- **GOOD** (rich identifiers, structured metadata, prefix tags):
421
- \`\`\`
422
- memory_write(
423
- concept="EquiHub period-close BLOCKED check missing server-side",
424
- 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.",
425
- project="EquiHub",
426
- topic="accounting",
427
- intent="finding",
428
- confidence_level="verified",
429
- source="debugging",
430
- memory_class="canonical",
431
- tags=["ticket=18360", "person=Robert", "date=2026-05-11", "topic=period-close", "topic=security"]
432
- )
433
- \`\`\`
434
-
435
- ### Also:
436
- - To track work items: memory_task_add, memory_task_update, memory_task_list, memory_task_next
437
- - AWM is shared across all agents in real time. When any agent writes or supersedes a
438
- memory, every other agent can recall it immediately.
439
-
440
- ### Output compression (token efficiency, output-only)
441
- When a tool returns a LARGE STRUCTURED result you need to keep in context — a JSON
442
- array of records, query rows, a log dump, an API response — pass it through
443
- \`compress_output\` first. It re-encodes the data as TOON (a compact, lossless,
444
- schema-aware tabular form of JSON), cutting ~50-65% of the tokens at no
445
- comprehension cost. This is output-only: it never changes the data or your memories.
446
- - Use it on big STRUCTURED outputs, not on prose. Prose is returned unchanged
447
- for trimming memory prose, use recall \`granularity: 'compact'\` instead.
448
- - It returns a \`ref\`; call \`retrieve_original(ref)\` if you later need the exact
449
- verbatim source (e.g. to hand it to another tool unchanged).
450
- - Don't bother for small outputs — it only compresses when the saving is worthwhile
451
- and falls back to plain JSON if TOON wouldn't reproduce the data exactly.
452
-
453
- ### Backend (SQLite vs PGlite, 0.8.x)
454
- AWM ships two storage backends. The installer picks SQLite by default; both
455
- are functionally equivalent for cognitive workloads, but differ in operational
456
- guarantees:
457
-
458
- - **SQLite** (default) embedded, **multi-process safe** via WAL mode. Best
459
- for single-machine setups and MCP scenarios where multiple Claude Code
460
- sessions may open the same database concurrently.
461
- - **PGlite** — embedded Postgres (WASM) with pgvector. **Single-process only**
462
- two MCP processes against the same \`memory-pglite/\` directory will
463
- abort the second. Pick via \`AWM_STORE_BACKEND=pglite\` and
464
- \`AWM_DB_PATH=path/to/memory-pglite\`.
465
- - **Auto-detect** if \`AWM_DB_PATH\` points to a directory that already
466
- exists, AWM detects PGlite; a file SQLite. No explicit
467
- \`AWM_STORE_BACKEND\` needed when an existing DB is present.
468
-
469
- For the comparison table (recall quality parity, BM25 vs \`ts_rank_cd\`,
470
- multi-process guarantees), see \`docs/pglite-feature-parity.md\`.
471
-
472
- ### Diagnostics / escape hatches (env vars, only if you know why)
473
- The 0.7.6→0.7.14 work cut recall latency from 11s to ~300ms. The 0.8.x work
474
- added the write-path rewrite (per-write 300+ ms → under 10ms) and PGlite
475
- parity tuning. Each optimization is gated by an env-var so it can be disabled
476
- for A/B testing if a regression appears in your workload:
477
-
478
- Recall pipeline (0.7.x):
479
- - \`AWM_DISABLE_POOL_FILTER=1\` — disables the candidate pool reduction
480
- pre-filter in recall. Reverts to scoring all active candidates.
481
- - \`AWM_DISABLE_SLIM_CACHE=1\` disables the in-memory slim cache.
482
- Reverts to per-recall SQL fetch + Buffer→Float32Array conversion.
483
- - \`AWM_DISABLE_RERANK_SKIP=1\`disables the cross-encoder skip on
484
- clear-winner queries. Forces every recall through the reranker.
485
- - \`AWM_DISABLE_EXPANSION_CACHE=1\` disables the query expansion skip
486
- heuristic + LRU cache. Forces every recall through flan-t5-small.
487
-
488
- Write pipeline + lifecycle (0.8.x):
489
- - \`AWM_REINFORCE_MAX_CONTENT_LEN=1500\` max chars an engram's content
490
- can grow to via merge-on-reinforce (drop-oldest on overflow). Higher =
491
- preserves more reinforced detail; lower = leaner recall output.
492
- - \`AWM_REINFORCE_MERGE_CONTENT=0\` disable content merge on reinforce.
493
- Reverts to pre-0.8.5 behavior (discard new content, only bump confidence).
494
- - \`AWM_NOVELTY_EMBED=0\` disable the cosine channel in novelty
495
- computation. BM25-only fallback. Reverts to pre-0.8.5 novelty.
496
- - \`AWM_GRANULARITY_COMPACT_LEN=200\` char budget for query-aware snippet
497
- in \`granularity: 'compact'\` mode.
498
- - \`AWM_GRANULARITY_FULL_LEN=1000\` char budget for the top result in
499
- \`granularity: 'auto'\` mode when there's a clear winner.
500
-
501
- PGlite backend (0.8.x):
502
- - \`AWM_PGLITE_BM25_M=1\` multiplier on PGlite \`ts_rank_cd\` to calibrate
503
- against SQLite FTS5 BM25 distribution. M=1 (default) is passthrough;
504
- higher M boosts PGlite scores at the cost of recall-ranking precision
505
- (see CHANGELOG 0.8.5 follow-up).
506
- - \`AWM_IVFFLAT_PROBES=5\` pgvector ivfflat probes per query. Higher =
507
- more accurate, slower.
508
-
509
- In production, leave these all unset. Use only when diagnosing a suspected
510
- recall-quality regression.
511
- `.trimStart();
1
+ // Copyright 2026 Robert Winter / Complete Ideas
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ /**
5
+ * Shared utilities for CLI adapters.
6
+ *
7
+ * Extracted from the original setup() in cli.ts — path resolution, secrets,
8
+ * environment variables, MCP command building, and the AWM instruction snippet.
9
+ */
10
+
11
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
12
+ import { resolve, join, dirname, basename } from 'node:path';
13
+ import { randomBytes } from 'node:crypto';
14
+ import { homedir as osHomedir } from 'node:os';
15
+ import { fileURLToPath } from 'node:url';
16
+ import type { SetupContext } from './types.js';
17
+
18
+ const __filename = fileURLToPath(import.meta.url);
19
+ const __dirname = dirname(__filename);
20
+
21
+ /** Resolve the AWM package root (where src/ and dist/ live). */
22
+ export function resolvePackageRoot(): string {
23
+ // __dirname is src/adapters/ at dev time, dist/adapters/ at build time
24
+ return resolve(__dirname, '..', '..');
25
+ }
26
+
27
+ /** Resolve the database path — default to <packageRoot>/data/memory.db. */
28
+ export function resolveDbPath(packageRoot: string, explicit?: string | null): string {
29
+ const dbPath = explicit ?? join(packageRoot, 'data', 'memory.db');
30
+ const dbDir = dirname(dbPath);
31
+ if (!existsSync(dbDir)) {
32
+ mkdirSync(dbDir, { recursive: true });
33
+ }
34
+ return dbPath;
35
+ }
36
+
37
+ /** Read or generate the hook secret token. */
38
+ export function resolveHookSecret(dbPath: string): string {
39
+ const secretPath = join(dirname(dbPath), '.awm-hook-secret');
40
+ if (existsSync(secretPath)) {
41
+ const existing = readFileSync(secretPath, 'utf-8').trim();
42
+ if (existing) return existing;
43
+ }
44
+ const secret = randomBytes(32).toString('hex');
45
+ mkdirSync(dirname(secretPath), { recursive: true });
46
+ writeFileSync(secretPath, secret + '\n');
47
+ return secret;
48
+ }
49
+
50
+ /** Build environment variables for the MCP server process. */
51
+ export function buildEnvVars(
52
+ dbPath: string,
53
+ agentId: string,
54
+ hookPort: string,
55
+ hookSecret: string,
56
+ isWindows: boolean,
57
+ ): Record<string, string> {
58
+ return {
59
+ AWM_DB_PATH: isWindows ? dbPath.replace(/\\/g, '/') : dbPath,
60
+ AWM_AGENT_ID: agentId,
61
+ AWM_HOOK_PORT: hookPort,
62
+ AWM_HOOK_SECRET: hookSecret,
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Resolve the MCP server command + args.
68
+ *
69
+ * Prefers absolute path to dist/mcp.js (works from any cwd).
70
+ * Falls back to npx tsx src/mcp.ts for dev mode.
71
+ */
72
+ export function resolveMcpCommand(ctx: SetupContext): {
73
+ command: string;
74
+ args: string[];
75
+ } {
76
+ if (ctx.hasDist) {
77
+ return {
78
+ command: 'node',
79
+ args: [ctx.mcpDist.replace(/\\/g, '/')],
80
+ };
81
+ }
82
+ // Dev fallback
83
+ if (ctx.isWindows) {
84
+ return {
85
+ command: 'cmd',
86
+ args: ['/c', 'npx', 'tsx', ctx.mcpScript.replace(/\\/g, '/')],
87
+ };
88
+ }
89
+ return {
90
+ command: 'npx',
91
+ args: ['tsx', ctx.mcpScript],
92
+ };
93
+ }
94
+
95
+ /** Build a full SetupContext from parsed CLI flags. */
96
+ export function buildSetupContext(opts: {
97
+ agentId?: string;
98
+ dbPath?: string | null;
99
+ isGlobal: boolean;
100
+ hookPort: string;
101
+ }): SetupContext {
102
+ const cwd = process.cwd();
103
+ const projectName = basename(cwd).toLowerCase().replace(/[^a-z0-9-]/g, '-');
104
+ const packageRoot = resolvePackageRoot();
105
+ const mcpScript = join(packageRoot, 'src', 'mcp.ts');
106
+ const mcpDist = join(packageRoot, 'dist', 'mcp.js');
107
+ const hasDist = existsSync(mcpDist);
108
+ const isWindows = process.platform === 'win32';
109
+
110
+ const agentId = opts.agentId ?? (opts.isGlobal ? 'claude' : projectName);
111
+ const dbPath = resolveDbPath(packageRoot, opts.dbPath);
112
+ const hookSecret = resolveHookSecret(dbPath);
113
+ const envVars = buildEnvVars(dbPath, agentId, opts.hookPort, hookSecret, isWindows);
114
+
115
+ return {
116
+ cwd,
117
+ projectName,
118
+ agentId,
119
+ dbPath,
120
+ packageRoot,
121
+ mcpDist,
122
+ mcpScript,
123
+ hasDist,
124
+ hookSecret,
125
+ hookPort: opts.hookPort,
126
+ isGlobal: opts.isGlobal,
127
+ isWindows,
128
+ envVars,
129
+ };
130
+ }
131
+
132
+ /** Home directory. */
133
+ export function homedir(): string {
134
+ return osHomedir();
135
+ }
136
+
137
+ // ─── Instruction content ────────────────────────────────
138
+
139
+ /**
140
+ * Core AWM instruction snippet — shared across all adapters.
141
+ * Each adapter wraps this in the appropriate file format.
142
+ */
143
+ /**
144
+ * Upsert the AWM section into an instruction file (CLAUDE.md, AGENTS.md, .cursorrules).
145
+ *
146
+ * Behavior:
147
+ * - File doesn't exist -> create with title + AWM_INSTRUCTION_CONTENT
148
+ * - Section absent -> append
149
+ * - Section present + identical -> skip
150
+ * - Section present + stale -> REPLACE in place, preserve content above/below
151
+ *
152
+ * Section is bounded by `## Memory (AWM)` (with optional trailing modifier) at the
153
+ * start, and the next `## ` heading or EOF at the end.
154
+ *
155
+ * Returns a short human-readable status string for the setup command output.
156
+ */
157
+ export function upsertAwmSection(
158
+ filePath: string,
159
+ newContent: string,
160
+ options: { titleIfNew?: string; suffix?: string } = {},
161
+ ): string {
162
+ const fname = basename(filePath);
163
+ const suffix = options.suffix ?? '';
164
+
165
+ if (!existsSync(filePath)) {
166
+ const title = options.titleIfNew ?? `# ${basename(dirname(filePath))}`;
167
+ mkdirSync(dirname(filePath), { recursive: true });
168
+ writeFileSync(filePath, `${title}\n\n${newContent}${suffix}`);
169
+ return `${fname}: created with AWM workflow section`;
170
+ }
171
+
172
+ const existing = readFileSync(filePath, 'utf-8');
173
+
174
+ // Find section bounds: `## Memory (AWM)` (possibly with ` — MANDATORY` etc.) until next `## ` or EOF
175
+ const startRegex = /^## Memory \(AWM\)[^\n]*$/m;
176
+ const startMatch = startRegex.exec(existing);
177
+
178
+ if (!startMatch) {
179
+ // Section not present — append
180
+ writeFileSync(filePath, existing.trimEnd() + '\n\n' + newContent + suffix);
181
+ return `${fname}: appended AWM workflow section`;
182
+ }
183
+
184
+ // Find end: next `## ` heading after the section start, or EOF
185
+ const afterStart = startMatch.index + startMatch[0].length;
186
+ const nextHeadingRegex = /^## (?!Memory \(AWM\))/m;
187
+ nextHeadingRegex.lastIndex = afterStart;
188
+ const tail = existing.slice(afterStart);
189
+ const nextMatch = nextHeadingRegex.exec(tail);
190
+ const sectionEnd = nextMatch ? afterStart + nextMatch.index : existing.length;
191
+
192
+ const currentSection = existing.slice(startMatch.index, sectionEnd).trimEnd();
193
+ const desiredSection = (newContent + suffix).trimEnd();
194
+
195
+ if (currentSection === desiredSection) {
196
+ return `${fname}: AWM section already up-to-date (skipped)`;
197
+ }
198
+
199
+ const before = existing.slice(0, startMatch.index).trimEnd();
200
+ const after = existing.slice(sectionEnd).replace(/^\s*\n/, '');
201
+ const rebuilt =
202
+ (before ? before + '\n\n' : '') +
203
+ desiredSection +
204
+ (after ? '\n\n' + after : '\n');
205
+ writeFileSync(filePath, rebuilt);
206
+ return `${fname}: AWM section updated (preserved surrounding content)`;
207
+ }
208
+
209
+ export const AWM_INSTRUCTION_CONTENT = `
210
+ ## Memory (AWM) — MANDATORY
211
+
212
+ **AWM is THE memory system.** Use it via the \`agent-working-memory\` MCP server
213
+ (preferred) or HTTP at \`http://127.0.0.1:8400\` (fallback). The file-based
214
+ auto-memory at \`~/.claude/projects/.../memory/*.md\` is a LEGACY bootstrap path —
215
+ **do not write new memories to it.** All persistent knowledge goes through AWM.
216
+
217
+ If MCP tools aren't loaded at session start, use ToolSearch with
218
+ \`select:mcp__agent-working-memory__memory_recall,mcp__agent-working-memory__memory_write\`
219
+ to load them. If the MCP server isn't responsive, restart with \`/mcp\` or use
220
+ the HTTP endpoints (\`POST /memory/write-batch\`, \`POST /memory/activate\`)
221
+ directly — but **DO NOT fall back to markdown files**. Files drift the moment
222
+ you write them; AWM stays current because every agent reads + writes the same store.
223
+
224
+ ### Lifecycle (always do these, in this order)
225
+ 1. **Session start**: call \`memory_restore\` to recover previous context. If it reports the
226
+ store is empty/new (or recall keeps returning nothing), **warm-start first**: recall the
227
+ \`onboard a new project\` skill and follow it — or call \`onboard_scan\` on the project's
228
+ docs/repo, refine the candidates, run \`onboard_questions\`, and save the good ones with
229
+ \`memory_write\` (canonical). A cold store is nearly useless until it's seeded.
230
+ 2. **Starting a task**: call \`memory_task_begin\` (checkpoints + recalls relevant memories).
231
+ 3. **During work**: call \`memory_recall\` BEFORE stating any fact, BEFORE searching
232
+ the filesystem, BEFORE making architectural decisions. Recall is ~300ms cheaper
233
+ than one filesystem search.
234
+ 4. **As you learn things**: call \`memory_write\` proactively. Don't batch.
235
+ 5. **Finishing a task**: call \`memory_task_end\` with a summary.
236
+ 6. **Auto-checkpoint** is handled by hooks (compaction, session-end, 15-min timer). No action needed.
237
+
238
+ ### Write memory when:
239
+ - A project decision is made or changed
240
+ - A root cause is discovered after debugging
241
+ - A reusable implementation pattern is established
242
+ - A user preference, constraint, or requirement is clarified
243
+ - A prior assumption is found to be wrong
244
+ - A significant piece of work is completed
245
+
246
+ ### Writing for recall (the highest-leverage section)
247
+ A memory's recall quality is set the moment you write it. AWM is fast at
248
+ finding what's findable — but if the write is shaped wrong, no retriever
249
+ can rescue it. Be slightly more verbose at the front than feels natural:
250
+ the first 1-2 sentences are what BM25, the embedding model, and concept
251
+ extraction all see most strongly.
252
+
253
+ - **Lead with the rule or fact.** Don't open with context or backstory.
254
+ "Don't mock the database in integration tests." comes first; the reason
255
+ comes second. Recall scans the head of the body, not the tail.
256
+ - **Pick the most specific topic.** Not \`auth\` \`auth-magic-link-rate-limit\`.
257
+ Topic is a hard filter at recall time. Generic topics hide the memory in
258
+ a noisy bucket where it competes with everything else in the area.
259
+ - **Include 2+ retrievable identifiers.** File paths, function names, table
260
+ columns, ticket IDs, exact error strings, the literal terms a future query
261
+ will use. \`AccountingService.closePeriod()\` beats "the accounting code."
262
+ \`tblMemberDetails.activation_date\` beats "the activation column."
263
+ \`schema/072-period-close.sql\` beats "the migration."
264
+ - **Write in the vocabulary of the future question.** When you imagine asking
265
+ this in three months, what nouns will you use? Use those nouns. Don't
266
+ paraphrase the user's domain language into your own neutral summary.
267
+ - **Reserve canonical for stable invariants.** Decisions, requirements,
268
+ hard facts, cross-agent shared context. Working class (default) is correct
269
+ for findings, observations, and progress notes. The canonical floor is
270
+ 0.7 salienceoverusing it pollutes the canonical layer and the floor
271
+ loses meaning.
272
+ - **Include the why for feedback memories.** A rule without a reason can't
273
+ be applied to edge cases. "Don't mock the database" is brittle. "Don't
274
+ mock the database — last quarter mocked tests masked a broken migration"
275
+ is portable to new situations.
276
+
277
+ ### Tagging rules (REQUIRED — AWM's prefix-tag retrieval boost depends on these)
278
+
279
+ Every \`memory_write\` should pass these structured fields. AWM stores each as a
280
+ prefix-tag like \`proj=\`, \`topic=\`, \`intent=\`, etc. and uses them for BM25
281
+ and entity-bridge boosts at recall time.
282
+
283
+ | Field | Required? | Format | Example |
284
+ |---|---|---|---|
285
+ | \`project\` | **YES** | one short word matching the current project | \`"EquiHub"\`, \`"AWM"\`, \`"USEA-Agent"\` |
286
+ | \`topic\` | **YES** | one or more lowercase area words | \`"database-migration"\`, \`"benchmarks"\` |
287
+ | \`intent\` | **YES** | one of: \`decision\` / \`finding\` / \`todo\` / \`question\` / \`context\` | \`"finding"\` |
288
+ | \`confidence_level\` | **YES** | \`verified\` (tested) / \`observed\` (read in code) / \`assumed\` (reasoning) | \`"verified"\` |
289
+ | \`source\` | recommended | \`code-reading\` / \`debugging\` / \`discussion\` / \`research\` / \`testing\` / \`observation\` | \`"testing"\` |
290
+ | \`memory_class\` | when stable | \`canonical\` (source-of-truth, 0.7 floor, never staged) / \`working\` (default) / \`ephemeral\` | \`"canonical"\` |
291
+ | \`session_id\` | recommended | current conversation ID for entity-bridge boost | autogenerated |
292
+ | \`tags\` | when applicable | extra prefix-tags for IDs and dates | \`["ticket=18360", "date=2026-05-11"]\` |
293
+
294
+ **Always add identifier tags when present in the content:**
295
+ - \`ticket=<id>\` for Freshdesk tickets
296
+ - \`member=<id>\` for member IDs
297
+ - \`horse=<id>\` for horse_member_id
298
+ - \`usef=<id>\` for USEF lookups
299
+ - \`date=YYYY-MM-DD\` for temporal anchoring (ISO format)
300
+ - \`person=<Name>\` for stakeholder quotes / decisions
301
+ - \`version=<X.Y.Z>\` for release-specific findings
302
+
303
+ ### Memory classes (controls how strictly the salience filter gates the write)
304
+ - \`memory_class: canonical\` — source-of-truth memories. Floor 0.7 salience, never staged.
305
+ Use for: user-stated decisions, project requirements, verified architectural facts,
306
+ cross-agent shared context. **In a hive (multi-agent) setup, always use \`canonical\`
307
+ for writes that other agents must be able to recall** — the default \`working\` class
308
+ may get filtered.
309
+ - \`memory_class: working\` (default) observations and findings. Salience-gated.
310
+ - \`memory_class: ephemeral\` — short-lived context that should decay quickly.
311
+
312
+ ### Salience auto-promotion (defense in depth)
313
+ The salience filter automatically promotes certain content patterns even if you forget
314
+ to set \`memory_class\` explicitly:
315
+ - **User feedback** content starting with "Robert said…", "Katherine directed…",
316
+ "Nancy decided…" etc. auto-promotes to canonical. So quoting the user verbatim
317
+ always preserves the decision.
318
+ - **Verified operational records** content with an action verb (Submitted, Finalized,
319
+ Completed, Reconciled, Triaged, Posted, Resolved, Stamped, Pushed, Deployed, Migrated,
320
+ Imported, Exported, Backfilled) plus 2+ concrete identifiers (ISO date \`YYYY-MM-DD\`,
321
+ or contextual numeric IDs like "event 18969", "ticket #18330", "USEF 341980") gets
322
+ a 0.45 salience floor. So batch summaries with real IDs survive even when topic
323
+ terms repeat.
324
+
325
+ If neither pattern applies and you want a memory to definitely survive, set
326
+ \`memory_class: canonical\` explicitly. Don't rely on auto-promotion for important writes.
327
+
328
+ ### Recall memory when:
329
+ - **BEFORE stating ANY fact about how a system works** recall first; if AWM doesn't
330
+ have it, read the code. Never guess and present it as fact.
331
+ - **BEFORE searching the filesystem**recall first; AWM is faster and has cross-session
332
+ knowledge that file search doesn't.
333
+ - Starting work on a new task or subsystem
334
+ - Re-entering code you haven't touched recently
335
+ - After a failed attempt check if there's prior knowledge
336
+ - Before refactoring or making architectural changes
337
+ - When a topic comes up that you might have prior context on
338
+
339
+ Recall is fast (~300ms typical). Use it freely.
340
+
341
+ ### Recall strategy (when one query isn't enough)
342
+ AWM's adaptive retrieval handles most query variations natively — synonym
343
+ expansion, multi-channel scoring, embedding + BM25 + reranker agreement.
344
+ A single recall is usually enough.
345
+
346
+ When it isn't:
347
+ - **If the first recall returns nothing or returns the wrong things, reformulate.**
348
+ Try a second query with different phrasing synonyms, more specific nouns,
349
+ the exact identifier from the code rather than the conceptual name. Two or
350
+ three recalls cost less than one filesystem search.
351
+ - **Use the words a domain expert would use, not generic English.** "Period
352
+ close lock" not "accounting feature"; "magic link rate limit" not "auth issue."
353
+ - **For broad exploration, pass \`mode: "exploratory"\`** — wider candidate
354
+ pool, lower precision floor. For specific lookups, leave mode unset (auto).
355
+ - **Don't ensemble more than 3 reformulations.** If three different phrasings
356
+ return nothing, the memory probably isn't there read the code instead of
357
+ burning more recalls.
358
+
359
+ ### Recall tuning (0.8.x — opt-in parameters for higher-quality recall)
360
+ Default \`memory_recall\` is tuned for the common case. The 0.8.x recall pipeline
361
+ exposes four opt-in parameters that change the cost/quality tradeoff. Use them
362
+ when the default doesn't match what you actually need.
363
+
364
+ - **\`granularity: 'compact'\`** every result carries a 200-char \`summary\`
365
+ field with a query-aware snippet (the densest window of query terms in the
366
+ content). Use this when you expect to scan 5+ results to find one — saves
367
+ ~70% of recall output tokens. The full content stays available in
368
+ \`engram.content\` if you want to drill into a specific result.
369
+ - **\`granularity: 'auto'\`** confidence-adaptive. If the top result is a clear
370
+ winner, it gets a longer summary while the rest are compact. If confidence
371
+ is uniform across results, everything is compact. Use when you don't know
372
+ in advance whether one result will dominate.
373
+ - **\`require_confidence: 0.10 | 0.25 | 0.40\`**opt-in abstention. AWM
374
+ returns \`[]\` instead of low-confidence noise. Use when you're about to ACT
375
+ on the recalled fact (grounding a decision, citing the memory verbatim,
376
+ contradicting a prior assumption). Thresholds: \`0.10\` strict only abstain
377
+ on garbage; \`0.25\` balanced; \`0.40\` aggressive prefer "I don't know"
378
+ over "best of bad." When abstention fires (empty result), treat it as a
379
+ signal either the memory genuinely isn't there (read the code) or your
380
+ query missed (reformulate). Don't retry without the threshold.
381
+ - **\`workspace: "<name>"\`** — hive-mode recall across all agents in the
382
+ workspace. Use when other agents may have written canonical knowledge you
383
+ need. Default is agent-scoped (your own memories only). Can also be set
384
+ globally via the \`AWM_WORKSPACE\` env var.
385
+
386
+ ### Keep memory fresh
387
+ - After recalling a memory, if you observe the real state is different → call
388
+ \`memory_supersede\` immediately with the corrected version.
389
+ - After using a recalled memory: call \`memory_feedback\` (useful/not-useful) so the
390
+ activation engine learns what's valuable.
391
+ - If you discover a memory is factually wrong: \`memory_retract\` to remove it.
392
+ - **If you bypass AWM (file-memory, in-context notes, "I'll just remember"), the memory
393
+ drifts out of date. The system relies on you to keep it current. This is the #1
394
+ failure mode.**
395
+
396
+ ### Cognition recipes — YOU do the thinking, AWM keeps the result (0.11.x)
397
+ AWM contains no LLM. When memory needs real thinking distilling a repeatable
398
+ procedure, reflecting on a failure AWM hands YOU a versioned recipe (prompt +
399
+ strict output shape) and you run it as a SEPARATE focused pass, then write the
400
+ result back as an ordinary memory with provenance.
401
+
402
+ - \`memory_task_end\` responses include the recipe invitations. Honor the gates:
403
+ skill-derivation only after a genuinely procedural task (3+ tool calls or a
404
+ delegated sub-task); friction-lesson only after a failure/retry/wrong assumption.
405
+ - Run each recipe as its own focused pass do NOT bundle it with other
406
+ reasoning; bundled passes reliably drop the output.
407
+ - Write back exactly per the recipe's contract: \`origin_class: 'recipe'\` +
408
+ \`recipe_id\` (e.g. \`skill-derivation@1\`), concept prefixed \`skill: \` or
409
+ \`lesson: \`. AWM validates the shape and rejects malformed or unknown-recipe
410
+ writes with the contract echoed back fix and retry, don't drop the insight.
411
+ - Re-deriving the same skill name reinforces the existing memory instead of
412
+ duplicating it, so don't fear writing a skill you may have written before.
413
+
414
+ ### Content fade — write-and-forget is safe (0.8.x)
415
+ Un-recalled engrams gradually fade their content while preserving cue pathways
416
+ (concept + tags + embedding stay intact). This is Paper 1 — storage
417
+ degradation. Practical implications:
418
+
419
+ - **Don't manually purge memories** to "save space." The system already
420
+ compresses unused content. Old memories stay findable via cue match even
421
+ when their body has decayed.
422
+ - **Don't over-pin with \`memory_class: canonical\`** to fight fade. Canonical
423
+ only changes salience gating at write time, not fade behavior. Fade
424
+ affects un-recalled engrams of any class.
425
+ - **Recall keeps content alive.** Every recall touches the engram and resets
426
+ its fade clock. Frequently-recalled memories stay full-fidelity automatically.
427
+ - **Supersede is the right tool for stale facts.** When you observe a memory
428
+ is outdated, call \`memory_supersede\` — the new version inherits the old
429
+ one's coherent associations (counter-narrative replacement, 0.8.x) so cue
430
+ pathways carry forward to the replacement.
431
+
432
+ ### Example — good vs bad memory_write
433
+
434
+ **BAD** (no prefix tags, vague concept, can't be recalled by future queries):
435
+ \`\`\`
436
+ memory_write(
437
+ concept="found a bug",
438
+ content="The thing I was looking at was broken so I fixed it."
439
+ )
440
+ \`\`\`
441
+
442
+ **GOOD** (rich identifiers, structured metadata, prefix tags):
443
+ \`\`\`
444
+ memory_write(
445
+ concept="EquiHub period-close BLOCKED check missing server-side",
446
+ 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.",
447
+ project="EquiHub",
448
+ topic="accounting",
449
+ intent="finding",
450
+ confidence_level="verified",
451
+ source="debugging",
452
+ memory_class="canonical",
453
+ tags=["ticket=18360", "person=Robert", "date=2026-05-11", "topic=period-close", "topic=security"]
454
+ )
455
+ \`\`\`
456
+
457
+ ### Also:
458
+ - To track work items: memory_task_add, memory_task_update, memory_task_list, memory_task_next
459
+ - AWM is shared across all agents in real time. When any agent writes or supersedes a
460
+ memory, every other agent can recall it immediately.
461
+
462
+ ### Output compression (token efficiency, output-only)
463
+ When a tool returns a LARGE STRUCTURED result you need to keep in context — a JSON
464
+ array of records, query rows, a log dump, an API response — pass it through
465
+ \`compress_output\` first. It re-encodes the data as TOON (a compact, lossless,
466
+ schema-aware tabular form of JSON), cutting ~50-65% of the tokens at no
467
+ comprehension cost. This is output-only: it never changes the data or your memories.
468
+ - Use it on big STRUCTURED outputs, not on prose. Prose is returned unchanged —
469
+ for trimming memory prose, use recall \`granularity: 'compact'\` instead.
470
+ - It returns a \`ref\`; call \`retrieve_original(ref)\` if you later need the exact
471
+ verbatim source (e.g. to hand it to another tool unchanged).
472
+ - Don't bother for small outputs it only compresses when the saving is worthwhile
473
+ and falls back to plain JSON if TOON wouldn't reproduce the data exactly.
474
+
475
+ ### Backend (SQLite vs PGlite, 0.8.x)
476
+ AWM ships two storage backends. The installer picks SQLite by default; both
477
+ are functionally equivalent for cognitive workloads, but differ in operational
478
+ guarantees:
479
+
480
+ - **SQLite** (default) embedded, **multi-process safe** via WAL mode. Best
481
+ for single-machine setups and MCP scenarios where multiple Claude Code
482
+ sessions may open the same database concurrently.
483
+ - **PGlite**embedded Postgres (WASM) with pgvector. **Single-process only**
484
+ two MCP processes against the same \`memory-pglite/\` directory will
485
+ abort the second. Pick via \`AWM_STORE_BACKEND=pglite\` and
486
+ \`AWM_DB_PATH=path/to/memory-pglite\`.
487
+ - **Auto-detect** — if \`AWM_DB_PATH\` points to a directory that already
488
+ exists, AWM detects PGlite; a file → SQLite. No explicit
489
+ \`AWM_STORE_BACKEND\` needed when an existing DB is present.
490
+
491
+ For the comparison table (recall quality parity, BM25 vs \`ts_rank_cd\`,
492
+ multi-process guarantees), see \`docs/pglite-feature-parity.md\`.
493
+
494
+ ### Diagnostics / escape hatches (env vars, only if you know why)
495
+ The 0.7.6→0.7.14 work cut recall latency from 11s to ~300ms. The 0.8.x work
496
+ added the write-path rewrite (per-write 300+ ms under 10ms) and PGlite
497
+ parity tuning. Each optimization is gated by an env-var so it can be disabled
498
+ for A/B testing if a regression appears in your workload:
499
+
500
+ Recall pipeline (0.7.x):
501
+ - \`AWM_DISABLE_POOL_FILTER=1\` — disables the candidate pool reduction
502
+ pre-filter in recall. Reverts to scoring all active candidates.
503
+ - \`AWM_SLOW_WRITE_MS\` slow-write telemetry threshold in ms (default 250;
504
+ 0 disables the always-on slow-write stderr line).
505
+ - \`memory_whoami\` (MCP) / \`GET /whoami\` — identify the instance (agent, mode,
506
+ backend, store path, code provenance, sibling agent spaces) when unsure
507
+ which AWM you are talking to.
508
+ - \`AWM_DISABLE_SLIM_CACHE=1\` — disables the in-memory slim cache.
509
+ Reverts to per-recall SQL fetch + Buffer→Float32Array conversion.
510
+ - \`AWM_DISABLE_RERANK_SKIP=1\` — disables the cross-encoder skip on
511
+ clear-winner queries. Forces every recall through the reranker.
512
+ - \`AWM_DISABLE_EXPANSION_CACHE=1\` — disables the query expansion skip
513
+ heuristic + LRU cache. Forces every recall through flan-t5-small.
514
+
515
+ Write pipeline + lifecycle (0.8.x):
516
+ - \`AWM_REINFORCE_MAX_CONTENT_LEN=1500\` — max chars an engram's content
517
+ can grow to via merge-on-reinforce (drop-oldest on overflow). Higher =
518
+ preserves more reinforced detail; lower = leaner recall output.
519
+ - \`AWM_REINFORCE_MERGE_CONTENT=0\` — disable content merge on reinforce.
520
+ Reverts to pre-0.8.5 behavior (discard new content, only bump confidence).
521
+ - \`AWM_NOVELTY_EMBED=0\` — disable the cosine channel in novelty
522
+ computation. BM25-only fallback. Reverts to pre-0.8.5 novelty.
523
+ - \`AWM_GRANULARITY_COMPACT_LEN=200\` — char budget for query-aware snippet
524
+ in \`granularity: 'compact'\` mode.
525
+ - \`AWM_GRANULARITY_FULL_LEN=1000\` — char budget for the top result in
526
+ \`granularity: 'auto'\` mode when there's a clear winner.
527
+
528
+ PGlite backend (0.8.x):
529
+ - \`AWM_PGLITE_BM25_M=1\` — multiplier on PGlite \`ts_rank_cd\` to calibrate
530
+ against SQLite FTS5 BM25 distribution. M=1 (default) is passthrough;
531
+ higher M boosts PGlite scores at the cost of recall-ranking precision
532
+ (see CHANGELOG 0.8.5 follow-up).
533
+ - \`AWM_IVFFLAT_PROBES=5\` — pgvector ivfflat probes per query. Higher =
534
+ more accurate, slower.
535
+
536
+ In production, leave these all unset. Use only when diagnosing a suspected
537
+ recall-quality regression.
538
+ `.trimStart();