agent-working-memory 0.6.0 → 0.7.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 (135) hide show
  1. package/README.md +19 -9
  2. package/dist/adapters/claude-code.d.ts +4 -0
  3. package/dist/adapters/claude-code.d.ts.map +1 -0
  4. package/dist/adapters/claude-code.js +218 -0
  5. package/dist/adapters/claude-code.js.map +1 -0
  6. package/dist/adapters/codex.d.ts +4 -0
  7. package/dist/adapters/codex.d.ts.map +1 -0
  8. package/dist/adapters/codex.js +226 -0
  9. package/dist/adapters/codex.js.map +1 -0
  10. package/dist/adapters/common.d.ts +34 -0
  11. package/dist/adapters/common.d.ts.map +1 -0
  12. package/dist/adapters/common.js +145 -0
  13. package/dist/adapters/common.js.map +1 -0
  14. package/dist/adapters/cursor.d.ts +4 -0
  15. package/dist/adapters/cursor.d.ts.map +1 -0
  16. package/dist/adapters/cursor.js +138 -0
  17. package/dist/adapters/cursor.js.map +1 -0
  18. package/dist/adapters/http.d.ts +4 -0
  19. package/dist/adapters/http.d.ts.map +1 -0
  20. package/dist/adapters/http.js +88 -0
  21. package/dist/adapters/http.js.map +1 -0
  22. package/dist/adapters/index.d.ts +7 -0
  23. package/dist/adapters/index.d.ts.map +1 -0
  24. package/dist/adapters/index.js +21 -0
  25. package/dist/adapters/index.js.map +1 -0
  26. package/dist/adapters/types.d.ts +65 -0
  27. package/dist/adapters/types.d.ts.map +1 -0
  28. package/dist/adapters/types.js +4 -0
  29. package/dist/adapters/types.js.map +1 -0
  30. package/dist/api/routes.d.ts.map +1 -1
  31. package/dist/api/routes.js +1 -0
  32. package/dist/api/routes.js.map +1 -1
  33. package/dist/cli.js +104 -230
  34. package/dist/cli.js.map +1 -1
  35. package/dist/coordination/events.d.ts +59 -0
  36. package/dist/coordination/events.d.ts.map +1 -0
  37. package/dist/coordination/events.js +28 -0
  38. package/dist/coordination/events.js.map +1 -0
  39. package/dist/coordination/index.d.ts +10 -1
  40. package/dist/coordination/index.d.ts.map +1 -1
  41. package/dist/coordination/index.js +87 -3
  42. package/dist/coordination/index.js.map +1 -1
  43. package/dist/coordination/peer-decisions.d.ts +40 -0
  44. package/dist/coordination/peer-decisions.d.ts.map +1 -0
  45. package/dist/coordination/peer-decisions.js +82 -0
  46. package/dist/coordination/peer-decisions.js.map +1 -0
  47. package/dist/coordination/plugin-loader.d.ts +18 -0
  48. package/dist/coordination/plugin-loader.d.ts.map +1 -0
  49. package/dist/coordination/plugin-loader.js +55 -0
  50. package/dist/coordination/plugin-loader.js.map +1 -0
  51. package/dist/coordination/plugin.d.ts +40 -0
  52. package/dist/coordination/plugin.d.ts.map +1 -0
  53. package/dist/coordination/plugin.js +22 -0
  54. package/dist/coordination/plugin.js.map +1 -0
  55. package/dist/coordination/routes.d.ts +2 -1
  56. package/dist/coordination/routes.d.ts.map +1 -1
  57. package/dist/coordination/routes.js +899 -76
  58. package/dist/coordination/routes.js.map +1 -1
  59. package/dist/coordination/schema.d.ts.map +1 -1
  60. package/dist/coordination/schema.js +72 -14
  61. package/dist/coordination/schema.js.map +1 -1
  62. package/dist/coordination/schemas.d.ts +84 -3
  63. package/dist/coordination/schemas.d.ts.map +1 -1
  64. package/dist/coordination/schemas.js +71 -1
  65. package/dist/coordination/schemas.js.map +1 -1
  66. package/dist/coordination/stale.d.ts.map +1 -1
  67. package/dist/coordination/stale.js +2 -1
  68. package/dist/coordination/stale.js.map +1 -1
  69. package/dist/coordination/types.d.ts +252 -0
  70. package/dist/coordination/types.d.ts.map +1 -0
  71. package/dist/coordination/types.js +8 -0
  72. package/dist/coordination/types.js.map +1 -0
  73. package/dist/coordination/write-mutex.d.ts +26 -0
  74. package/dist/coordination/write-mutex.d.ts.map +1 -0
  75. package/dist/coordination/write-mutex.js +63 -0
  76. package/dist/coordination/write-mutex.js.map +1 -0
  77. package/dist/core/embeddings.d.ts +2 -0
  78. package/dist/core/embeddings.d.ts.map +1 -1
  79. package/dist/core/embeddings.js +4 -0
  80. package/dist/core/embeddings.js.map +1 -1
  81. package/dist/core/hebbian.d.ts +25 -1
  82. package/dist/core/hebbian.d.ts.map +1 -1
  83. package/dist/core/hebbian.js +71 -3
  84. package/dist/core/hebbian.js.map +1 -1
  85. package/dist/engine/activation.d.ts +22 -4
  86. package/dist/engine/activation.d.ts.map +1 -1
  87. package/dist/engine/activation.js +152 -76
  88. package/dist/engine/activation.js.map +1 -1
  89. package/dist/engine/consolidation.d.ts.map +1 -1
  90. package/dist/engine/consolidation.js +15 -6
  91. package/dist/engine/consolidation.js.map +1 -1
  92. package/dist/engine/retraction.d.ts +3 -1
  93. package/dist/engine/retraction.d.ts.map +1 -1
  94. package/dist/engine/retraction.js +19 -6
  95. package/dist/engine/retraction.js.map +1 -1
  96. package/dist/index.js +6 -18
  97. package/dist/index.js.map +1 -1
  98. package/dist/mcp.js +61 -4
  99. package/dist/mcp.js.map +1 -1
  100. package/dist/storage/sqlite.d.ts +23 -1
  101. package/dist/storage/sqlite.d.ts.map +1 -1
  102. package/dist/storage/sqlite.js +112 -3
  103. package/dist/storage/sqlite.js.map +1 -1
  104. package/dist/types/engram.d.ts +1 -0
  105. package/dist/types/engram.d.ts.map +1 -1
  106. package/package.json +1 -1
  107. package/src/adapters/claude-code.ts +234 -0
  108. package/src/adapters/codex.ts +262 -0
  109. package/src/adapters/common.ts +172 -0
  110. package/src/adapters/cursor.ts +150 -0
  111. package/src/adapters/http.ts +100 -0
  112. package/src/adapters/index.ts +31 -0
  113. package/src/adapters/types.ts +75 -0
  114. package/src/api/routes.ts +2 -0
  115. package/src/cli.ts +107 -238
  116. package/src/coordination/events.ts +90 -0
  117. package/src/coordination/index.ts +102 -3
  118. package/src/coordination/peer-decisions.ts +105 -0
  119. package/src/coordination/plugin-loader.ts +60 -0
  120. package/src/coordination/plugin.ts +44 -0
  121. package/src/coordination/routes.ts +1176 -105
  122. package/src/coordination/schema.ts +67 -14
  123. package/src/coordination/schemas.ts +85 -1
  124. package/src/coordination/stale.ts +3 -2
  125. package/src/coordination/types.ts +311 -0
  126. package/src/coordination/write-mutex.ts +69 -0
  127. package/src/core/embeddings.ts +5 -0
  128. package/src/core/hebbian.ts +84 -3
  129. package/src/engine/activation.ts +149 -73
  130. package/src/engine/consolidation.ts +15 -6
  131. package/src/engine/retraction.ts +22 -6
  132. package/src/index.ts +6 -15
  133. package/src/mcp.ts +77 -4
  134. package/src/storage/sqlite.ts +116 -3
  135. package/src/types/engram.ts +1 -0
