agent-working-memory 0.7.16 → 0.7.17

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 (38) hide show
  1. package/dist/adapters/claude-code.d.ts.map +1 -1
  2. package/dist/adapters/claude-code.js +2 -16
  3. package/dist/adapters/claude-code.js.map +1 -1
  4. package/dist/adapters/codex.d.ts.map +1 -1
  5. package/dist/adapters/codex.js +2 -11
  6. package/dist/adapters/codex.js.map +1 -1
  7. package/dist/adapters/common.d.ts +18 -0
  8. package/dist/adapters/common.d.ts.map +1 -1
  9. package/dist/adapters/common.js +127 -14
  10. package/dist/adapters/common.js.map +1 -1
  11. package/dist/adapters/cursor.d.ts.map +1 -1
  12. package/dist/adapters/cursor.js +2 -15
  13. package/dist/adapters/cursor.js.map +1 -1
  14. package/dist/adapters/http.d.ts.map +1 -1
  15. package/dist/adapters/http.js +6 -12
  16. package/dist/adapters/http.js.map +1 -1
  17. package/dist/api/routes.d.ts.map +1 -1
  18. package/dist/api/routes.js +45 -57
  19. package/dist/api/routes.js.map +1 -1
  20. package/dist/cli.js +103 -103
  21. package/dist/core/write-pipeline.d.ts +120 -0
  22. package/dist/core/write-pipeline.d.ts.map +1 -0
  23. package/dist/core/write-pipeline.js +236 -0
  24. package/dist/core/write-pipeline.js.map +1 -0
  25. package/dist/index.js +1 -1
  26. package/dist/mcp.js +107 -178
  27. package/dist/mcp.js.map +1 -1
  28. package/package.json +1 -1
  29. package/src/adapters/claude-code.ts +2 -18
  30. package/src/adapters/codex.ts +2 -12
  31. package/src/adapters/common.ts +141 -14
  32. package/src/adapters/cursor.ts +2 -17
  33. package/src/adapters/http.ts +5 -12
  34. package/src/api/routes.ts +714 -723
  35. package/src/cli.ts +719 -719
  36. package/src/core/write-pipeline.ts +343 -0
  37. package/src/index.ts +212 -212
  38. package/src/mcp.ts +1121 -1192
@@ -11,7 +11,7 @@
11
11
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
12
12
  import { join, dirname, basename } from 'node:path';
13
13
  import type { CLIAdapter, SetupContext, DiagnosticResult } from './types.js';
14
- import { resolveMcpCommand, homedir, AWM_INSTRUCTION_CONTENT } from './common.js';
14
+ import { resolveMcpCommand, homedir, AWM_INSTRUCTION_CONTENT, upsertAwmSection } from './common.js';
15
15
 
