agent-working-memory 0.8.7 → 0.8.8

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/README.md CHANGED
@@ -659,6 +659,47 @@ callers keep working without modification. Full validation at the milestone:
659
659
 
660
660
  See [CHANGELOG.md](CHANGELOG.md) for full details.
661
661
 
662
+ ## Integrations
663
+
664
+ AWM is a standard MCP server, so it plugs into any MCP-capable agent host with
665
+ **no adapter code** — the same server Claude Code uses. Point two hosts at the
666
+ same `AWM_DB_PATH` (with a shared `AWM_AGENT_ID`/`AWM_WORKSPACE`) and they share
667
+ one cognitive memory.
668
+
669
+ ### Hermes Agent (Nous Research)
670
+
671
+ 1. Make AWM available where Hermes runs (e.g. a derived Docker image — the
672
+ Hermes image already bundles Node):
673
+
674
+ ```dockerfile
675
+ FROM hermes-agent:local
676
+ USER root
677
+ RUN npm install -g agent-working-memory@latest
678
+ ENV HF_HOME=/opt/data/.cache/huggingface
679
+ ```
680
+
681
+ 2. Register it in `~/.hermes/config.yaml`:
682
+
683
+ ```yaml
684
+ mcp_servers:
685
+ awm:
686
+ command: node
687
+ args: ["/usr/local/lib/node_modules/agent-working-memory/dist/mcp.js"]
688
+ env:
689
+ AWM_AGENT_ID: hermes
690
+ AWM_DB_PATH: /opt/data/awm/hermes.db # on a persistent volume
691
+ HF_HOME: /opt/data/.cache/huggingface
692
+ timeout: 600 # first call downloads the embedder
693
+ ```
694
+
695
+ 3. AWM's tools appear to the agent as `mcp_awm_memory_write`,
696
+ `mcp_awm_memory_recall`, etc. Works with any Hermes model provider
697
+ (verified on Anthropic and Azure `gpt-5-4-mini`).
698
+
699
+ Full recipe — model-provider examples, the Azure GPT-5.x `/openai/v1` note, and
700
+ gotchas (incl. the Windows CRLF/s6 clone fix) — is in
701
+ [docs/integrations/hermes.md](docs/integrations/hermes.md).
702
+
662
703
  ## Project Status
663
704
 
664
705
  AWM is in active development (v0.8.5). The core memory pipeline, consolidation
@@ -667,6 +708,7 @@ daily in production coding workflows.
667
708
 
668
709
  - Core retrieval and consolidation: **stable**
669
710
  - MCP tools and Claude Code integration: **stable**
711
+ - Other MCP hosts (e.g. [Hermes Agent](docs/integrations/hermes.md)): **supported** — AWM drops in as an MCP memory server with no adapter code
670
712
  - Multi-agent coordination: **stable** (v0.8.1 hardening)
671
713
  - Task management: **stable**
672
714
  - Hook sidecar and auto-checkpoint: **stable**
@@ -655,9 +655,9 @@ export function registerRoutes(app, deps) {
655
655
  return reply.code(501).send({ error: 'export endpoint requires the SQLite backend' });
656
656
  }
657
657
  const db = store.getDb();
658
- let engramSql = `SELECT id, agent_id, concept, content, confidence, salience, access_count,
659
- last_accessed, created_at, salience_features, reason_codes, stage, ttl,
660
- retracted, retracted_by, retracted_at, tags
658
+ let engramSql = `SELECT id, agent_id, concept, content, confidence, salience, access_count,
659
+ last_accessed, created_at, salience_features, reason_codes, stage, ttl,
660
+ retracted, retracted_by, retracted_at, tags
661
661
  FROM engrams`;
662
662
  const conditions = [];
663
663
  const params = [];
@@ -675,7 +675,7 @@ export function registerRoutes(app, deps) {
675
675
  engramSql += ' ORDER BY created_at ASC';
676
676
  const engrams = db.prepare(engramSql).all(...params);
677
677
  const engramIds = new Set(engrams.map(e => e.id));
678
- const allAssocs = db.prepare(`SELECT id, from_engram_id, to_engram_id, weight, confidence, type, activation_count, created_at, last_activated
678
+ const allAssocs = db.prepare(`SELECT id, from_engram_id, to_engram_id, weight, confidence, type, activation_count, created_at, last_activated
679
679
  FROM associations`).all();
680
680
  const associations = allAssocs.filter(a => engramIds.has(a.from_engram_id) && engramIds.has(a.to_engram_id));