package/src/mcp.ts CHANGED
@@ -26,7 +26,7 @@
26
26
 
27
27
  import { readFileSync } from 'node:fs';
28
28
  import { resolve } from 'node:path';
29
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
29
+ import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
30
30
 
31
31
  // Load .env file if present (no external dependency)
32
32
  try {
@@ -42,6 +42,12 @@ try {
42
42
  if (!process.env[key]) process.env[key] = val;
43
43
  }
44
44
  } catch { /* No .env file */ }
45
+
46
+ // MCP uses stdout for JSON-RPC. Redirect console.log to stderr so engine
47
+ // startup messages (ConsolidationScheduler, model loading, etc.) don't
48
+ // corrupt the transport. This MUST happen before any engine imports.
49
+ console.log = console.error;
50
+
45
51
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
46
52
  import { z } from 'zod';
47
53
 
@@ -62,6 +68,7 @@ import { DEFAULT_AGENT_CONFIG } from './types/agent.js';
62
68
  import { embed } from './core/embeddings.js';
63
69
  import { startSidecar } from './hooks/sidecar.js';
64
70
  import { initLogger, log, getLogPath } from './core/logger.js';
71
+ import { queryPeerDecisions, formatPeerDecisions } from './coordination/peer-decisions.js';
65
72
 
