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.
- package/dist/adapters/claude-code.d.ts.map +1 -1
- package/dist/adapters/claude-code.js +2 -16
- package/dist/adapters/claude-code.js.map +1 -1
- package/dist/adapters/codex.d.ts.map +1 -1
- package/dist/adapters/codex.js +2 -11
- package/dist/adapters/codex.js.map +1 -1
- package/dist/adapters/common.d.ts +18 -0
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +127 -14
- package/dist/adapters/common.js.map +1 -1
- package/dist/adapters/cursor.d.ts.map +1 -1
- package/dist/adapters/cursor.js +2 -15
- package/dist/adapters/cursor.js.map +1 -1
- package/dist/adapters/http.d.ts.map +1 -1
- package/dist/adapters/http.js +6 -12
- package/dist/adapters/http.js.map +1 -1
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +45 -57
- package/dist/api/routes.js.map +1 -1
- package/dist/cli.js +103 -103
- package/dist/core/write-pipeline.d.ts +120 -0
- package/dist/core/write-pipeline.d.ts.map +1 -0
- package/dist/core/write-pipeline.js +236 -0
- package/dist/core/write-pipeline.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/mcp.js +107 -178
- package/dist/mcp.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/claude-code.ts +2 -18
- package/src/adapters/codex.ts +2 -12
- package/src/adapters/common.ts +141 -14
- package/src/adapters/cursor.ts +2 -17
- package/src/adapters/http.ts +5 -12
- package/src/api/routes.ts +714 -723
- package/src/cli.ts +719 -719
- package/src/core/write-pipeline.ts +343 -0
- package/src/index.ts +212 -212
- 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
|
-
|
|
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 {
|
package/src/adapters/codex.ts
CHANGED
|
@@ -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
|
-
|
|
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 {
|
package/src/adapters/common.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
###
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
- \`
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
package/src/adapters/cursor.ts
CHANGED
|
@@ -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
|
-
|
|
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 {
|
package/src/adapters/http.ts
CHANGED
|
@@ -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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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 {
|