681
681
  return reply.send({
@@ -694,15 +694,15 @@ export function registerRoutes(app, deps) {
694
694
  const base = {
695
695
  status: 'ok',
696
696
  timestamp: new Date().toISOString(),
697
- version: '0.8.7',
697
+ version: '0.8.8',
698
698
  coordination: coordEnabled,
699
699
  };
700
700
  if (coordEnabled && typeof deps.store.getDb === 'function') {
701
701
  try {
702
702
  const db = deps.store.getDb();
703
- const stats = db.prepare(`SELECT
704
- (SELECT COUNT(*) FROM coord_agents WHERE status != 'dead') AS agents_alive,
705
- (SELECT COUNT(*) FROM coord_assignments WHERE status = 'pending') AS pending_tasks,
703
+ const stats = db.prepare(`SELECT
704
+ (SELECT COUNT(*) FROM coord_agents WHERE status != 'dead') AS agents_alive,
705
+ (SELECT COUNT(*) FROM coord_assignments WHERE status = 'pending') AS pending_tasks,
706
706
  (SELECT COUNT(*) FROM coord_locks) AS active_locks`).get();
707
707
  Object.assign(base, stats);
708
708
  }
package/dist/cli.js CHANGED
@@ -38,46 +38,46 @@ catch { /* No .env file */ }
38
38
  const args = process.argv.slice(2);
39
39
  const command = args[0];
40
40
  function printUsage() {
41
- console.log(`
42
- AgentWorkingMemory — Cognitive memory for AI agents
43
-
44
- Usage:
45
- awm setup [target] [options] Configure AWM for an AI CLI
46
- awm doctor [target|--all] Validate AWM integrations
47
- awm mcp Start MCP server (stdio)
48
- awm serve [--port <port>] Start HTTP API server
49
- awm health [--port <port>] Check server health
50
- awm export --db <path> [--agent <id>] [--output <file>] [--active-only]
51
- Export memories to JSON
52
- awm import <file> --db <path> [--remap-agent <id>] [--dedupe] [--dry-run]
53
- Import memories from JSON
54
- awm merge --target <db> --source <db> [--source ...]
55
- [--remap uuid=name] [--remap-all-uuids <name>]
56
- [--dedupe] [--dry-run] Merge multiple memory DBs
57
- awm migrate --from <sqlite.db> --to <pglite-dir> [--dry-run] [--verbose]
58
- Migrate SQLite DB to PGlite
59
-
60
- Setup targets:
61
- claude-code (default) .mcp.json + CLAUDE.md + hooks
62
- codex ~/.codex/config.toml + AGENTS.md
63
- cursor .cursor/mcp.json + .cursorrules
64
- http Connection info for HTTP API
65
-
66
- Setup options:
67
- --global Use global scope (recommended for claude-code)
68
- --agent-id <id> Agent identifier (default: project name)
69
- --db-path <path> Database path (default: <awm>/data/memory.db)
70
- --no-instructions Skip instruction file (CLAUDE.md, AGENTS.md, etc.)
71
- --no-claude-md Alias for --no-instructions
72
- --no-hooks Skip hook installation
73
- --hook-port PORT Sidecar port for hooks (default: 8401)
74
-
75
- Examples:
76
- awm setup --global Claude Code, global (recommended)
77
- awm setup codex Codex CLI
78
- awm setup cursor Cursor IDE
79
- awm setup http Generic HTTP integration
80
- awm doctor --all Check all configured targets
41
+ console.log(`
42
+ AgentWorkingMemory — Cognitive memory for AI agents
43
+
44
+ Usage:
45
+ awm setup [target] [options] Configure AWM for an AI CLI
46
+ awm doctor [target|--all] Validate AWM integrations
47
+ awm mcp Start MCP server (stdio)
48
+ awm serve [--port <port>] Start HTTP API server
49
+ awm health [--port <port>] Check server health
50
+ awm export --db <path> [--agent <id>] [--output <file>] [--active-only]
51
+ Export memories to JSON
52
+ awm import <file> --db <path> [--remap-agent <id>] [--dedupe] [--dry-run]
53
+ Import memories from JSON
54
+ awm merge --target <db> --source <db> [--source ...]
55
+ [--remap uuid=name] [--remap-all-uuids <name>]
56
+ [--dedupe] [--dry-run] Merge multiple memory DBs
57
+ awm migrate --from <sqlite.db> --to <pglite-dir> [--dry-run] [--verbose]
58
+ Migrate SQLite DB to PGlite
59
+
60
+ Setup targets:
61
+ claude-code (default) .mcp.json + CLAUDE.md + hooks
62
+ codex ~/.codex/config.toml + AGENTS.md
63
+ cursor .cursor/mcp.json + .cursorrules
64
+ http Connection info for HTTP API
65
+
66
+ Setup options:
67
+ --global Use global scope (recommended for claude-code)
68
+ --agent-id <id> Agent identifier (default: project name)
69
+ --db-path <path> Database path (default: <awm>/data/memory.db)
70
+ --no-instructions Skip instruction file (CLAUDE.md, AGENTS.md, etc.)
71
+ --no-claude-md Alias for --no-instructions
72
+ --no-hooks Skip hook installation
73
+ --hook-port PORT Sidecar port for hooks (default: 8401)
74
+
75
+ Examples:
76
+ awm setup --global Claude Code, global (recommended)
77
+ awm setup codex Codex CLI
78
+ awm setup cursor Cursor IDE
79
+ awm setup http Generic HTTP integration
80
+ awm doctor --all Check all configured targets
81
81
  `.trim());
82
82
  }
83
83
  // ─── SETUP ──────────────────────────────────────
@@ -135,18 +135,18 @@ async function setup() {
135
135
  const configAction = adapter.writeMcpConfig(ctx);
136
136
  const instructionsAction = adapter.writeInstructions(ctx, skipInstructions);
137
137
  const hooksAction = adapter.writeHooks(ctx, skipHooks);
138
- console.log(`
139
- AWM configured for ${adapter.name}${isGlobal ? ' (global)' : ''}
140
-
141
- Agent ID: ${ctx.agentId}
142
- DB path: ${ctx.dbPath}
143
- ${configAction}
144
- ${instructionsAction}
145
- ${hooksAction}
146
-
147
- Next steps:
148
- 1. Restart ${adapter.name} to pick up the MCP server
149
- 2. Memory tools will appear automatically${adapter.id === 'codex' ? ' (verify with /mcp)' : ''}
138
+ console.log(`
139
+ AWM configured for ${adapter.name}${isGlobal ? ' (global)' : ''}
140
+
141
+ Agent ID: ${ctx.agentId}
142
+ DB path: ${ctx.dbPath}
143
+ ${configAction}
144
+ ${instructionsAction}
145
+ ${hooksAction}
146
+
147
+ Next steps:
148
+ 1. Restart ${adapter.name} to pick up the MCP server
149
+ 2. Memory tools will appear automatically${adapter.id === 'codex' ? ' (verify with /mcp)' : ''}
150
150
  `.trim());
151
151
  }
152
152
  // ─── DOCTOR ──────────────────────────────────────
@@ -309,7 +309,7 @@ async function exportMemories() {
309
309
  // Collect unique agents
310
310
  const agents = [...new Set(memories.map((m) => m.agent_id))];
311
311
  const exportData = {
312
- version: '0.8.7',
312
+ version: '0.8.8',
313
313
  exported_at: new Date().toISOString(),
314
314
  source_db: dbPath,
315
315
  agent_filter: agentFilter,
@@ -374,23 +374,23 @@ async function importMemories() {
374
374
  const Database = (await import('better-sqlite3')).default;
375
375
  const db = new Database(dbPath);
376
376
  // Ensure tables exist in target
377
- db.exec(`
378
- CREATE TABLE IF NOT EXISTS engrams (
379
- id TEXT PRIMARY KEY, agent_id TEXT NOT NULL, concept TEXT NOT NULL, content TEXT NOT NULL,
380
- embedding BLOB, confidence REAL NOT NULL DEFAULT 0.5, salience REAL NOT NULL DEFAULT 0.5,
381
- access_count INTEGER NOT NULL DEFAULT 0, last_accessed TEXT NOT NULL, created_at TEXT NOT NULL,
382
- salience_features TEXT NOT NULL DEFAULT '{}', reason_codes TEXT NOT NULL DEFAULT '[]',
383
- stage TEXT NOT NULL DEFAULT 'active', ttl INTEGER, retracted INTEGER NOT NULL DEFAULT 0,
384
- retracted_by TEXT, retracted_at TEXT, tags TEXT NOT NULL DEFAULT '[]',
385
- episode_id TEXT, task_status TEXT, task_priority TEXT, blocked_by TEXT,
386
- memory_class TEXT NOT NULL DEFAULT 'working', superseded_by TEXT, supersedes TEXT
387
- );
388
- CREATE TABLE IF NOT EXISTS associations (
389
- id TEXT PRIMARY KEY, from_engram_id TEXT NOT NULL, to_engram_id TEXT NOT NULL,
390
- weight REAL NOT NULL DEFAULT 0.1, confidence REAL NOT NULL DEFAULT 0.5,
391
- type TEXT NOT NULL DEFAULT 'hebbian', activation_count INTEGER NOT NULL DEFAULT 0,
392
- created_at TEXT NOT NULL, last_activated TEXT
393
- );
377
+ db.exec(`
378
+ CREATE TABLE IF NOT EXISTS engrams (
379
+ id TEXT PRIMARY KEY, agent_id TEXT NOT NULL, concept TEXT NOT NULL, content TEXT NOT NULL,
380
+ embedding BLOB, confidence REAL NOT NULL DEFAULT 0.5, salience REAL NOT NULL DEFAULT 0.5,
381
+ access_count INTEGER NOT NULL DEFAULT 0, last_accessed TEXT NOT NULL, created_at TEXT NOT NULL,
382
+ salience_features TEXT NOT NULL DEFAULT '{}', reason_codes TEXT NOT NULL DEFAULT '[]',
383
+ stage TEXT NOT NULL DEFAULT 'active', ttl INTEGER, retracted INTEGER NOT NULL DEFAULT 0,
384
+ retracted_by TEXT, retracted_at TEXT, tags TEXT NOT NULL DEFAULT '[]',
385
+ episode_id TEXT, task_status TEXT, task_priority TEXT, blocked_by TEXT,
386
+ memory_class TEXT NOT NULL DEFAULT 'working', superseded_by TEXT, supersedes TEXT
387
+ );
388
+ CREATE TABLE IF NOT EXISTS associations (
389
+ id TEXT PRIMARY KEY, from_engram_id TEXT NOT NULL, to_engram_id TEXT NOT NULL,
390
+ weight REAL NOT NULL DEFAULT 0.1, confidence REAL NOT NULL DEFAULT 0.5,
391
+ type TEXT NOT NULL DEFAULT 'hebbian', activation_count INTEGER NOT NULL DEFAULT 0,
392
+ created_at TEXT NOT NULL, last_activated TEXT
393
+ );
394
394
  `);
395
395
  // Build dedup set if needed
396
396
  const existingHashes = new Set();
@@ -405,15 +405,15 @@ async function importMemories() {
405
405
  let imported = 0;
406
406
  let skippedDupes = 0;
407
407
  let skippedRetracted = 0;
408
- const insertMem = db.prepare(`
409
- INSERT INTO engrams (id, agent_id, concept, content, confidence, salience,
410
- access_count, last_accessed, created_at, stage, tags, memory_class,
411
- episode_id, task_status, task_priority, supersedes, superseded_by, retracted)
412
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
408
+ const insertMem = db.prepare(`
409
+ INSERT INTO engrams (id, agent_id, concept, content, confidence, salience,
410
+ access_count, last_accessed, created_at, stage, tags, memory_class,
411
+ episode_id, task_status, task_priority, supersedes, superseded_by, retracted)
412
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
413
413
  `);
414
- const insertAssoc = db.prepare(`
415
- INSERT INTO associations (id, from_engram_id, to_engram_id, weight, type, activation_count, created_at)
416
- VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
414
+ const insertAssoc = db.prepare(`
415
+ INSERT INTO associations (id, from_engram_id, to_engram_id, weight, type, activation_count, created_at)
416
+ VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
417
417
  `);
418
418
  const importTx = db.transaction(() => {
419
419
  // Import memories
@@ -516,21 +516,21 @@ async function mergeMemories() {
516
516
  targetDb.pragma('journal_mode = WAL');
517
517
  targetDb.pragma('foreign_keys = ON');
518
518
  // Ensure tables exist in target
519
- targetDb.exec(`
520
- CREATE TABLE IF NOT EXISTS engrams (
521
- id TEXT PRIMARY KEY, agent_id TEXT NOT NULL, concept TEXT NOT NULL, content TEXT NOT NULL,
522
- embedding BLOB, confidence REAL NOT NULL DEFAULT 0.5, salience REAL NOT NULL DEFAULT 0.5,
523
- access_count INTEGER NOT NULL DEFAULT 0, last_accessed TEXT NOT NULL, created_at TEXT NOT NULL,
524
- salience_features TEXT NOT NULL DEFAULT '{}', reason_codes TEXT NOT NULL DEFAULT '[]',
525
- stage TEXT NOT NULL DEFAULT 'active', ttl INTEGER, retracted INTEGER NOT NULL DEFAULT 0,
526
- retracted_by TEXT, retracted_at TEXT, tags TEXT NOT NULL DEFAULT '[]'
527
- );
528
- CREATE TABLE IF NOT EXISTS associations (
529
- id TEXT PRIMARY KEY, from_engram_id TEXT NOT NULL, to_engram_id TEXT NOT NULL,
530
- weight REAL NOT NULL DEFAULT 0.1, confidence REAL NOT NULL DEFAULT 0.5,
531
- type TEXT NOT NULL DEFAULT 'hebbian', activation_count INTEGER NOT NULL DEFAULT 0,
532
- created_at TEXT NOT NULL, last_activated TEXT NOT NULL
533
- );
519
+ targetDb.exec(`
520
+ CREATE TABLE IF NOT EXISTS engrams (
521
+ id TEXT PRIMARY KEY, agent_id TEXT NOT NULL, concept TEXT NOT NULL, content TEXT NOT NULL,
522
+ embedding BLOB, confidence REAL NOT NULL DEFAULT 0.5, salience REAL NOT NULL DEFAULT 0.5,
523
+ access_count INTEGER NOT NULL DEFAULT 0, last_accessed TEXT NOT NULL, created_at TEXT NOT NULL,
524
+ salience_features TEXT NOT NULL DEFAULT '{}', reason_codes TEXT NOT NULL DEFAULT '[]',
525
+ stage TEXT NOT NULL DEFAULT 'active', ttl INTEGER, retracted INTEGER NOT NULL DEFAULT 0,
526
+ retracted_by TEXT, retracted_at TEXT, tags TEXT NOT NULL DEFAULT '[]'
527
+ );
528
+ CREATE TABLE IF NOT EXISTS associations (
529
+ id TEXT PRIMARY KEY, from_engram_id TEXT NOT NULL, to_engram_id TEXT NOT NULL,
530
+ weight REAL NOT NULL DEFAULT 0.1, confidence REAL NOT NULL DEFAULT 0.5,
531
+ type TEXT NOT NULL DEFAULT 'hebbian', activation_count INTEGER NOT NULL DEFAULT 0,
532
+ created_at TEXT NOT NULL, last_activated TEXT NOT NULL
533
+ );
534
534
  `);
535
535
  // Build dedupe hash set from existing target memories
536
536
  const existingHashes = new Set();
@@ -540,16 +540,16 @@ async function mergeMemories() {
540
540
  existingHashes.add(contentHash(row.concept, row.content));
541
541
  console.log(`Target has ${existingHashes.size} unique memories (for dedupe)\n`);
542
542
  }
543
- const insertEngram = targetDb.prepare(`
544
- INSERT OR IGNORE INTO engrams (id, agent_id, concept, content, confidence, salience, access_count,
545
- last_accessed, created_at, salience_features, reason_codes, stage, ttl,
546
- retracted, retracted_by, retracted_at, tags)
547
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
543
+ const insertEngram = targetDb.prepare(`
544
+ INSERT OR IGNORE INTO engrams (id, agent_id, concept, content, confidence, salience, access_count,
545
+ last_accessed, created_at, salience_features, reason_codes, stage, ttl,
546
+ retracted, retracted_by, retracted_at, tags)
547
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
548
548
  `);
549
- const insertAssoc = targetDb.prepare(`
550
- INSERT OR IGNORE INTO associations (id, from_engram_id, to_engram_id, weight, confidence, type,
551
- activation_count, created_at, last_activated)
552
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
549
+ const insertAssoc = targetDb.prepare(`
550
+ INSERT OR IGNORE INTO associations (id, from_engram_id, to_engram_id, weight, confidence, type,
551
+ activation_count, created_at, last_activated)
552
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
553
553
  `);
554
554
  let totalMemories = 0, totalAssociations = 0, totalSkipped = 0;
555
555
  for (const sourcePath of sources) {
@@ -558,10 +558,10 @@ async function mergeMemories() {
558
558
  continue;
559
559
  }
560
560
  const sourceDb = new Database(sourcePath, { readonly: true });
561
- const engrams = sourceDb.prepare(`SELECT id, agent_id, concept, content, confidence, salience, access_count,
562
- last_accessed, created_at, salience_features, reason_codes, stage, ttl,
561
+ const engrams = sourceDb.prepare(`SELECT id, agent_id, concept, content, confidence, salience, access_count,
562
+ last_accessed, created_at, salience_features, reason_codes, stage, ttl,
563
563
  retracted, retracted_by, retracted_at, tags FROM engrams`).all();
564
- const assocs = sourceDb.prepare(`SELECT id, from_engram_id, to_engram_id, weight, confidence, type,
564
+ const assocs = sourceDb.prepare(`SELECT id, from_engram_id, to_engram_id, weight, confidence, type,
565
565
  activation_count, created_at, last_activated FROM associations`).all();
566
566
  const idMap = new Map();
567
567
  const skippedIds = new Set();
package/dist/mcp.js CHANGED
@@ -77,7 +77,7 @@ import { queryPeerDecisions, formatPeerDecisions } from './coordination/peer-dec
77
77
  const INCOGNITO = process.env.AWM_INCOGNITO === '1' || process.env.AWM_INCOGNITO === 'true';
78
78
  if (INCOGNITO) {
79
79
  console.error('AWM: incognito mode — all memory tools disabled, nothing will be recorded');
80
- const server = new McpServer({ name: 'agent-working-memory', version: '0.8.7' });
80
+ const server = new McpServer({ name: 'agent-working-memory', version: '0.8.8' });
81
81
  const transport = new StdioServerTransport();
82
82
  server.connect(transport).catch(err => {
83
83
  console.error('MCP server failed:', err);
@@ -126,7 +126,7 @@ else {
126
126
  let coordDb = null;
127
127
  const server = new McpServer({
128
128
  name: 'agent-working-memory',
129
- version: '0.8.7',
129
+ version: '0.8.8',
130
130
  });
131
131
  server.registerResource('awm-overview', 'awm://server/overview', {
132
132
  title: 'AWM Overview',
@@ -186,15 +186,15 @@ else {
186
186
  return 'unclassified';
187
187
  }
188
188
  // --- Tools ---
189
- server.tool('memory_write', `Store a memory. The salience filter decides whether it's worth keeping (active), needs more evidence (staging), or should be discarded.
190
-
191
- CALL THIS PROACTIVELY — do not wait to be asked. Write memories when you:
192
- - Discover something about the codebase, bugs, or architecture
193
- - Make a decision and want to remember why
194
- - Encounter and resolve an error
195
- - Learn a user preference or project pattern
196
- - Complete a significant piece of work
197
-
189
+ server.tool('memory_write', `Store a memory. The salience filter decides whether it's worth keeping (active), needs more evidence (staging), or should be discarded.
190
+
191
+ CALL THIS PROACTIVELY — do not wait to be asked. Write memories when you:
192
+ - Discover something about the codebase, bugs, or architecture
193
+ - Make a decision and want to remember why
194
+ - Encounter and resolve an error
195
+ - Learn a user preference or project pattern
196
+ - Complete a significant piece of work
197
+
198
198
  The concept should be a short label (3-8 words). The content should be the full detail.`, {
199
199
  concept: z.string().describe('Short label for this memory (3-8 words)'),
200
200
  content: z.string().describe('Full detail of what was learned'),
@@ -298,15 +298,15 @@ The concept should be a short label (3-8 words). The content should be the full
298
298
  }],
299
299
  };
300
300
  });
301
- server.tool('memory_recall', `Recall memories relevant to a query. Uses cognitive activation — not keyword search.
302
-
303
- ALWAYS call this when:
304
- - Starting work on a project or topic (recall what you know)
305
- - Debugging (recall similar errors and solutions)
306
- - Making decisions (recall past decisions and outcomes)
307
- - The user mentions a topic you might have stored memories about
308
-
309
- Accepts either "query" or "context" parameter — both work identically.
301
+ server.tool('memory_recall', `Recall memories relevant to a query. Uses cognitive activation — not keyword search.
302
+
303
+ ALWAYS call this when:
304
+ - Starting work on a project or topic (recall what you know)
305
+ - Debugging (recall similar errors and solutions)
306
+ - Making decisions (recall past decisions and outcomes)
307
+ - The user mentions a topic you might have stored memories about
308
+
309
+ Accepts either "query" or "context" parameter — both work identically.
310
310
  Returns the most relevant memories ranked by text relevance, temporal recency, and associative strength.`, {
311
311
  query: z.string().optional().describe('What to search for — describe the situation, question, or topic'),
312
312
  context: z.string().optional().describe('Alias for query (either works)'),
@@ -377,8 +377,8 @@ Returns the most relevant memories ranked by text relevance, temporal recency, a
377
377
  }],
378
378
  };
379
379
  });
380
- server.tool('memory_feedback', `Report whether a recalled memory was actually useful. This updates the memory's confidence score — useful memories become stronger, useless ones weaken.
381
-
380
+ server.tool('memory_feedback', `Report whether a recalled memory was actually useful. This updates the memory's confidence score — useful memories become stronger, useless ones weaken.
381
+
382
382
  Always call this after using a recalled memory so the system learns what's valuable.`, {
383
383
  engram_id: z.string().describe('ID of the memory (from memory_recall results)'),
384
384
  useful: z.boolean().describe('Was this memory actually helpful?'),
@@ -401,8 +401,8 @@ Always call this after using a recalled memory so the system learns what's valua
401
401
  }],
402
402
  };
403
403
  });
404
- server.tool('memory_retract', `Retract a memory that turned out to be wrong. Creates a correction and reduces confidence of related memories.
405
-
404
+ server.tool('memory_retract', `Retract a memory that turned out to be wrong. Creates a correction and reduces confidence of related memories.
405
+
406
406
  Use this when you discover a memory contains incorrect information.`, {
407
407
  engram_id: z.string().describe('ID of the wrong memory'),
408
408
  reason: z.string().describe('Why is this memory wrong?'),
@@ -426,13 +426,13 @@ Use this when you discover a memory contains incorrect information.`, {
426
426
  }],
427
427
  };
428
428
  });
429
- server.tool('memory_supersede', `Replace an outdated memory with a newer one. Unlike retraction (which marks memories as wrong), supersession marks the old memory as outdated but historically correct.
430
-
431
- Use this when:
432
- - A status or count has changed (e.g., "5 reviews done" → "7 reviews done")
433
- - Architecture or infrastructure evolved (e.g., "two-repo model" → "three-repo model")
434
- - A schedule or plan was updated
435
-
429
+ server.tool('memory_supersede', `Replace an outdated memory with a newer one. Unlike retraction (which marks memories as wrong), supersession marks the old memory as outdated but historically correct.
430
+
431
+ Use this when:
432
+ - A status or count has changed (e.g., "5 reviews done" → "7 reviews done")
433
+ - Architecture or infrastructure evolved (e.g., "two-repo model" → "three-repo model")
434
+ - A schedule or plan was updated
435
+
436
436
  The old memory stays in the database (searchable for history) but is heavily down-ranked in recall so the current version dominates.`, {
437
437
  old_engram_id: z.string().describe('ID of the outdated memory'),
438
438
  new_engram_id: z.string().describe('ID of the replacement memory'),
@@ -459,7 +459,7 @@ The old memory stays in the database (searchable for history) but is heavily dow
459
459
  }],
460
460
  };
461
461
  });
462
- server.tool('memory_stats', `Get memory health stats — how many memories, confidence levels, association count, and system performance.
462
+ server.tool('memory_stats', `Get memory health stats — how many memories, confidence levels, association count, and system performance.
463
463
  Also shows the activity log path so the user can tail it to see what's happening.`, {}, async () => {
464
464
  const metrics = await evalEngine.computeMetrics(AGENT_ID);
465
465
  const checkpoint = await store.getCheckpoint(AGENT_ID);
@@ -490,13 +490,13 @@ Also shows the activity log path so the user can tail it to see what's happening
490
490
  };
491
491
  });
492
492
  // --- Checkpointing Tools ---
493
- server.tool('memory_checkpoint', `Save your current execution state so you can recover after context compaction.
494
-
495
- ALWAYS call this before:
496
- - Long operations (multi-file generation, large refactors, overnight work)
497
- - Anything that might fill the context window
498
- - Switching to a different task
499
-
493
+ server.tool('memory_checkpoint', `Save your current execution state so you can recover after context compaction.
494
+
495
+ ALWAYS call this before:
496
+ - Long operations (multi-file generation, large refactors, overnight work)
497
+ - Anything that might fill the context window
498
+ - Switching to a different task
499
+
500
500
  Also call periodically during long sessions to avoid losing state. The state is saved per-agent and overwrites any previous checkpoint.`, {
501
501
  current_task: z.string().describe('What you are currently working on'),
502
502
  decisions: z.array(z.string()).optional().default([])
@@ -530,14 +530,14 @@ Also call periodically during long sessions to avoid losing state. The state is
530
530
  }],
531
531
  };
532
532
  });
533
- server.tool('memory_restore', `Restore your previous execution state after context compaction or at session start.
534
-
535
- Returns:
536
- - Your saved execution state (task, decisions, next steps, files)
537
- - Recently recalled memories for context
538
- - Your last write for continuity
539
- - How long you were idle
540
-
533
+ server.tool('memory_restore', `Restore your previous execution state after context compaction or at session start.
534
+
535
+ Returns:
536
+ - Your saved execution state (task, decisions, next steps, files)
537
+ - Recently recalled memories for context
538
+ - Your last write for continuity
539
+ - How long you were idle
540
+
541
541
  Use this at the start of every session or after compaction to pick up where you left off.`, {}, async () => {
542
542
  const checkpoint = await store.getCheckpoint(AGENT_ID);
543
543
  const now = Date.now();
@@ -645,9 +645,9 @@ Use this at the start of every session or after compaction to pick up where you
645
645
  if (coordDb) {
646
646
  try {
647
647
  const myAgent = coordDb.prepare(`SELECT id FROM coord_agents WHERE name = ? AND status != 'dead' ORDER BY last_seen DESC LIMIT 1`).get(AGENT_ID);
648
- const peerDecisions = coordDb.prepare(`SELECT d.summary, a.name AS author_name, d.created_at
649
- FROM coord_decisions d JOIN coord_agents a ON d.author_id = a.id
650
- WHERE d.author_id != ? AND d.created_at > datetime('now', '-30 minutes')
648
+ const peerDecisions = coordDb.prepare(`SELECT d.summary, a.name AS author_name, d.created_at
649
+ FROM coord_decisions d JOIN coord_agents a ON d.author_id = a.id
650
+ WHERE d.author_id != ? AND d.created_at > datetime('now', '-30 minutes')
651
651
  ORDER BY d.created_at DESC LIMIT 10`).all(myAgent?.id ?? '');
652
652
  if (peerDecisions.length > 0) {
653
653
  parts.push(`\n**Peer decisions (last 30 min):**`);
@@ -666,13 +666,13 @@ Use this at the start of every session or after compaction to pick up where you
666
666
  };
667
667
  });
668
668
  // --- Task Management Tools ---
669
- server.tool('memory_task_add', `Create a task that you need to come back to. Tasks are memories with status and priority tracking.
670
-
671
- Use this when:
672
- - You identify work that needs doing but can't do it right now
673
- - The user mentions something to do later
674
- - You want to park a sub-task while focusing on something more urgent
675
-
669
+ server.tool('memory_task_add', `Create a task that you need to come back to. Tasks are memories with status and priority tracking.
670
+
671
+ Use this when:
672
+ - You identify work that needs doing but can't do it right now
673
+ - The user mentions something to do later
674
+ - You want to park a sub-task while focusing on something more urgent
675
+
676
676
  Tasks automatically get high salience so they won't be discarded.`, {
677
677
  concept: z.string().describe('Short task title (3-10 words)'),
678
678
  content: z.string().describe('Full task description — what needs doing, context, acceptance criteria'),
@@ -712,11 +712,11 @@ Tasks automatically get high salience so they won't be discarded.`, {
712
712
  }],
713
713
  };
714
714
  });
715
- server.tool('memory_task_update', `Update a task's status or priority. Use this to:
716
- - Start working on a task (open → in_progress)
717
- - Mark a task done (→ done)
718
- - Block a task on another (→ blocked)
719
- - Reprioritize (change priority)
715
+ server.tool('memory_task_update', `Update a task's status or priority. Use this to:
716
+ - Start working on a task (open → in_progress)
717
+ - Mark a task done (→ done)
718
+ - Block a task on another (→ blocked)
719
+ - Reprioritize (change priority)
720
720
  - Unblock a task (clear blocked_by)`, {
721
721
  task_id: z.string().describe('ID of the task to update'),
722
722
  status: z.enum(['open', 'in_progress', 'blocked', 'done']).optional()
@@ -746,8 +746,8 @@ Tasks automatically get high salience so they won't be discarded.`, {
746
746
  }],
747
747
  };
748
748
  });
749
- server.tool('memory_task_list', `List tasks with optional status filter. Shows tasks ordered by priority (urgent first).
750
-
749
+ server.tool('memory_task_list', `List tasks with optional status filter. Shows tasks ordered by priority (urgent first).
750
+
751
751
  Use at the start of a session to see what's pending, or to check blocked/done tasks.`, {
752
752
  status: z.enum(['open', 'in_progress', 'blocked', 'done']).optional()
753
753
  .describe('Filter by status (omit to see all active tasks)'),
@@ -773,10 +773,10 @@ Use at the start of a session to see what's pending, or to check blocked/done ta
773
773
  }],
774
774
  };
775
775
  });
776
- server.tool('memory_task_next', `Get the single most important task to work on next.
777
-
778
- Prioritizes: in_progress tasks first (finish what you started), then by priority level, then oldest first. Skips blocked and done tasks.
779
-
776
+ server.tool('memory_task_next', `Get the single most important task to work on next.
777
+
778
+ Prioritizes: in_progress tasks first (finish what you started), then by priority level, then oldest first. Skips blocked and done tasks.
779
+
780
780
  Use this when you finish a task or need to decide what to do next.`, {}, async () => {
781
781
  const next = await store.getNextTask(AGENT_ID);
782
782
  if (!next) {
@@ -792,13 +792,13 @@ Use this when you finish a task or need to decide what to do next.`, {}, async (
792
792
  };
793
793
  });
794
794
  // --- Task Bracket Tools ---
795
- server.tool('memory_task_begin', `Signal that you're starting a significant task. Auto-checkpoints current state and recalls relevant memories.
796
-
797
- CALL THIS when starting:
798
- - A multi-step operation (doc generation, large refactor, migration)
799
- - Work on a new topic or project area
800
- - Anything that might fill the context window
801
-
795
+ server.tool('memory_task_begin', `Signal that you're starting a significant task. Auto-checkpoints current state and recalls relevant memories.
796
+
797
+ CALL THIS when starting:
798
+ - A multi-step operation (doc generation, large refactor, migration)
799
+ - Work on a new topic or project area
800
+ - Anything that might fill the context window
801
+
802
802
  This ensures your state is saved before you start, and primes recall with relevant context.`, {
803
803
  topic: z.string().describe('What task are you starting? (3-15 words)'),
804
804
  files: z.array(z.string()).optional().default([])
@@ -849,13 +849,13 @@ This ensures your state is saved before you start, and primes recall with releva
849
849
  }],
850
850
  };
851
851
  });
852
- server.tool('memory_task_end', `Signal that you've finished a significant task. Writes a summary memory and auto-checkpoints.
853
-
854
- CALL THIS when you finish:
855
- - A multi-step operation
856
- - Before switching to a different topic
857
- - At the end of a work session
858
-
852
+ server.tool('memory_task_end', `Signal that you've finished a significant task. Writes a summary memory and auto-checkpoints.
853
+
854
+ CALL THIS when you finish:
855
+ - A multi-step operation
856
+ - Before switching to a different topic
857
+ - At the end of a work session
858
+
859
859
  This captures what was accomplished so future sessions can recall it.`, {
860
860
  summary: z.string().describe('What was accomplished? Include key outcomes, decisions, and any issues.'),
861
861
  tags: z.array(z.string()).optional().default([])
@@ -928,14 +928,14 @@ This captures what was accomplished so future sessions can recall it.`, {
928
928
  }],
929
929
  };
930
930
  });
931
- server.tool('compress_output', `Compress a STRUCTURED tool output (JSON object/array, query rows, log records) into TOON —
932
- a compact, schema-aware tabular encoding — before putting it in your context. Cuts ~50-65%
933
- of the tokens on uniform arrays at zero comprehension cost (validated: models read TOON as
934
- accurately as JSON). Use this on large tool results you need to keep in context.
935
-
936
- Output-only and safe: it never changes the data. Non-JSON / prose is returned unchanged.
937
- TOON is only emitted when it reproduces the input exactly (self-verified round-trip);
938
- otherwise you get plain JSON back. When compressed, you also get a 'ref' — call
931
+ server.tool('compress_output', `Compress a STRUCTURED tool output (JSON object/array, query rows, log records) into TOON —
932
+ a compact, schema-aware tabular encoding — before putting it in your context. Cuts ~50-65%
933
+ of the tokens on uniform arrays at zero comprehension cost (validated: models read TOON as
934
+ accurately as JSON). Use this on large tool results you need to keep in context.
935
+
936
+ Output-only and safe: it never changes the data. Non-JSON / prose is returned unchanged.
937
+ TOON is only emitted when it reproduces the input exactly (self-verified round-trip);
938
+ otherwise you get plain JSON back. When compressed, you also get a 'ref' — call
939
939
  retrieve_original(ref) to get the verbatim source back if you ever need it.`, {
940
940
  output: z.string().describe('The tool output to compress — JSON text (preferred) or any string. Non-JSON is returned unchanged.'),
941
941
  min_saving_chars: z.number().optional().describe('Only emit TOON if it saves at least this many characters (default 40).'),
@@ -949,8 +949,8 @@ retrieve_original(ref) to get the verbatim source back if you ever need it.`, {
949
949
  content: [{ type: 'text', text: header + r.text }],
950
950
  };
951
951
  });
952
- server.tool('retrieve_original', `Retrieve the verbatim original text for a 'ref' returned by compress_output. Use this when
953
- you need the exact, uncompressed source (e.g. to pass it to another tool unchanged). Returns
952
+ server.tool('retrieve_original', `Retrieve the verbatim original text for a 'ref' returned by compress_output. Use this when
953
+ you need the exact, uncompressed source (e.g. to pass it to another tool unchanged). Returns
954
954
  an error if the ref has expired (originals are kept for the most recent compressions only).`, {
955
955
  ref: z.string().describe('The ref handle returned by compress_output (e.g. "awm_orig_12").'),
956
956
  }, async (params) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-working-memory",
3
- "version": "0.8.7",
3
+ "version": "0.8.8",
4
4
  "description": "Cognitive memory layer for AI agents — activation-based retrieval, salience filtering, associative connections",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/api/routes.ts CHANGED
@@ -952,7 +952,7 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
952
952
  const base: Record<string, unknown> = {
953
953
  status: 'ok',
954
954
  timestamp: new Date().toISOString(),
955
- version: '0.8.7',
955
+ version: '0.8.8',
956
956
  coordination: coordEnabled,
957
957
  };
958
958
  if (coordEnabled && typeof (deps.store as any).getDb === 'function') {
package/src/cli.ts CHANGED
@@ -336,7 +336,7 @@ async function exportMemories() {
336
336
  const agents = [...new Set(memories.map((m: any) => m.agent_id))];
337
337
 
338
338
  const exportData = {
339
- version: '0.8.7',
339
+ version: '0.8.8',
340
340
  exported_at: new Date().toISOString(),
341
341
  source_db: dbPath,
342
342
  agent_filter: agentFilter,
package/src/mcp.ts CHANGED
@@ -86,7 +86,7 @@ const INCOGNITO = process.env.AWM_INCOGNITO === '1' || process.env.AWM_INCOGNITO
86
86
 
87
87
  if (INCOGNITO) {
88
88
  console.error('AWM: incognito mode — all memory tools disabled, nothing will be recorded');
89
- const server = new McpServer({ name: 'agent-working-memory', version: '0.8.7' });
89
+ const server = new McpServer({ name: 'agent-working-memory', version: '0.8.8' });
90
90
  const transport = new StdioServerTransport();
91
91
  server.connect(transport).catch(err => {
92
92
  console.error('MCP server failed:', err);
@@ -141,7 +141,7 @@ let coordDb: import('better-sqlite3').Database | null = null;
141
141
 
142
142
  const server = new McpServer({
143
143
  name: 'agent-working-memory',
144
- version: '0.8.7',
144
+ version: '0.8.8',
145
145
  });
146
146
 
147
147
  server.registerResource(