66
73
  // --- Incognito Mode ---
67
74
  // When AWM_INCOGNITO=1, register zero tools. Claude won't see memory tools at all.
@@ -111,6 +118,58 @@ const server = new McpServer({
111
118
  version: '0.6.0',
112
119
  });
113
120
 
121
+ server.registerResource(
122
+ 'awm-overview',
123
+ 'awm://server/overview',
124
+ {
125
+ title: 'AWM Overview',
126
+ description: 'AgentWorkingMemory MCP server metadata and discovery notes',
127
+ mimeType: 'text/markdown',
128
+ },
129
+ async () => ({
130
+ contents: [{
131
+ uri: 'awm://server/overview',
132
+ text: [
133
+ '# Agent Working Memory',
134
+ '',
135
+ `Agent: ${AGENT_ID}`,
136
+ `DB: ${DB_PATH}`,
137
+ `Coordination: ${process.env.AWM_COORDINATION === 'true' || process.env.AWM_COORDINATION === '1' ? 'enabled' : 'disabled'}`,
138
+ '',
139
+ 'This MCP server primarily exposes tools such as `memory_restore`, `memory_recall`, `memory_write`, and task/checkpoint operations.',
140
+ 'The resources below exist so generic MCP clients can discover the server through `resources/list` and `resources/templates/list`.',
141
+ ].join('\n'),
142
+ mimeType: 'text/markdown',
143
+ }],
144
+ })
145
+ );
146
+
147
+ server.registerResource(
148
+ 'awm-memory-template',
149
+ new ResourceTemplate('awm://memory/{id}', { list: undefined }),
150
+ {
151
+ title: 'AWM Memory By ID',
152
+ description: 'Metadata resource template for a memory identifier',
153
+ mimeType: 'text/markdown',
154
+ },
155
+ async (_uri, variables) => ({
156
+ contents: [{
157
+ uri: `awm://memory/${variables.id ?? ''}`,
158
+ text: [
159
+ '# AWM Memory Reference',
160
+ '',
161
+ `Requested memory id: ${variables.id ?? ''}`,
162
+ '',
163
+ 'Use the AWM memory tools for actual retrieval and mutation:',
164
+ '- `memory_recall` for cognitive retrieval',
165
+ '- `memory_restore` for session state',
166
+ '- `memory_feedback`, `memory_retract`, `memory_supersede` for memory maintenance',
167
+ ].join('\n'),
168
+ mimeType: 'text/markdown',
169
+ }],
170
+ })
171
+ );
172
+
114
173
  // --- Auto-classification for memory types ---
115
174
 
