agent-working-memory 0.5.4 → 0.5.6

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 (71) hide show
  1. package/README.md +87 -46
  2. package/dist/api/routes.d.ts.map +1 -1
  3. package/dist/api/routes.js +21 -5
  4. package/dist/api/routes.js.map +1 -1
  5. package/dist/cli.js +67 -67
  6. package/dist/coordination/index.d.ts +11 -0
  7. package/dist/coordination/index.d.ts.map +1 -0
  8. package/dist/coordination/index.js +39 -0
  9. package/dist/coordination/index.js.map +1 -0
  10. package/dist/coordination/mcp-tools.d.ts +8 -0
  11. package/dist/coordination/mcp-tools.d.ts.map +1 -0
  12. package/dist/coordination/mcp-tools.js +216 -0
  13. package/dist/coordination/mcp-tools.js.map +1 -0
  14. package/dist/coordination/routes.d.ts +9 -0
  15. package/dist/coordination/routes.d.ts.map +1 -0
  16. package/dist/coordination/routes.js +434 -0
  17. package/dist/coordination/routes.js.map +1 -0
  18. package/dist/coordination/schema.d.ts +12 -0
  19. package/dist/coordination/schema.d.ts.map +1 -0
  20. package/dist/coordination/schema.js +91 -0
  21. package/dist/coordination/schema.js.map +1 -0
  22. package/dist/coordination/schemas.d.ts +208 -0
  23. package/dist/coordination/schemas.d.ts.map +1 -0
  24. package/dist/coordination/schemas.js +109 -0
  25. package/dist/coordination/schemas.js.map +1 -0
  26. package/dist/coordination/stale.d.ts +25 -0
  27. package/dist/coordination/stale.d.ts.map +1 -0
  28. package/dist/coordination/stale.js +53 -0
  29. package/dist/coordination/stale.js.map +1 -0
  30. package/dist/index.js +21 -3
  31. package/dist/index.js.map +1 -1
  32. package/dist/mcp.js +90 -79
  33. package/dist/mcp.js.map +1 -1
  34. package/dist/storage/sqlite.d.ts +3 -0
  35. package/dist/storage/sqlite.d.ts.map +1 -1
  36. package/dist/storage/sqlite.js +285 -281
  37. package/dist/storage/sqlite.js.map +1 -1
  38. package/package.json +55 -55
  39. package/src/api/index.ts +3 -3
  40. package/src/api/routes.ts +551 -536
  41. package/src/cli.ts +397 -397
  42. package/src/coordination/index.ts +47 -0
  43. package/src/coordination/mcp-tools.ts +313 -0
  44. package/src/coordination/routes.ts +656 -0
  45. package/src/coordination/schema.ts +94 -0
  46. package/src/coordination/schemas.ts +136 -0
  47. package/src/coordination/stale.ts +89 -0
  48. package/src/core/decay.ts +63 -63
  49. package/src/core/embeddings.ts +88 -88
  50. package/src/core/hebbian.ts +93 -93
  51. package/src/core/index.ts +5 -5
  52. package/src/core/logger.ts +36 -36
  53. package/src/core/query-expander.ts +66 -66
  54. package/src/core/reranker.ts +101 -101
  55. package/src/engine/activation.ts +656 -656
  56. package/src/engine/connections.ts +103 -103
  57. package/src/engine/consolidation-scheduler.ts +125 -125
  58. package/src/engine/eval.ts +102 -102
  59. package/src/engine/eviction.ts +101 -101
  60. package/src/engine/index.ts +8 -8
  61. package/src/engine/retraction.ts +100 -100
  62. package/src/engine/staging.ts +74 -74
  63. package/src/index.ts +137 -121
  64. package/src/mcp.ts +1024 -1013
  65. package/src/storage/index.ts +3 -3
  66. package/src/storage/sqlite.ts +968 -963
  67. package/src/types/agent.ts +67 -67
  68. package/src/types/checkpoint.ts +46 -46
  69. package/src/types/engram.ts +217 -217
  70. package/src/types/eval.ts +100 -100
  71. package/src/types/index.ts +6 -6
@@ -27,119 +27,123 @@ export class EngramStore {
27
27
  this.db.pragma('foreign_keys = ON');
28
28
  this.init();
29
29
  }