16
16
  const adapter: CLIAdapter = {
17
17
  id: 'claude-code',
@@ -56,24 +56,8 @@ const adapter: CLIAdapter = {
56
56
 
57
57
  if (skip) return 'CLAUDE.md: skipped (--no-instructions)';
58
58
 
59
- // Ensure parent directory exists
60
- const dir = dirname(claudeMdPath);
61
- if (!existsSync(dir)) {
62
- mkdirSync(dir, { recursive: true });
63
- }
64
-
65
- if (existsSync(claudeMdPath)) {
66
- const content = readFileSync(claudeMdPath, 'utf-8');
67
- if (content.includes('## Memory (AWM)')) {
68
- return 'CLAUDE.md: already has AWM section (skipped)';
69
- }
70
- writeFileSync(claudeMdPath, content.trimEnd() + '\n\n' + AWM_INSTRUCTION_CONTENT);
71
- return 'CLAUDE.md: appended AWM workflow section';
72
- }
73
-
74
59
  const title = ctx.isGlobal ? '# Global Instructions' : `# ${basename(ctx.cwd)}`;
75
- writeFileSync(claudeMdPath, `${title}\n\n${AWM_INSTRUCTION_CONTENT}`);
76
- return 'CLAUDE.md: created with AWM workflow section';
60
+ return upsertAwmSection(claudeMdPath, AWM_INSTRUCTION_CONTENT, { titleIfNew: title });
77
61
  },
78
62
 
79
63
  writeHooks(ctx: SetupContext, skip: boolean): string {
@@ -11,7 +11,7 @@
11
11
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
12
12
  import { join, dirname, basename } from 'node:path';
13
13
  import type { CLIAdapter, SetupContext, DiagnosticResult } from './types.js';
14
- import { resolveMcpCommand, homedir, AWM_INSTRUCTION_CONTENT } from './common.js';
14
+ import { resolveMcpCommand, homedir, AWM_INSTRUCTION_CONTENT, upsertAwmSection } from './common.js';
15
15
 
16
16
  // ─── Minimal TOML read/write ──────────────────────────
17
17
  // Only handles the flat structure Codex uses: [section.name] with key = "value" or key = ["array"]
@@ -165,18 +165,8 @@ const adapter: CLIAdapter = {
165
165
 
166
166
  if (skip) return 'AGENTS.md: skipped (--no-instructions)';
167
167
 
168
- if (existsSync(agentsMdPath)) {
169
- const content = readFileSync(agentsMdPath, 'utf-8');
170
- if (content.includes('## Memory (AWM)')) {
171
- return 'AGENTS.md: already has AWM section (skipped)';
172
- }
173
- writeFileSync(agentsMdPath, content.trimEnd() + '\n\n' + AWM_INSTRUCTION_CONTENT);
174
- return 'AGENTS.md: appended AWM workflow section';
175
- }
176
-
177
168
  const title = `# ${basename(ctx.cwd)} — Agent Instructions`;
178
- writeFileSync(agentsMdPath, `${title}\n\n${AWM_INSTRUCTION_CONTENT}`);
179
- return 'AGENTS.md: created with AWM workflow section';
169
+ return upsertAwmSection(agentsMdPath, AWM_INSTRUCTION_CONTENT, { titleIfNew: title });
180
170
  },
181
171
 
182
172
  writeHooks(_ctx: SetupContext, _skip: boolean): string {
@@ -140,15 +140,96 @@ export function homedir(): string {
140
140
  * Core AWM instruction snippet — shared across all adapters.
141
141
  * Each adapter wraps this in the appropriate file format.
142
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
+
143
209
  export const AWM_INSTRUCTION_CONTENT = `
144
- ## Memory (AWM)
145
- You have persistent memory via the agent-working-memory MCP server.
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.
146
223
 
147
- ### Lifecycle (always do these)
148
- - Session start: call memory_restore to recover previous context
149
- - Starting a task: call memory_task_begin (checkpoints + recalls relevant memories)
150
- - Finishing a task: call memory_task_end with a summary
151
- - Auto-checkpoint: hooks handle compaction, session end, and 15-min timer (no action needed)
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.
152
233
 
153
234
  ### Write memory when:
154
235
  - A project decision is made or changed
@@ -189,13 +270,31 @@ extraction all see most strongly.
189
270
  mock the database — last quarter mocked tests masked a broken migration"
190
271
  is portable to new situations.
191
272
 
192
- ### When writing, include metadata for better recall:
193
- - \`project\`: current project name (e.g., "EquiHub", "AWM")
194
- - \`topic\`: subject area (e.g., "database-migration", "auth-flow")
195
- - \`session_id\`: conversation grouping ID associates related memories
196
- - \`source\`: how acquired (code-reading, debugging, discussion, research, testing, observation)
197
- - \`confidence_level\`: verified (tested), observed (read in code), assumed (reasoning)
198
- - \`intent\`: decision, finding, todo, question, or context
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
199
298
 
200
299
  ### Memory classes (controls how strictly the salience filter gates the write)
201
300
  - \`memory_class: canonical\` — source-of-truth memories. Floor 0.7 salience, never staged.
@@ -259,6 +358,34 @@ When it isn't:
259
358
  - After using a recalled memory: call \`memory_feedback\` (useful/not-useful) so the
260
359
  activation engine learns what's valuable.
261
360
  - If you discover a memory is factually wrong: \`memory_retract\` to remove it.
361
+ - **If you bypass AWM (file-memory, in-context notes, "I'll just remember"), the memory
362
+ drifts out of date. The system relies on you to keep it current. This is the #1
363
+ failure mode.**
364
+
365
+ ### Example — good vs bad memory_write
366
+
367
+ **BAD** (no prefix tags, vague concept, can't be recalled by future queries):
368
+ \`\`\`
369
+ memory_write(
370
+ concept="found a bug",
371
+ content="The thing I was looking at was broken so I fixed it."
372
+ )
373
+ \`\`\`
374
+
375
+ **GOOD** (rich identifiers, structured metadata, prefix tags):
376
+ \`\`\`
377
+ memory_write(
378
+ concept="EquiHub period-close BLOCKED check missing server-side",
379
+ 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.",
380
+ project="EquiHub",
381
+ topic="accounting",
382
+ intent="finding",
383
+ confidence_level="verified",
384
+ source="debugging",
385
+ memory_class="canonical",
386
+ tags=["ticket=18360", "person=Robert", "date=2026-05-11", "topic=period-close", "topic=security"]
387
+ )
388
+ \`\`\`
262
389
 
263
390
  ### Also:
264
391
  - To track work items: memory_task_add, memory_task_update, memory_task_list, memory_task_next
@@ -11,7 +11,7 @@
11
11
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
12
12
  import { join, dirname, basename } from 'node:path';
13
13
  import type { CLIAdapter, SetupContext, DiagnosticResult } from './types.js';
14
- import { resolveMcpCommand, homedir, AWM_INSTRUCTION_CONTENT } from './common.js';
14
+ import { resolveMcpCommand, homedir, AWM_INSTRUCTION_CONTENT, upsertAwmSection } from './common.js';
15
15
 
16
16
  const adapter: CLIAdapter = {
17
17
  id: 'cursor',
@@ -54,22 +54,7 @@ const adapter: CLIAdapter = {
54
54
 
55
55
  if (skip) return '.cursorrules: skipped (--no-instructions)';
56
56
 
57
- const dir = dirname(rulesPath);
58
- if (!existsSync(dir)) {
59
- mkdirSync(dir, { recursive: true });
60
- }
61
-
62
- if (existsSync(rulesPath)) {
63
- const content = readFileSync(rulesPath, 'utf-8');
64
- if (content.includes('## Memory (AWM)')) {
65
- return '.cursorrules: already has AWM section (skipped)';
66
- }
67
- writeFileSync(rulesPath, content.trimEnd() + '\n\n' + AWM_INSTRUCTION_CONTENT);
68
- return '.cursorrules: appended AWM workflow section';
69
- }
70
-
71
- writeFileSync(rulesPath, AWM_INSTRUCTION_CONTENT);
72
- return '.cursorrules: created with AWM workflow section';
57
+ return upsertAwmSection(rulesPath, AWM_INSTRUCTION_CONTENT, { titleIfNew: '# Agent Working Memory' });
73
58
  },
74
59
 
75
60
  writeHooks(_ctx: SetupContext, _skip: boolean): string {
@@ -11,7 +11,7 @@
11
11
  import { readFileSync, writeFileSync, existsSync } from 'node:fs';
12
12
  import { join } from 'node:path';
13
13
  import type { CLIAdapter, SetupContext, DiagnosticResult } from './types.js';
14
- import { AWM_INSTRUCTION_CONTENT } from './common.js';
14
+ import { AWM_INSTRUCTION_CONTENT, upsertAwmSection } from './common.js';
15
15
 
16
16
  const HTTP_ADDENDUM = `
17
17
  ### HTTP API (for tools without MCP support)
@@ -53,17 +53,10 @@ const adapter: CLIAdapter = {
53
53
 
54
54
  if (skip) return 'AWM-INSTRUCTIONS.md: skipped (--no-instructions)';
55
55
 
56
- if (existsSync(instrPath)) {
57
- const content = readFileSync(instrPath, 'utf-8');
58
- if (content.includes('## Memory (AWM)')) {
59
- return 'AWM-INSTRUCTIONS.md: already has AWM section (skipped)';
60
- }
61
- writeFileSync(instrPath, content.trimEnd() + '\n\n' + AWM_INSTRUCTION_CONTENT + HTTP_ADDENDUM);
62
- return 'AWM-INSTRUCTIONS.md: appended AWM + HTTP API section';
63
- }
64
-
65
- writeFileSync(instrPath, `# Agent Working Memory\n\n${AWM_INSTRUCTION_CONTENT}${HTTP_ADDENDUM}`);
66
- return 'AWM-INSTRUCTIONS.md: created with AWM + HTTP API section';
56
+ return upsertAwmSection(instrPath, AWM_INSTRUCTION_CONTENT, {
57
+ titleIfNew: '# Agent Working Memory',
58
+ suffix: HTTP_ADDENDUM,
59
+ });
67
60
  },
68
61
 
69
62
  writeHooks(_ctx: SetupContext, _skip: boolean): string {