116
175
  function classifyMemoryType(content: string): 'episodic' | 'semantic' | 'procedural' | 'unclassified' {
@@ -319,6 +378,7 @@ Returns the most relevant memories ranked by text relevance, temporal recency, a
319
378
  use_reranker: z.boolean().optional().default(true).describe('Use cross-encoder re-ranking for better relevance (default true)'),
320
379
  use_expansion: z.boolean().optional().default(true).describe('Expand query with synonyms for better recall (default true)'),
321
380
  memory_type: z.enum(['episodic', 'semantic', 'procedural']).optional().describe('Filter by memory type (omit to search all types)'),
381
+ workspace: z.string().optional().describe('Search across all agents in this workspace (hive mode). Omit for agent-scoped recall only.'),
322
382
  },
323
383
  async (params) => {
324
384
  const queryText = params.query ?? params.context;
@@ -330,6 +390,8 @@ Returns the most relevant memories ranked by text relevance, temporal recency, a
330
390
  }],
331
391
  };
332
392
  }
393
+ // Use workspace from param, env var, or omit for agent-scoped
394
+ const workspace = params.workspace ?? process.env.AWM_WORKSPACE ?? undefined;
333
395
  const results = await activationEngine.activate({
334
396
  agentId: AGENT_ID,
335
397
  context: queryText,
@@ -339,6 +401,7 @@ Returns the most relevant memories ranked by text relevance, temporal recency, a
339
401
  useReranker: params.use_reranker,
340
402
  useExpansion: params.use_expansion,
341
403
  memoryType: params.memory_type,
404
+ workspace,
342
405
  });
343
406
 
344
407
  // Auto-checkpoint: track recall
@@ -349,11 +412,16 @@ Returns the most relevant memories ranked by text relevance, temporal recency, a
349
412
 
350
413
  log(AGENT_ID, 'recall', `"${queryText.slice(0, 80)}" → ${results.length} results`);
351
414
 
415
+ // Peer decisions: append recent decisions by other agents relevant to this query
416
+ const peerSuffix = coordDb
417
+ ? formatPeerDecisions(queryPeerDecisions(coordDb, AGENT_ID, queryText))
418
+ : '';
419
+
352
420
  if (results.length === 0) {
353
421
  return {
354
422
  content: [{
355
423
  type: 'text' as const,
356
- text: 'No relevant memories found.',
424
+ text: 'No relevant memories found.' + peerSuffix,
357
425
  }],
358
426
  };
359
427
  }
@@ -365,7 +433,7 @@ Returns the most relevant memories ranked by text relevance, temporal recency, a
365
433
  return {
366
434
  content: [{
367
435
  type: 'text' as const,
368
- text: lines.join('\n'),
436
+ text: lines.join('\n') + peerSuffix,
369
437
  }],
370
438
  };
371
439
  }
@@ -392,10 +460,13 @@ Always call this after using a recalled memory so the system learns what's valua
392
460
  store.updateConfidence(engram.id, engram.confidence + delta);
393
461
  }
394
462
 
463
+ // Validation-gated Hebbian: resolve pending co-activation pairs for this engram
464
+ const hebbianUpdated = activationEngine.resolveHebbianFeedback(params.engram_id, params.useful);
465
+
395
466
  return {
396
467
  content: [{
397
468
  type: 'text' as const,
398
- text: `Feedback: ${params.useful ? '+useful' : '-not useful'}`,
469
+ text: `Feedback: ${params.useful ? '+useful' : '-not useful'}${hebbianUpdated > 0 ? ` (${hebbianUpdated} association${hebbianUpdated > 1 ? 's' : ''} ${params.useful ? 'strengthened' : 'weakened'})` : ''}`,
399
470
  }],
400
471
  };
401
472
  }
@@ -609,6 +680,7 @@ Use this at the start of every session or after compaction to pick up where you
609
680
  minScore: 0.05,
610
681
  useReranker: true,
611
682
  useExpansion: true,
683
+ workspace: process.env.AWM_WORKSPACE ?? undefined,
612
684
  });