30
+ /** Expose the raw database handle for the coordination module. */
31
+ getDb() {
32
+ return this.db;
33
+ }
30
34
  init() {
31
- this.db.exec(`
32
- CREATE TABLE IF NOT EXISTS engrams (
33
- id TEXT PRIMARY KEY,
34
- agent_id TEXT NOT NULL,
35
- concept TEXT NOT NULL,
36
- content TEXT NOT NULL,
37
- embedding BLOB,
38
- confidence REAL NOT NULL DEFAULT 0.5,
39
- salience REAL NOT NULL DEFAULT 0.5,
40
- access_count INTEGER NOT NULL DEFAULT 0,
41
- last_accessed TEXT NOT NULL,
42
- created_at TEXT NOT NULL,
43
- salience_features TEXT NOT NULL DEFAULT '{}',
44
- reason_codes TEXT NOT NULL DEFAULT '[]',
45
- stage TEXT NOT NULL DEFAULT 'active',
46
- ttl INTEGER,
47
- retracted INTEGER NOT NULL DEFAULT 0,
48
- retracted_by TEXT,
49
- retracted_at TEXT,
50
- tags TEXT NOT NULL DEFAULT '[]'
51
- );
52
-
53
- CREATE INDEX IF NOT EXISTS idx_engrams_agent ON engrams(agent_id);
54
- CREATE INDEX IF NOT EXISTS idx_engrams_stage ON engrams(agent_id, stage);
55
- CREATE INDEX IF NOT EXISTS idx_engrams_concept ON engrams(concept);
56
- CREATE INDEX IF NOT EXISTS idx_engrams_retracted ON engrams(agent_id, retracted);
57
-
58
- CREATE TABLE IF NOT EXISTS associations (
59
- id TEXT PRIMARY KEY,
60
- from_engram_id TEXT NOT NULL REFERENCES engrams(id) ON DELETE CASCADE,
61
- to_engram_id TEXT NOT NULL REFERENCES engrams(id) ON DELETE CASCADE,
62
- weight REAL NOT NULL DEFAULT 0.1,
63
- confidence REAL NOT NULL DEFAULT 0.5,
64
- type TEXT NOT NULL DEFAULT 'hebbian',
65
- activation_count INTEGER NOT NULL DEFAULT 0,
66
- created_at TEXT NOT NULL,
67
- last_activated TEXT NOT NULL
68
- );
69
-
70
- CREATE INDEX IF NOT EXISTS idx_assoc_from ON associations(from_engram_id);
71
- CREATE INDEX IF NOT EXISTS idx_assoc_to ON associations(to_engram_id);
72
- CREATE UNIQUE INDEX IF NOT EXISTS idx_assoc_pair ON associations(from_engram_id, to_engram_id);
73
-
74
- CREATE TABLE IF NOT EXISTS agents (
75
- id TEXT PRIMARY KEY,
76
- name TEXT NOT NULL,
77
- created_at TEXT NOT NULL,
78
- config TEXT NOT NULL DEFAULT '{}'
79
- );
80
-
81
- -- FTS5 for full-text search (BM25 ranking built in)
82
- CREATE VIRTUAL TABLE IF NOT EXISTS engrams_fts USING fts5(
83
- concept, content, tags,
84
- content=engrams,
85
- content_rowid=rowid
86
- );
87
-
88
- -- Triggers to keep FTS in sync
89
- CREATE TRIGGER IF NOT EXISTS engrams_ai AFTER INSERT ON engrams BEGIN
90
- INSERT INTO engrams_fts(rowid, concept, content, tags) VALUES (new.rowid, new.concept, new.content, new.tags);
91
- END;
92
- CREATE TRIGGER IF NOT EXISTS engrams_ad AFTER DELETE ON engrams BEGIN
93
- INSERT INTO engrams_fts(engrams_fts, rowid, concept, content, tags) VALUES('delete', old.rowid, old.concept, old.content, old.tags);
94
- END;
95
- CREATE TRIGGER IF NOT EXISTS engrams_au AFTER UPDATE ON engrams BEGIN
96
- INSERT INTO engrams_fts(engrams_fts, rowid, concept, content, tags) VALUES('delete', old.rowid, old.concept, old.content, old.tags);
97
- INSERT INTO engrams_fts(rowid, concept, content, tags) VALUES (new.rowid, new.concept, new.content, new.tags);
98
- END;
99
-
100
- -- Eval event logs
101
- CREATE TABLE IF NOT EXISTS activation_events (
102
- id TEXT PRIMARY KEY,
103
- agent_id TEXT NOT NULL,
104
- timestamp TEXT NOT NULL,
105
- context TEXT NOT NULL,
106
- results_returned INTEGER NOT NULL,
107
- top_score REAL,
108
- latency_ms REAL NOT NULL,
109
- engram_ids TEXT NOT NULL DEFAULT '[]'
110
- );
111
-
112
- CREATE TABLE IF NOT EXISTS staging_events (
113
- engram_id TEXT NOT NULL,
114
- agent_id TEXT NOT NULL,
115
- action TEXT NOT NULL,
116
- resonance_score REAL,
117
- timestamp TEXT NOT NULL,
118
- age_ms INTEGER NOT NULL
119
- );
120
-
121
- CREATE TABLE IF NOT EXISTS retrieval_feedback (
122
- id TEXT PRIMARY KEY,
123
- activation_event_id TEXT,
124
- engram_id TEXT NOT NULL,
125
- useful INTEGER NOT NULL,
126
- context TEXT,
127
- timestamp TEXT NOT NULL
128
- );
129
-
130
- CREATE TABLE IF NOT EXISTS episodes (
131
- id TEXT PRIMARY KEY,
132
- agent_id TEXT NOT NULL,
133
- label TEXT NOT NULL,
134
- embedding BLOB,
135
- engram_count INTEGER NOT NULL DEFAULT 0,
136
- start_time TEXT NOT NULL,
137
- end_time TEXT NOT NULL,
138
- created_at TEXT NOT NULL
139
- );
140
-
141
- CREATE INDEX IF NOT EXISTS idx_episodes_agent ON episodes(agent_id);
142
- CREATE INDEX IF NOT EXISTS idx_episodes_time ON episodes(agent_id, end_time);
35
+ this.db.exec(`
36
+ CREATE TABLE IF NOT EXISTS engrams (
37
+ id TEXT PRIMARY KEY,
38
+ agent_id TEXT NOT NULL,
39
+ concept TEXT NOT NULL,
40
+ content TEXT NOT NULL,
41
+ embedding BLOB,
42
+ confidence REAL NOT NULL DEFAULT 0.5,
43
+ salience REAL NOT NULL DEFAULT 0.5,
44
+ access_count INTEGER NOT NULL DEFAULT 0,
45
+ last_accessed TEXT NOT NULL,
46
+ created_at TEXT NOT NULL,
47
+ salience_features TEXT NOT NULL DEFAULT '{}',
48
+ reason_codes TEXT NOT NULL DEFAULT '[]',
49
+ stage TEXT NOT NULL DEFAULT 'active',
50
+ ttl INTEGER,
51
+ retracted INTEGER NOT NULL DEFAULT 0,
52
+ retracted_by TEXT,
53
+ retracted_at TEXT,
54
+ tags TEXT NOT NULL DEFAULT '[]'
55
+ );
56
+
57
+ CREATE INDEX IF NOT EXISTS idx_engrams_agent ON engrams(agent_id);
58
+ CREATE INDEX IF NOT EXISTS idx_engrams_stage ON engrams(agent_id, stage);
59
+ CREATE INDEX IF NOT EXISTS idx_engrams_concept ON engrams(concept);
60
+ CREATE INDEX IF NOT EXISTS idx_engrams_retracted ON engrams(agent_id, retracted);
61
+
62
+ CREATE TABLE IF NOT EXISTS associations (
63
+ id TEXT PRIMARY KEY,
64
+ from_engram_id TEXT NOT NULL REFERENCES engrams(id) ON DELETE CASCADE,
65
+ to_engram_id TEXT NOT NULL REFERENCES engrams(id) ON DELETE CASCADE,
66
+ weight REAL NOT NULL DEFAULT 0.1,
67
+ confidence REAL NOT NULL DEFAULT 0.5,
68
+ type TEXT NOT NULL DEFAULT 'hebbian',
69
+ activation_count INTEGER NOT NULL DEFAULT 0,
70
+ created_at TEXT NOT NULL,
71
+ last_activated TEXT NOT NULL
72
+ );
73
+
74
+ CREATE INDEX IF NOT EXISTS idx_assoc_from ON associations(from_engram_id);
75
+ CREATE INDEX IF NOT EXISTS idx_assoc_to ON associations(to_engram_id);
76
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_assoc_pair ON associations(from_engram_id, to_engram_id);
77
+
78
+ CREATE TABLE IF NOT EXISTS agents (
79
+ id TEXT PRIMARY KEY,
80
+ name TEXT NOT NULL,
81
+ created_at TEXT NOT NULL,
82
+ config TEXT NOT NULL DEFAULT '{}'
83
+ );
84
+
85
+ -- FTS5 for full-text search (BM25 ranking built in)
86
+ CREATE VIRTUAL TABLE IF NOT EXISTS engrams_fts USING fts5(
87
+ concept, content, tags,
88
+ content=engrams,
89
+ content_rowid=rowid
90
+ );
91
+
92
+ -- Triggers to keep FTS in sync
93
+ CREATE TRIGGER IF NOT EXISTS engrams_ai AFTER INSERT ON engrams BEGIN
94
+ INSERT INTO engrams_fts(rowid, concept, content, tags) VALUES (new.rowid, new.concept, new.content, new.tags);
95
+ END;
96
+ CREATE TRIGGER IF NOT EXISTS engrams_ad AFTER DELETE ON engrams BEGIN
97
+ INSERT INTO engrams_fts(engrams_fts, rowid, concept, content, tags) VALUES('delete', old.rowid, old.concept, old.content, old.tags);
98
+ END;
99
+ CREATE TRIGGER IF NOT EXISTS engrams_au AFTER UPDATE ON engrams BEGIN
100
+ INSERT INTO engrams_fts(engrams_fts, rowid, concept, content, tags) VALUES('delete', old.rowid, old.concept, old.content, old.tags);
101
+ INSERT INTO engrams_fts(rowid, concept, content, tags) VALUES (new.rowid, new.concept, new.content, new.tags);
102
+ END;
103
+
104
+ -- Eval event logs
105
+ CREATE TABLE IF NOT EXISTS activation_events (
106
+ id TEXT PRIMARY KEY,
107
+ agent_id TEXT NOT NULL,
108
+ timestamp TEXT NOT NULL,
109
+ context TEXT NOT NULL,
110
+ results_returned INTEGER NOT NULL,
111
+ top_score REAL,
112
+ latency_ms REAL NOT NULL,
113
+ engram_ids TEXT NOT NULL DEFAULT '[]'
114
+ );
115
+
116
+ CREATE TABLE IF NOT EXISTS staging_events (
117
+ engram_id TEXT NOT NULL,
118
+ agent_id TEXT NOT NULL,
119
+ action TEXT NOT NULL,
120
+ resonance_score REAL,
121
+ timestamp TEXT NOT NULL,
122
+ age_ms INTEGER NOT NULL
123
+ );
124
+
125
+ CREATE TABLE IF NOT EXISTS retrieval_feedback (
126
+ id TEXT PRIMARY KEY,
127
+ activation_event_id TEXT,
128
+ engram_id TEXT NOT NULL,
129
+ useful INTEGER NOT NULL,
130
+ context TEXT,
131
+ timestamp TEXT NOT NULL
132
+ );
133
+
134
+ CREATE TABLE IF NOT EXISTS episodes (
135
+ id TEXT PRIMARY KEY,
136
+ agent_id TEXT NOT NULL,
137
+ label TEXT NOT NULL,
138
+ embedding BLOB,
139
+ engram_count INTEGER NOT NULL DEFAULT 0,
140
+ start_time TEXT NOT NULL,
141
+ end_time TEXT NOT NULL,
142
+ created_at TEXT NOT NULL
143
+ );
144
+
145
+ CREATE INDEX IF NOT EXISTS idx_episodes_agent ON episodes(agent_id);
146
+ CREATE INDEX IF NOT EXISTS idx_episodes_time ON episodes(agent_id, end_time);
143
147
  `);
144
148
  // Migration: add episode_id column if missing
145
149
  try {
@@ -153,10 +157,10 @@ export class EngramStore {
153
157
  this.db.prepare('SELECT task_status FROM engrams LIMIT 0').get();
154
158
  }
155
159
  catch {
156
- this.db.exec(`
157
- ALTER TABLE engrams ADD COLUMN task_status TEXT;
158
- ALTER TABLE engrams ADD COLUMN task_priority TEXT;
159
- ALTER TABLE engrams ADD COLUMN blocked_by TEXT;
160
+ this.db.exec(`
161
+ ALTER TABLE engrams ADD COLUMN task_status TEXT;
162
+ ALTER TABLE engrams ADD COLUMN task_priority TEXT;
163
+ ALTER TABLE engrams ADD COLUMN blocked_by TEXT;
160
164
  `);
161
165
  this.db.exec('CREATE INDEX IF NOT EXISTS idx_engrams_task ON engrams(agent_id, task_status)');
162
166
  }
@@ -165,29 +169,29 @@ export class EngramStore {
165
169
  this.db.prepare('SELECT memory_class FROM engrams LIMIT 0').get();
166
170
  }
167
171
  catch {
168
- this.db.exec(`
169
- ALTER TABLE engrams ADD COLUMN memory_class TEXT NOT NULL DEFAULT 'working';
170
- ALTER TABLE engrams ADD COLUMN superseded_by TEXT;
171
- ALTER TABLE engrams ADD COLUMN supersedes TEXT;
172
+ this.db.exec(`
173
+ ALTER TABLE engrams ADD COLUMN memory_class TEXT NOT NULL DEFAULT 'working';
174
+ ALTER TABLE engrams ADD COLUMN superseded_by TEXT;
175
+ ALTER TABLE engrams ADD COLUMN supersedes TEXT;
172
176
  `);
173
177
  }
174
178
  // Migration: add conscious_state table for checkpointing
175
- this.db.exec(`
176
- CREATE TABLE IF NOT EXISTS conscious_state (
177
- agent_id TEXT PRIMARY KEY,
178
- last_write_id TEXT,
179
- last_recall_context TEXT,
180
- last_recall_ids TEXT NOT NULL DEFAULT '[]',
181
- last_activity_at TEXT NOT NULL DEFAULT (datetime('now')),
182
- write_count_since_consolidation INTEGER NOT NULL DEFAULT 0,
183
- recall_count_since_consolidation INTEGER NOT NULL DEFAULT 0,
184
- execution_state TEXT,
185
- checkpoint_at TEXT,
186
- last_consolidation_at TEXT,
187
- last_mini_consolidation_at TEXT,
188
- consolidation_cycle_count INTEGER NOT NULL DEFAULT 0,
189
- updated_at TEXT NOT NULL DEFAULT (datetime('now'))
190
- )
179
+ this.db.exec(`
180
+ CREATE TABLE IF NOT EXISTS conscious_state (
181
+ agent_id TEXT PRIMARY KEY,
182
+ last_write_id TEXT,
183
+ last_recall_context TEXT,
184
+ last_recall_ids TEXT NOT NULL DEFAULT '[]',
185
+ last_activity_at TEXT NOT NULL DEFAULT (datetime('now')),
186
+ write_count_since_consolidation INTEGER NOT NULL DEFAULT 0,
187
+ recall_count_since_consolidation INTEGER NOT NULL DEFAULT 0,
188
+ execution_state TEXT,
189
+ checkpoint_at TEXT,
190
+ last_consolidation_at TEXT,
191
+ last_mini_consolidation_at TEXT,
192
+ consolidation_cycle_count INTEGER NOT NULL DEFAULT 0,
193
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
194
+ )
191
195
  `);
192
196
  // Migration: add consolidation_cycle_count if missing (existing DBs)
193
197
  try {
@@ -202,11 +206,11 @@ export class EngramStore {
202
206
  const embeddingBlob = input.embedding
203
207
  ? Buffer.from(new Float32Array(input.embedding).buffer)
204
208
  : null;
205
- this.db.prepare(`
206
- INSERT INTO engrams (id, agent_id, concept, content, embedding, confidence, salience,
207
- access_count, last_accessed, created_at, salience_features, reason_codes, stage, tags, episode_id,
208
- ttl, memory_class, supersedes, task_status, task_priority, blocked_by)
209
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, 'active', ?, ?, ?, ?, ?, ?, ?, ?)
209
+ this.db.prepare(`
210
+ INSERT INTO engrams (id, agent_id, concept, content, embedding, confidence, salience,
211
+ access_count, last_accessed, created_at, salience_features, reason_codes, stage, tags, episode_id,
212
+ ttl, memory_class, supersedes, task_status, task_priority, blocked_by)
213
+ VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, 'active', ?, ?, ?, ?, ?, ?, ?, ?)
210
214
  `).run(id, input.agentId, input.concept, input.content, embeddingBlob, input.confidence ?? 0.5, input.salience ?? 0.5, now, now, JSON.stringify(input.salienceFeatures ?? DEFAULT_SALIENCE_FEATURES), JSON.stringify(input.reasonCodes ?? []), JSON.stringify(input.tags ?? []), input.episodeId ?? null, input.ttl ?? null, input.memoryClass ?? 'working', input.supersedes ?? null, input.taskStatus ?? null, input.taskPriority ?? null, input.blockedBy ?? null);
211
215
  return this.getEngram(id);
212
216
  }
@@ -227,8 +231,8 @@ export class EngramStore {
227
231
  return this.db.prepare(query).all(...params).map(r => this.rowToEngram(r));
228
232
  }
229
233
  touchEngram(id) {
230
- this.db.prepare(`
231
- UPDATE engrams SET access_count = access_count + 1, last_accessed = ? WHERE id = ?
234
+ this.db.prepare(`
235
+ UPDATE engrams SET access_count = access_count + 1, last_accessed = ? WHERE id = ?
232
236
  `).run(new Date().toISOString(), id);
233
237
  }
234
238
  updateStage(id, stage) {
@@ -242,8 +246,8 @@ export class EngramStore {
242
246
  this.db.prepare('UPDATE engrams SET embedding = ? WHERE id = ?').run(blob, id);
243
247
  }
244
248
  retractEngram(id, retractedBy) {
245
- this.db.prepare(`
246
- UPDATE engrams SET retracted = 1, retracted_by = ?, retracted_at = ? WHERE id = ?
249
+ this.db.prepare(`
250
+ UPDATE engrams SET retracted = 1, retracted_by = ?, retracted_at = ? WHERE id = ?
247
251
  `).run(retractedBy, new Date().toISOString(), id);
248
252
  }
249
253
  deleteEngram(id) {
@@ -258,20 +262,20 @@ export class EngramStore {
258
262
  let count = 0;
259
263
  const shiftSec = Math.round(ms / 1000);
260
264
  // Shift engram timestamps
261
- const r1 = this.db.prepare(`
262
- UPDATE engrams SET
263
- created_at = datetime(created_at, '-${shiftSec} seconds'),
264
- last_accessed = datetime(last_accessed, '-${shiftSec} seconds')
265
- WHERE agent_id = ?
265
+ const r1 = this.db.prepare(`
266
+ UPDATE engrams SET
267
+ created_at = datetime(created_at, '-${shiftSec} seconds'),
268
+ last_accessed = datetime(last_accessed, '-${shiftSec} seconds')
269
+ WHERE agent_id = ?
266
270
  `).run(agentId);
267
271
  count += r1.changes;
268
272
  // Shift association timestamps
269
- const r2 = this.db.prepare(`
270
- UPDATE associations SET
271
- created_at = datetime(created_at, '-${shiftSec} seconds'),
272
- last_activated = datetime(last_activated, '-${shiftSec} seconds')
273
- WHERE from_engram_id IN (SELECT id FROM engrams WHERE agent_id = ?)
274
- OR to_engram_id IN (SELECT id FROM engrams WHERE agent_id = ?)
273
+ const r2 = this.db.prepare(`
274
+ UPDATE associations SET
275
+ created_at = datetime(created_at, '-${shiftSec} seconds'),
276
+ last_activated = datetime(last_activated, '-${shiftSec} seconds')
277
+ WHERE from_engram_id IN (SELECT id FROM engrams WHERE agent_id = ?)
278
+ OR to_engram_id IN (SELECT id FROM engrams WHERE agent_id = ?)
275
279
  `).run(agentId, agentId);
276
280
  count += r2.changes;
277
281
  return count;
@@ -296,12 +300,12 @@ export class EngramStore {
296
300
  if (!sanitized)
297
301
  return [];
298
302
  try {
299
- const rows = this.db.prepare(`
300
- SELECT e.*, rank FROM engrams e
301
- JOIN engrams_fts ON e.rowid = engrams_fts.rowid
302
- WHERE engrams_fts MATCH ? AND e.agent_id = ? AND e.retracted = 0
303
- ORDER BY rank
304
- LIMIT ?
303
+ const rows = this.db.prepare(`
304
+ SELECT e.*, rank FROM engrams e
305
+ JOIN engrams_fts ON e.rowid = engrams_fts.rowid
306
+ WHERE engrams_fts MATCH ? AND e.agent_id = ? AND e.retracted = 0
307
+ ORDER BY rank
308
+ LIMIT ?
305
309
  `).all(sanitized, agentId, limit);
306
310
  return rows.map(r => ({
307
311
  engram: this.rowToEngram(r),
@@ -381,14 +385,14 @@ export class EngramStore {
381
385
  sql += ' AND task_status = ?';
382
386
  params.push(status);
383
387
  }
384
- sql += ` ORDER BY
385
- CASE task_priority
386
- WHEN 'urgent' THEN 0
387
- WHEN 'high' THEN 1
388
- WHEN 'medium' THEN 2
389
- WHEN 'low' THEN 3
390
- ELSE 4
391
- END,
388
+ sql += ` ORDER BY
389
+ CASE task_priority
390
+ WHEN 'urgent' THEN 0
391
+ WHEN 'high' THEN 1
392
+ WHEN 'medium' THEN 2
393
+ WHEN 'low' THEN 3
394
+ ELSE 4
395
+ END,
392
396
  created_at DESC`;
393
397
  return this.db.prepare(sql).all(...params).map(r => this.rowToEngram(r));
394
398
  }
@@ -396,20 +400,20 @@ export class EngramStore {
396
400
  * Get the next actionable task — highest priority that's not blocked or done.
397
401
  */
398
402
  getNextTask(agentId) {
399
- const row = this.db.prepare(`
400
- SELECT * FROM engrams
401
- WHERE agent_id = ? AND task_status IN ('open', 'in_progress') AND retracted = 0
402
- ORDER BY
403
- CASE task_status WHEN 'in_progress' THEN 0 ELSE 1 END,
404
- CASE task_priority
405
- WHEN 'urgent' THEN 0
406
- WHEN 'high' THEN 1
407
- WHEN 'medium' THEN 2
408
- WHEN 'low' THEN 3
409
- ELSE 4
410
- END,
411
- created_at ASC
412
- LIMIT 1
403
+ const row = this.db.prepare(`
404
+ SELECT * FROM engrams
405
+ WHERE agent_id = ? AND task_status IN ('open', 'in_progress') AND retracted = 0
406
+ ORDER BY
407
+ CASE task_status WHEN 'in_progress' THEN 0 ELSE 1 END,
408
+ CASE task_priority
409
+ WHEN 'urgent' THEN 0
410
+ WHEN 'high' THEN 1
411
+ WHEN 'medium' THEN 2
412
+ WHEN 'low' THEN 3
413
+ ELSE 4
414
+ END,
415
+ created_at ASC
416
+ LIMIT 1
413
417
  `).get(agentId);
414
418
  return row ? this.rowToEngram(row) : null;
415
419
  }
@@ -436,11 +440,11 @@ export class EngramStore {
436
440
  upsertAssociation(fromId, toId, weight, type = 'hebbian', confidence = 0.5) {
437
441
  const now = new Date().toISOString();
438
442
  const id = randomUUID();
439
- this.db.prepare(`
440
- INSERT INTO associations (id, from_engram_id, to_engram_id, weight, confidence, type, activation_count, created_at, last_activated)
441
- VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)
442
- ON CONFLICT(from_engram_id, to_engram_id) DO UPDATE SET
443
- weight = ?, confidence = ?, last_activated = ?, activation_count = activation_count + 1
443
+ this.db.prepare(`
444
+ INSERT INTO associations (id, from_engram_id, to_engram_id, weight, confidence, type, activation_count, created_at, last_activated)
445
+ VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)
446
+ ON CONFLICT(from_engram_id, to_engram_id) DO UPDATE SET
447
+ weight = ?, confidence = ?, last_activated = ?, activation_count = activation_count + 1
444
448
  `).run(id, fromId, toId, weight, confidence, type, now, now, weight, confidence, now);
445
449
  return this.getAssociation(fromId, toId);
446
450
  }
@@ -468,22 +472,22 @@ export class EngramStore {
468
472
  this.db.prepare('DELETE FROM associations WHERE id = ?').run(id);
469
473
  }
470
474
  getAllAssociations(agentId) {
471
- const rows = this.db.prepare(`
472
- SELECT a.* FROM associations a
473
- JOIN engrams e ON a.from_engram_id = e.id
474
- WHERE e.agent_id = ?
475
+ const rows = this.db.prepare(`
476
+ SELECT a.* FROM associations a
477
+ JOIN engrams e ON a.from_engram_id = e.id
478
+ WHERE e.agent_id = ?
475
479
  `).all(agentId);
476
480
  return rows.map(r => this.rowToAssociation(r));
477
481
  }
478
482
  // --- Eviction ---
479
483
  getEvictionCandidates(agentId, limit) {
480
484
  // Lowest combined score: low salience + low access + low confidence + oldest
481
- const rows = this.db.prepare(`
482
- SELECT * FROM engrams
483
- WHERE agent_id = ? AND stage = 'active' AND retracted = 0
484
- ORDER BY (salience * 0.3 + confidence * 0.3 + (CAST(access_count AS REAL) / (access_count + 5)) * 0.2 +
485
- (1.0 / (1.0 + (julianday('now') - julianday(last_accessed)))) * 0.2) ASC
486
- LIMIT ?
485
+ const rows = this.db.prepare(`
486
+ SELECT * FROM engrams
487
+ WHERE agent_id = ? AND stage = 'active' AND retracted = 0
488
+ ORDER BY (salience * 0.3 + confidence * 0.3 + (CAST(access_count AS REAL) / (access_count + 5)) * 0.2 +
489
+ (1.0 / (1.0 + (julianday('now') - julianday(last_accessed)))) * 0.2) ASC
490
+ LIMIT ?
487
491
  `).all(agentId, limit);
488
492
  return rows.map(r => this.rowToEngram(r));
489
493
  }
@@ -498,8 +502,8 @@ export class EngramStore {
498
502
  // --- Staging buffer ---
499
503
  getExpiredStaging() {
500
504
  const now = Date.now();
501
- const rows = this.db.prepare(`
502
- SELECT * FROM engrams WHERE stage = 'staging' AND ttl IS NOT NULL
505
+ const rows = this.db.prepare(`
506
+ SELECT * FROM engrams WHERE stage = 'staging' AND ttl IS NOT NULL
503
507
  `).all();
504
508
  return rows
505
509
  .map(r => this.rowToEngram(r))
@@ -507,53 +511,53 @@ export class EngramStore {
507
511
  }
508
512
  // --- Eval event logging ---
509
513
  logActivationEvent(event) {
510
- this.db.prepare(`
511
- INSERT INTO activation_events (id, agent_id, timestamp, context, results_returned, top_score, latency_ms, engram_ids)
512
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
514
+ this.db.prepare(`
515
+ INSERT INTO activation_events (id, agent_id, timestamp, context, results_returned, top_score, latency_ms, engram_ids)
516
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
513
517
  `).run(event.id, event.agentId, event.timestamp.toISOString(), event.context, event.resultsReturned, event.topScore, event.latencyMs, JSON.stringify(event.engramIds));
514
518
  }
515
519
  logStagingEvent(event) {
516
- this.db.prepare(`
517
- INSERT INTO staging_events (engram_id, agent_id, action, resonance_score, timestamp, age_ms)
518
- VALUES (?, ?, ?, ?, ?, ?)
520
+ this.db.prepare(`
521
+ INSERT INTO staging_events (engram_id, agent_id, action, resonance_score, timestamp, age_ms)
522
+ VALUES (?, ?, ?, ?, ?, ?)
519
523
  `).run(event.engramId, event.agentId, event.action, event.resonanceScore, event.timestamp.toISOString(), event.ageMs);
520
524
  }
521
525
  logRetrievalFeedback(activationEventId, engramId, useful, context) {
522
- this.db.prepare(`
523
- INSERT INTO retrieval_feedback (id, activation_event_id, engram_id, useful, context, timestamp)
524
- VALUES (?, ?, ?, ?, ?, ?)
526
+ this.db.prepare(`
527
+ INSERT INTO retrieval_feedback (id, activation_event_id, engram_id, useful, context, timestamp)
528
+ VALUES (?, ?, ?, ?, ?, ?)
525
529
  `).run(randomUUID(), activationEventId, engramId, useful ? 1 : 0, context, new Date().toISOString());
526
530
  }
527
531
  // --- Eval metrics queries ---
528
532
  getRetrievalPrecision(agentId, windowHours = 24) {
529
533
  const since = new Date(Date.now() - windowHours * 3600_000).toISOString();
530
- const row = this.db.prepare(`
531
- SELECT
532
- COUNT(CASE WHEN useful = 1 THEN 1 END) as useful_count,
533
- COUNT(*) as total_count
534
- FROM retrieval_feedback rf
535
- LEFT JOIN activation_events ae ON rf.activation_event_id = ae.id
536
- JOIN engrams e ON rf.engram_id = e.id
537
- WHERE e.agent_id = ? AND rf.timestamp > ?
534
+ const row = this.db.prepare(`
535
+ SELECT
536
+ COUNT(CASE WHEN useful = 1 THEN 1 END) as useful_count,
537
+ COUNT(*) as total_count
538
+ FROM retrieval_feedback rf
539
+ LEFT JOIN activation_events ae ON rf.activation_event_id = ae.id
540
+ JOIN engrams e ON rf.engram_id = e.id
541
+ WHERE e.agent_id = ? AND rf.timestamp > ?
538
542
  `).get(agentId, since);
539
543
  return row.total_count > 0 ? row.useful_count / row.total_count : 0;
540
544
  }
541
545
  getStagingMetrics(agentId) {
542
- const row = this.db.prepare(`
543
- SELECT
544
- COUNT(CASE WHEN action = 'promoted' THEN 1 END) as promoted,
545
- COUNT(CASE WHEN action = 'discarded' THEN 1 END) as discarded,
546
- COUNT(CASE WHEN action = 'expired' THEN 1 END) as expired
547
- FROM staging_events WHERE agent_id = ?
546
+ const row = this.db.prepare(`
547
+ SELECT
548
+ COUNT(CASE WHEN action = 'promoted' THEN 1 END) as promoted,
549
+ COUNT(CASE WHEN action = 'discarded' THEN 1 END) as discarded,
550
+ COUNT(CASE WHEN action = 'expired' THEN 1 END) as expired
551
+ FROM staging_events WHERE agent_id = ?
548
552
  `).get(agentId);
549
553
  return { promoted: row.promoted, discarded: row.discarded, expired: row.expired };
550
554
  }
551
555
  getActivationStats(agentId, windowHours = 24) {
552
556
  const since = new Date(Date.now() - windowHours * 3600_000).toISOString();
553
- const rows = this.db.prepare(`
554
- SELECT latency_ms FROM activation_events
555
- WHERE agent_id = ? AND timestamp > ?
556
- ORDER BY latency_ms ASC
557
+ const rows = this.db.prepare(`
558
+ SELECT latency_ms FROM activation_events
559
+ WHERE agent_id = ? AND timestamp > ?
560
+ ORDER BY latency_ms ASC
557
561
  `).all(agentId, since);
558
562
  if (rows.length === 0)
559
563
  return { count: 0, avgLatencyMs: 0, p95LatencyMs: 0 };
@@ -621,9 +625,9 @@ export class EngramStore {
621
625
  const embeddingBlob = input.embedding
622
626
  ? Buffer.from(new Float32Array(input.embedding).buffer)
623
627
  : null;
624
- this.db.prepare(`
625
- INSERT INTO episodes (id, agent_id, label, embedding, engram_count, start_time, end_time, created_at)
626
- VALUES (?, ?, ?, ?, 0, ?, ?, ?)
628
+ this.db.prepare(`
629
+ INSERT INTO episodes (id, agent_id, label, embedding, engram_count, start_time, end_time, created_at)
630
+ VALUES (?, ?, ?, ?, 0, ?, ?, ?)
627
631
  `).run(id, input.agentId, input.label, embeddingBlob, now, now, now);
628
632
  return this.getEpisode(id);
629
633
  }
@@ -638,19 +642,19 @@ export class EngramStore {
638
642
  getActiveEpisode(agentId, windowMs = 3600_000) {
639
643
  // Find most recent episode that ended within the time window
640
644
  const cutoff = new Date(Date.now() - windowMs).toISOString();
641
- const row = this.db.prepare(`
642
- SELECT * FROM episodes WHERE agent_id = ? AND end_time > ?
643
- ORDER BY end_time DESC LIMIT 1
645
+ const row = this.db.prepare(`
646
+ SELECT * FROM episodes WHERE agent_id = ? AND end_time > ?
647
+ ORDER BY end_time DESC LIMIT 1
644
648
  `).get(agentId, cutoff);
645
649
  return row ? this.rowToEpisode(row) : null;
646
650
  }
647
651
  addEngramToEpisode(engramId, episodeId) {
648
652
  this.db.prepare('UPDATE engrams SET episode_id = ? WHERE id = ?').run(episodeId, engramId);
649
- this.db.prepare(`
650
- UPDATE episodes SET
651
- engram_count = engram_count + 1,
652
- end_time = MAX(end_time, ?)
653
- WHERE id = ?
653
+ this.db.prepare(`
654
+ UPDATE episodes SET
655
+ engram_count = engram_count + 1,
656
+ end_time = MAX(end_time, ?)
657
+ WHERE id = ?
654
658
  `).run(new Date().toISOString(), episodeId);
655
659
  }
656
660
  getEngramsByEpisode(episodeId) {
@@ -701,49 +705,49 @@ export class EngramStore {
701
705
  // --- Checkpointing ---
702
706
  updateAutoCheckpointWrite(agentId, engramId) {
703
707
  const now = new Date().toISOString();
704
- this.db.prepare(`
705
- INSERT INTO conscious_state (agent_id, last_write_id, last_activity_at, write_count_since_consolidation, updated_at)
706
- VALUES (?, ?, ?, 1, ?)
707
- ON CONFLICT(agent_id) DO UPDATE SET
708
- last_write_id = excluded.last_write_id,
709
- last_activity_at = excluded.last_activity_at,
710
- write_count_since_consolidation = write_count_since_consolidation + 1,
711
- updated_at = excluded.updated_at
708
+ this.db.prepare(`
709
+ INSERT INTO conscious_state (agent_id, last_write_id, last_activity_at, write_count_since_consolidation, updated_at)
710
+ VALUES (?, ?, ?, 1, ?)
711
+ ON CONFLICT(agent_id) DO UPDATE SET
712
+ last_write_id = excluded.last_write_id,
713
+ last_activity_at = excluded.last_activity_at,
714
+ write_count_since_consolidation = write_count_since_consolidation + 1,
715
+ updated_at = excluded.updated_at
712
716
  `).run(agentId, engramId, now, now);
713
717
  }
714
718
  updateAutoCheckpointRecall(agentId, context, engramIds) {
715
719
  const now = new Date().toISOString();
716
- this.db.prepare(`
717
- INSERT INTO conscious_state (agent_id, last_recall_context, last_recall_ids, last_activity_at, recall_count_since_consolidation, updated_at)
718
- VALUES (?, ?, ?, ?, 1, ?)
719
- ON CONFLICT(agent_id) DO UPDATE SET
720
- last_recall_context = excluded.last_recall_context,
721
- last_recall_ids = excluded.last_recall_ids,
722
- last_activity_at = excluded.last_activity_at,
723
- recall_count_since_consolidation = recall_count_since_consolidation + 1,
724
- updated_at = excluded.updated_at
720
+ this.db.prepare(`
721
+ INSERT INTO conscious_state (agent_id, last_recall_context, last_recall_ids, last_activity_at, recall_count_since_consolidation, updated_at)
722
+ VALUES (?, ?, ?, ?, 1, ?)
723
+ ON CONFLICT(agent_id) DO UPDATE SET
724
+ last_recall_context = excluded.last_recall_context,
725
+ last_recall_ids = excluded.last_recall_ids,
726
+ last_activity_at = excluded.last_activity_at,
727
+ recall_count_since_consolidation = recall_count_since_consolidation + 1,
728
+ updated_at = excluded.updated_at
725
729
  `).run(agentId, context, JSON.stringify(engramIds), now, now);
726
730
  }
727
731
  touchActivity(agentId) {
728
732
  const now = new Date().toISOString();
729
- this.db.prepare(`
730
- INSERT INTO conscious_state (agent_id, last_activity_at, updated_at)
731
- VALUES (?, ?, ?)
732
- ON CONFLICT(agent_id) DO UPDATE SET
733
- last_activity_at = excluded.last_activity_at,
734
- updated_at = excluded.updated_at
733
+ this.db.prepare(`
734
+ INSERT INTO conscious_state (agent_id, last_activity_at, updated_at)
735
+ VALUES (?, ?, ?)
736
+ ON CONFLICT(agent_id) DO UPDATE SET
737
+ last_activity_at = excluded.last_activity_at,
738
+ updated_at = excluded.updated_at
735
739
  `).run(agentId, now, now);
736
740
  }
737
741
  saveCheckpoint(agentId, state) {
738
742
  const now = new Date().toISOString();
739
- this.db.prepare(`
740
- INSERT INTO conscious_state (agent_id, execution_state, checkpoint_at, last_activity_at, updated_at)
741
- VALUES (?, ?, ?, ?, ?)
742
- ON CONFLICT(agent_id) DO UPDATE SET
743
- execution_state = excluded.execution_state,
744
- checkpoint_at = excluded.checkpoint_at,
745
- last_activity_at = excluded.last_activity_at,
746
- updated_at = excluded.updated_at
743
+ this.db.prepare(`
744
+ INSERT INTO conscious_state (agent_id, execution_state, checkpoint_at, last_activity_at, updated_at)
745
+ VALUES (?, ?, ?, ?, ?)
746
+ ON CONFLICT(agent_id) DO UPDATE SET
747
+ execution_state = excluded.execution_state,
748
+ checkpoint_at = excluded.checkpoint_at,
749
+ last_activity_at = excluded.last_activity_at,
750
+ updated_at = excluded.updated_at
747
751
  `).run(agentId, JSON.stringify(state), now, now, now);
748
752
  }
749
753
  getCheckpoint(agentId) {
@@ -770,20 +774,20 @@ export class EngramStore {
770
774
  markConsolidation(agentId, mini) {
771
775
  const now = new Date().toISOString();
772
776
  if (mini) {
773
- this.db.prepare(`
774
- UPDATE conscious_state SET last_mini_consolidation_at = ?, updated_at = ? WHERE agent_id = ?
777
+ this.db.prepare(`
778
+ UPDATE conscious_state SET last_mini_consolidation_at = ?, updated_at = ? WHERE agent_id = ?
775
779
  `).run(now, now, agentId);
776
780
  }
777
781
  else {
778
- this.db.prepare(`
779
- UPDATE conscious_state SET
780
- last_consolidation_at = ?,
781
- last_mini_consolidation_at = ?,
782
- write_count_since_consolidation = 0,
783
- recall_count_since_consolidation = 0,
784
- consolidation_cycle_count = consolidation_cycle_count + 1,
785
- updated_at = ?
786
- WHERE agent_id = ?
782
+ this.db.prepare(`
783
+ UPDATE conscious_state SET
784
+ last_consolidation_at = ?,
785
+ last_mini_consolidation_at = ?,
786
+ write_count_since_consolidation = 0,
787
+ recall_count_since_consolidation = 0,
788
+ consolidation_cycle_count = consolidation_cycle_count + 1,
789
+ updated_at = ?
790
+ WHERE agent_id = ?
787
791
  `).run(now, now, now, agentId);
788
792
  }
789
793
  }