613
685
  recalledMemories = results.map(r => ({
614
686
  id: r.engram.id,
@@ -919,6 +991,7 @@ This ensures your state is saved before you start, and primes recall with releva
919
991
  minScore: 0.05,
920
992
  useReranker: true,
921
993
  useExpansion: true,
994
+ workspace: process.env.AWM_WORKSPACE ?? undefined,
922
995
  });
923
996
 
924
997
  if (results.length > 0) {
@@ -30,6 +30,7 @@ const DEFAULT_SALIENCE_FEATURES: SalienceFeatures = {
30
30
 
31
31
  export class EngramStore {
32
32
  private db: Database.Database;
33
+ private walTimer: ReturnType<typeof setInterval> | null = null;
33
34
 
34
35
  constructor(dbPath: string = 'memory.db') {
35
36
  this.db = new Database(dbPath);
@@ -37,7 +38,9 @@ export class EngramStore {
37
38
  this.db.pragma('foreign_keys = ON');
38
39
  this.db.pragma('busy_timeout = 5000');
39
40
  this.db.pragma('synchronous = NORMAL');
41
+ this.db.pragma('wal_autocheckpoint = 1000');
40
42
  this.init();
43
+ this.startWalCheckpointTimer();
41
44
  }
42
45
 
43
46
  /** Expose the raw database handle for the coordination module. */
@@ -63,7 +66,27 @@ export class EngramStore {
63
66
 
64
67
  /** Flush WAL to main database file. */
65
68
  walCheckpoint(): void {
66
- this.db.pragma('wal_checkpoint(TRUNCATE)');
69
+ try {
70
+ this.db.pragma('wal_checkpoint(TRUNCATE)');
71
+ } catch {
72
+ // Checkpoint can fail if another connection holds the DB; non-fatal
73
+ }
74
+ }
75
+
76
+ /** Start periodic WAL checkpoint every 5 minutes to prevent unbounded WAL growth. */
77
+ private startWalCheckpointTimer(): void {
78
+ this.walTimer = setInterval(() => {
79
+ this.walCheckpoint();
80
+ }, 5 * 60 * 1000);
81
+ this.walTimer.unref();
82
+ }
83
+
84
+ /** Stop the WAL checkpoint timer (call before close). */
85
+ stopWalCheckpointTimer(): void {
86
+ if (this.walTimer) {
87
+ clearInterval(this.walTimer);
88
+ this.walTimer = null;
89
+ }
67
90
  }
68
91
 
69
92
  private init(): void {
@@ -212,6 +235,13 @@ export class EngramStore {
212
235
  `);
213
236
  }
214
237
 
238
+ // Migration: add embedding_model for version tracking (prevents drift on model change)
239
+ try {
240
+ this.db.prepare('SELECT embedding_model FROM engrams LIMIT 0').get();
241
+ } catch {
242
+ this.db.exec(`ALTER TABLE engrams ADD COLUMN embedding_model TEXT`);
243
+ }
244
+
215
245
  // Migration: add conscious_state table for checkpointing
216
246
  this.db.exec(`
217
247
  CREATE TABLE IF NOT EXISTS conscious_state (
@@ -299,6 +329,83 @@ export class EngramStore {
299
329
  return (this.db.prepare(query).all(...params) as any[]).map(r => this.rowToEngram(r));
300
330
  }
301
331
 
332
+ /**
333
+ * Get engrams across multiple agents (workspace-scoped recall).
334
+ * Used when workspace mode is enabled for hive memory sharing.
335
+ */
336
+ getEngramsByAgents(agentIds: string[], stage?: EngramStage, includeRetracted: boolean = false): Engram[] {
337
+ if (agentIds.length === 0) return [];
338
+ if (agentIds.length === 1) return this.getEngramsByAgent(agentIds[0], stage, includeRetracted);
339
+
340
+ const placeholders = agentIds.map(() => '?').join(',');
341
+ let query = `SELECT * FROM engrams WHERE agent_id IN (${placeholders})`;
342
+ const params: any[] = [...agentIds];
343
+
344
+ if (stage) {
345
+ query += ' AND stage = ?';
346
+ params.push(stage);
347
+ }
348
+ if (!includeRetracted) {
349
+ query += ' AND retracted = 0';
350
+ }
351
+
352
+ return (this.db.prepare(query).all(...params) as any[]).map(r => this.rowToEngram(r));
353
+ }
354
+
355
+ /**
356
+ * BM25 search across multiple agents (workspace-scoped).
357
+ */
358
+ searchBM25WithRankMultiAgent(agentIds: string[], query: string, limit: number = 10): { engram: Engram; bm25Score: number }[] {
359
+ if (agentIds.length === 0) return [];
360
+ if (agentIds.length === 1) return this.searchBM25WithRank(agentIds[0], query, limit);
361
+
362
+ const sanitized = query
363
+ .replace(/[^\w\s]/g, '')
364
+ .split(/\s+/)
365
+ .filter(w => w.length > 1)
366
+ .map(w => `"${w}"`)
367
+ .join(' OR ');
368
+
369
+ if (!sanitized) return [];
370
+
371
+ try {
372
+ const placeholders = agentIds.map(() => '?').join(',');
373
+ const rows = this.db.prepare(`
374
+ SELECT e.*, rank FROM engrams e
375
+ JOIN engrams_fts ON e.rowid = engrams_fts.rowid
376
+ WHERE engrams_fts MATCH ? AND e.agent_id IN (${placeholders}) AND e.retracted = 0
377
+ ORDER BY rank
378
+ LIMIT ?
379
+ `).all(sanitized, ...agentIds, limit) as any[];
380
+
381
+ return rows.map(r => ({
382
+ engram: this.rowToEngram(r),
383
+ bm25Score: Math.abs(r.rank ?? 0) / (1 + Math.abs(r.rank ?? 0)),
384
+ }));
385
+ } catch {
386
+ return [];
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Get all distinct agent IDs that share a workspace (requires coord_agents table).
392
+ * Returns just the queried agentId if coordination tables don't exist.
393
+ */
394
+ getWorkspaceAgentIds(agentId: string, workspace: string): string[] {
395
+ try {
396
+ const rows = this.db.prepare(
397
+ `SELECT DISTINCT id FROM coord_agents WHERE workspace = ? AND status != 'dead'`
398
+ ).all(workspace) as Array<{ id: string }>;
399
+ const ids = rows.map(r => r.id);
400
+ // Ensure the querying agent is always included
401
+ if (!ids.includes(agentId)) ids.push(agentId);
402
+ return ids;
403
+ } catch {
404
+ // No coordination tables — fall back to single agent
405
+ return [agentId];
406
+ }
407
+ }
408
+
302
409
  /**
303
410
  * Touch an engram: increment access count, update last_accessed, and
304
411
  * nudge confidence upward. Each retrieval is weak evidence the memory
@@ -327,9 +434,13 @@ export class EngramStore {
327
434
  );
328
435
  }
329
436
 
330
- updateEmbedding(id: string, embedding: number[]): void {
437
+ updateEmbedding(id: string, embedding: number[], modelId?: string): void {
331
438
  const blob = Buffer.from(new Float32Array(embedding).buffer);
332
- this.db.prepare('UPDATE engrams SET embedding = ? WHERE id = ?').run(blob, id);
439
+ if (modelId) {
440
+ this.db.prepare('UPDATE engrams SET embedding = ?, embedding_model = ? WHERE id = ?').run(blob, modelId, id);
441
+ } else {
442
+ this.db.prepare('UPDATE engrams SET embedding = ? WHERE id = ?').run(blob, id);
443
+ }
333
444
  }
334
445
 
335
446
  retractEngram(id: string, retractedBy: string | null): void {
@@ -1012,6 +1123,8 @@ export class EngramStore {
1012
1123
  }
1013
1124
 
1014
1125
  close(): void {
1126
+ this.stopWalCheckpointTimer();
1127
+ this.walCheckpoint();
1015
1128
  this.db.close();
1016
1129
  }
1017
1130
  }
@@ -186,6 +186,7 @@ export interface ActivationQuery {
186
186
  internal?: boolean; // Skip access count increment, Hebbian update, and event logging (for system calls)
187
187
  memoryType?: MemoryType; // Filter by memory type (episodic, semantic, procedural)
188
188
  mode?: QueryMode; // Pipeline mode — 'auto' by default
189
+ workspace?: string; // Search across all agents in this workspace (hive mode). If unset, agent-scoped only.
189
190
  }
190
191
 
191
192
  /**