agent-working-memory 0.5.5 → 0.6.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 (82) hide show
  1. package/README.md +428 -399
  2. package/dist/api/routes.d.ts.map +1 -1
  3. package/dist/api/routes.js +60 -5
  4. package/dist/api/routes.js.map +1 -1
  5. package/dist/cli.js +468 -68
  6. package/dist/cli.js.map +1 -1
  7. package/dist/coordination/index.d.ts +11 -0
  8. package/dist/coordination/index.d.ts.map +1 -0
  9. package/dist/coordination/index.js +39 -0
  10. package/dist/coordination/index.js.map +1 -0
  11. package/dist/coordination/mcp-tools.d.ts +8 -0
  12. package/dist/coordination/mcp-tools.d.ts.map +1 -0
  13. package/dist/coordination/mcp-tools.js +221 -0
  14. package/dist/coordination/mcp-tools.js.map +1 -0
  15. package/dist/coordination/routes.d.ts +9 -0
  16. package/dist/coordination/routes.d.ts.map +1 -0
  17. package/dist/coordination/routes.js +573 -0
  18. package/dist/coordination/routes.js.map +1 -0
  19. package/dist/coordination/schema.d.ts +12 -0
  20. package/dist/coordination/schema.d.ts.map +1 -0
  21. package/dist/coordination/schema.js +125 -0
  22. package/dist/coordination/schema.js.map +1 -0
  23. package/dist/coordination/schemas.d.ts +227 -0
  24. package/dist/coordination/schemas.d.ts.map +1 -0
  25. package/dist/coordination/schemas.js +125 -0
  26. package/dist/coordination/schemas.js.map +1 -0
  27. package/dist/coordination/stale.d.ts +27 -0
  28. package/dist/coordination/stale.d.ts.map +1 -0
  29. package/dist/coordination/stale.js +58 -0
  30. package/dist/coordination/stale.js.map +1 -0
  31. package/dist/engine/activation.d.ts.map +1 -1
  32. package/dist/engine/activation.js +119 -23
  33. package/dist/engine/activation.js.map +1 -1
  34. package/dist/engine/consolidation.d.ts.map +1 -1
  35. package/dist/engine/consolidation.js +27 -6
  36. package/dist/engine/consolidation.js.map +1 -1
  37. package/dist/index.js +100 -4
  38. package/dist/index.js.map +1 -1
  39. package/dist/mcp.js +149 -80
  40. package/dist/mcp.js.map +1 -1
  41. package/dist/storage/sqlite.d.ts +21 -0
  42. package/dist/storage/sqlite.d.ts.map +1 -1
  43. package/dist/storage/sqlite.js +331 -282
  44. package/dist/storage/sqlite.js.map +1 -1
  45. package/dist/types/engram.d.ts +24 -0
  46. package/dist/types/engram.d.ts.map +1 -1
  47. package/dist/types/engram.js.map +1 -1
  48. package/package.json +57 -55
  49. package/src/api/index.ts +3 -3
  50. package/src/api/routes.ts +600 -536
  51. package/src/cli.ts +850 -397
  52. package/src/coordination/index.ts +47 -0
  53. package/src/coordination/mcp-tools.ts +318 -0
  54. package/src/coordination/routes.ts +846 -0
  55. package/src/coordination/schema.ts +120 -0
  56. package/src/coordination/schemas.ts +155 -0
  57. package/src/coordination/stale.ts +97 -0
  58. package/src/core/decay.ts +63 -63
  59. package/src/core/embeddings.ts +88 -88
  60. package/src/core/hebbian.ts +93 -93
  61. package/src/core/index.ts +5 -5
  62. package/src/core/logger.ts +36 -36
  63. package/src/core/query-expander.ts +66 -66
  64. package/src/core/reranker.ts +101 -101
  65. package/src/engine/activation.ts +758 -656
  66. package/src/engine/connections.ts +103 -103
  67. package/src/engine/consolidation-scheduler.ts +125 -125
  68. package/src/engine/consolidation.ts +29 -6
  69. package/src/engine/eval.ts +102 -102
  70. package/src/engine/eviction.ts +101 -101
  71. package/src/engine/index.ts +8 -8
  72. package/src/engine/retraction.ts +100 -100
  73. package/src/engine/staging.ts +74 -74
  74. package/src/index.ts +208 -121
  75. package/src/mcp.ts +1093 -1013
  76. package/src/storage/index.ts +3 -3
  77. package/src/storage/sqlite.ts +1017 -963
  78. package/src/types/agent.ts +67 -67
  79. package/src/types/checkpoint.ts +46 -46
  80. package/src/types/engram.ts +245 -217
  81. package/src/types/eval.ts +100 -100
  82. package/src/types/index.ts +6 -6
@@ -25,121 +25,147 @@ export class EngramStore {
25
25
  this.db = new Database(dbPath);
26
26
  this.db.pragma('journal_mode = WAL');
27
27
  this.db.pragma('foreign_keys = ON');
28
+ this.db.pragma('busy_timeout = 5000');
29
+ this.db.pragma('synchronous = NORMAL');
28
30
  this.init();
29
31
  }
32
+ /** Expose the raw database handle for the coordination module. */
33
+ getDb() {
34
+ return this.db;
35
+ }
36
+ /** Run PRAGMA quick_check and return true if DB is healthy. */
37
+ integrityCheck() {
38
+ try {
39
+ const rows = this.db.pragma('quick_check');
40
+ const result = rows[0]?.quick_check ?? 'unknown';
41
+ return { ok: result === 'ok', result };
42
+ }
43
+ catch (err) {
44
+ return { ok: false, result: err.message };
45
+ }
46
+ }
47
+ /** Hot backup using SQLite backup API. Returns the backup path. */
48
+ backup(destPath) {
49
+ this.db.backup(destPath);
50
+ }
51
+ /** Flush WAL to main database file. */
52
+ walCheckpoint() {
53
+ this.db.pragma('wal_checkpoint(TRUNCATE)');
54
+ }
30
55
  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);
56
+ this.db.exec(`
57
+ CREATE TABLE IF NOT EXISTS engrams (
58
+ id TEXT PRIMARY KEY,
59
+ agent_id TEXT NOT NULL,
60
+ concept TEXT NOT NULL,
61
+ content TEXT NOT NULL,
62
+ embedding BLOB,
63
+ confidence REAL NOT NULL DEFAULT 0.5,
64
+ salience REAL NOT NULL DEFAULT 0.5,
65
+ access_count INTEGER NOT NULL DEFAULT 0,
66
+ last_accessed TEXT NOT NULL,
67
+ created_at TEXT NOT NULL,
68
+ salience_features TEXT NOT NULL DEFAULT '{}',
69
+ reason_codes TEXT NOT NULL DEFAULT '[]',
70
+ stage TEXT NOT NULL DEFAULT 'active',
71
+ ttl INTEGER,
72
+ retracted INTEGER NOT NULL DEFAULT 0,
73
+ retracted_by TEXT,
74
+ retracted_at TEXT,
75
+ tags TEXT NOT NULL DEFAULT '[]',
76
+ memory_type TEXT NOT NULL DEFAULT 'unclassified'
77
+ );
78
+
79
+ CREATE INDEX IF NOT EXISTS idx_engrams_agent ON engrams(agent_id);
80
+ CREATE INDEX IF NOT EXISTS idx_engrams_stage ON engrams(agent_id, stage);
81
+ CREATE INDEX IF NOT EXISTS idx_engrams_concept ON engrams(concept);
82
+ CREATE INDEX IF NOT EXISTS idx_engrams_retracted ON engrams(agent_id, retracted);
83
+
84
+ CREATE TABLE IF NOT EXISTS associations (
85
+ id TEXT PRIMARY KEY,
86
+ from_engram_id TEXT NOT NULL REFERENCES engrams(id) ON DELETE CASCADE,
87
+ to_engram_id TEXT NOT NULL REFERENCES engrams(id) ON DELETE CASCADE,
88
+ weight REAL NOT NULL DEFAULT 0.1,
89
+ confidence REAL NOT NULL DEFAULT 0.5,
90
+ type TEXT NOT NULL DEFAULT 'hebbian',
91
+ activation_count INTEGER NOT NULL DEFAULT 0,
92
+ created_at TEXT NOT NULL,
93
+ last_activated TEXT NOT NULL
94
+ );
95
+
96
+ CREATE INDEX IF NOT EXISTS idx_assoc_from ON associations(from_engram_id);
97
+ CREATE INDEX IF NOT EXISTS idx_assoc_to ON associations(to_engram_id);
98
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_assoc_pair ON associations(from_engram_id, to_engram_id);
99
+
100
+ CREATE TABLE IF NOT EXISTS agents (
101
+ id TEXT PRIMARY KEY,
102
+ name TEXT NOT NULL,
103
+ created_at TEXT NOT NULL,
104
+ config TEXT NOT NULL DEFAULT '{}'
105
+ );
106
+
107
+ -- FTS5 for full-text search (BM25 ranking built in)
108
+ CREATE VIRTUAL TABLE IF NOT EXISTS engrams_fts USING fts5(
109
+ concept, content, tags,
110
+ content=engrams,
111
+ content_rowid=rowid
112
+ );
113
+
114
+ -- Triggers to keep FTS in sync
115
+ CREATE TRIGGER IF NOT EXISTS engrams_ai AFTER INSERT ON engrams BEGIN
116
+ INSERT INTO engrams_fts(rowid, concept, content, tags) VALUES (new.rowid, new.concept, new.content, new.tags);
117
+ END;
118
+ CREATE TRIGGER IF NOT EXISTS engrams_ad AFTER DELETE ON engrams BEGIN
119
+ INSERT INTO engrams_fts(engrams_fts, rowid, concept, content, tags) VALUES('delete', old.rowid, old.concept, old.content, old.tags);
120
+ END;
121
+ CREATE TRIGGER IF NOT EXISTS engrams_au AFTER UPDATE ON engrams BEGIN
122
+ INSERT INTO engrams_fts(engrams_fts, rowid, concept, content, tags) VALUES('delete', old.rowid, old.concept, old.content, old.tags);
123
+ INSERT INTO engrams_fts(rowid, concept, content, tags) VALUES (new.rowid, new.concept, new.content, new.tags);
124
+ END;
125
+
126
+ -- Eval event logs
127
+ CREATE TABLE IF NOT EXISTS activation_events (
128
+ id TEXT PRIMARY KEY,
129
+ agent_id TEXT NOT NULL,
130
+ timestamp TEXT NOT NULL,
131
+ context TEXT NOT NULL,
132
+ results_returned INTEGER NOT NULL,
133
+ top_score REAL,
134
+ latency_ms REAL NOT NULL,
135
+ engram_ids TEXT NOT NULL DEFAULT '[]'
136
+ );
137
+
138
+ CREATE TABLE IF NOT EXISTS staging_events (
139
+ engram_id TEXT NOT NULL,
140
+ agent_id TEXT NOT NULL,
141
+ action TEXT NOT NULL,
142
+ resonance_score REAL,
143
+ timestamp TEXT NOT NULL,
144
+ age_ms INTEGER NOT NULL
145
+ );
146
+
147
+ CREATE TABLE IF NOT EXISTS retrieval_feedback (
148
+ id TEXT PRIMARY KEY,
149
+ activation_event_id TEXT,
150
+ engram_id TEXT NOT NULL,
151
+ useful INTEGER NOT NULL,
152
+ context TEXT,
153
+ timestamp TEXT NOT NULL
154
+ );
155
+
156
+ CREATE TABLE IF NOT EXISTS episodes (
157
+ id TEXT PRIMARY KEY,
158
+ agent_id TEXT NOT NULL,
159
+ label TEXT NOT NULL,
160
+ embedding BLOB,
161
+ engram_count INTEGER NOT NULL DEFAULT 0,
162
+ start_time TEXT NOT NULL,
163
+ end_time TEXT NOT NULL,
164
+ created_at TEXT NOT NULL
165
+ );
166
+
167
+ CREATE INDEX IF NOT EXISTS idx_episodes_agent ON episodes(agent_id);
168
+ CREATE INDEX IF NOT EXISTS idx_episodes_time ON episodes(agent_id, end_time);
143
169
  `);
144
170
  // Migration: add episode_id column if missing
145
171
  try {
@@ -153,10 +179,10 @@ export class EngramStore {
153
179
  this.db.prepare('SELECT task_status FROM engrams LIMIT 0').get();
154
180
  }
155
181
  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;
182
+ this.db.exec(`
183
+ ALTER TABLE engrams ADD COLUMN task_status TEXT;
184
+ ALTER TABLE engrams ADD COLUMN task_priority TEXT;
185
+ ALTER TABLE engrams ADD COLUMN blocked_by TEXT;
160
186
  `);
161
187
  this.db.exec('CREATE INDEX IF NOT EXISTS idx_engrams_task ON engrams(agent_id, task_status)');
162
188
  }
@@ -165,35 +191,42 @@ export class EngramStore {
165
191
  this.db.prepare('SELECT memory_class FROM engrams LIMIT 0').get();
166
192
  }
167
193
  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;
194
+ this.db.exec(`
195
+ ALTER TABLE engrams ADD COLUMN memory_class TEXT NOT NULL DEFAULT 'working';
196
+ ALTER TABLE engrams ADD COLUMN superseded_by TEXT;
197
+ ALTER TABLE engrams ADD COLUMN supersedes TEXT;
172
198
  `);
173
199
  }
174
200
  // 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
- )
201
+ this.db.exec(`
202
+ CREATE TABLE IF NOT EXISTS conscious_state (
203
+ agent_id TEXT PRIMARY KEY,
204
+ last_write_id TEXT,
205
+ last_recall_context TEXT,
206
+ last_recall_ids TEXT NOT NULL DEFAULT '[]',
207
+ last_activity_at TEXT NOT NULL DEFAULT (datetime('now')),
208
+ write_count_since_consolidation INTEGER NOT NULL DEFAULT 0,
209
+ recall_count_since_consolidation INTEGER NOT NULL DEFAULT 0,
210
+ execution_state TEXT,
211
+ checkpoint_at TEXT,
212
+ last_consolidation_at TEXT,
213
+ last_mini_consolidation_at TEXT,
214
+ consolidation_cycle_count INTEGER NOT NULL DEFAULT 0,
215
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
216
+ )
191
217
  `);
192
218
  // Migration: add consolidation_cycle_count if missing (existing DBs)
193
219
  try {
194
220
  this.db.exec(`ALTER TABLE conscious_state ADD COLUMN consolidation_cycle_count INTEGER NOT NULL DEFAULT 0`);
195
221
  }
196
222
  catch { /* column already exists */ }
223
+ // Migration: add memory_type column if missing
224
+ try {
225
+ this.db.prepare('SELECT memory_type FROM engrams LIMIT 0').get();
226
+ }
227
+ catch {
228
+ this.db.exec(`ALTER TABLE engrams ADD COLUMN memory_type TEXT NOT NULL DEFAULT 'unclassified'`);
229
+ }
197
230
  }
198
231
  // --- Engram CRUD ---
199
232
  createEngram(input) {
@@ -202,12 +235,12 @@ export class EngramStore {
202
235
  const embeddingBlob = input.embedding
203
236
  ? Buffer.from(new Float32Array(input.embedding).buffer)
204
237
  : 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', ?, ?, ?, ?, ?, ?, ?, ?)
210
- `).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);
238
+ this.db.prepare(`
239
+ INSERT INTO engrams (id, agent_id, concept, content, embedding, confidence, salience,
240
+ access_count, last_accessed, created_at, salience_features, reason_codes, stage, tags, episode_id,
241
+ ttl, memory_class, supersedes, task_status, task_priority, blocked_by, memory_type)
242
+ VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, 'active', ?, ?, ?, ?, ?, ?, ?, ?, ?)
243
+ `).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, input.memoryType ?? 'unclassified');
211
244
  return this.getEngram(id);
212
245
  }
213
246
  getEngram(id) {
@@ -226,9 +259,21 @@ export class EngramStore {
226
259
  }
227
260
  return this.db.prepare(query).all(...params).map(r => this.rowToEngram(r));
228
261
  }
262
+ /**
263
+ * Touch an engram: increment access count, update last_accessed, and
264
+ * nudge confidence upward. Each retrieval is weak evidence the memory
265
+ * is useful — bounded so only explicit feedback can push confidence
266
+ * above 0.85. Diminishing returns: first accesses matter most.
267
+ *
268
+ * Boost: +0.02 per access, scaled by 1/sqrt(accessCount+1), capped at 0.85.
269
+ */
229
270
  touchEngram(id) {
230
- this.db.prepare(`
231
- UPDATE engrams SET access_count = access_count + 1, last_accessed = ? WHERE id = ?
271
+ this.db.prepare(`
272
+ UPDATE engrams
273
+ SET access_count = access_count + 1,
274
+ last_accessed = ?,
275
+ confidence = MIN(0.85, confidence + 0.02 / (1.0 + sqrt(access_count)))
276
+ WHERE id = ?
232
277
  `).run(new Date().toISOString(), id);
233
278
  }
234
279
  updateStage(id, stage) {
@@ -242,8 +287,8 @@ export class EngramStore {
242
287
  this.db.prepare('UPDATE engrams SET embedding = ? WHERE id = ?').run(blob, id);
243
288
  }
244
289
  retractEngram(id, retractedBy) {
245
- this.db.prepare(`
246
- UPDATE engrams SET retracted = 1, retracted_by = ?, retracted_at = ? WHERE id = ?
290
+ this.db.prepare(`
291
+ UPDATE engrams SET retracted = 1, retracted_by = ?, retracted_at = ? WHERE id = ?
247
292
  `).run(retractedBy, new Date().toISOString(), id);
248
293
  }
249
294
  deleteEngram(id) {
@@ -258,20 +303,20 @@ export class EngramStore {
258
303
  let count = 0;
259
304
  const shiftSec = Math.round(ms / 1000);
260
305
  // 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 = ?
306
+ const r1 = this.db.prepare(`
307
+ UPDATE engrams SET
308
+ created_at = datetime(created_at, '-${shiftSec} seconds'),
309
+ last_accessed = datetime(last_accessed, '-${shiftSec} seconds')
310
+ WHERE agent_id = ?
266
311
  `).run(agentId);
267
312
  count += r1.changes;
268
313
  // 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 = ?)
314
+ const r2 = this.db.prepare(`
315
+ UPDATE associations SET
316
+ created_at = datetime(created_at, '-${shiftSec} seconds'),
317
+ last_activated = datetime(last_activated, '-${shiftSec} seconds')
318
+ WHERE from_engram_id IN (SELECT id FROM engrams WHERE agent_id = ?)
319
+ OR to_engram_id IN (SELECT id FROM engrams WHERE agent_id = ?)
275
320
  `).run(agentId, agentId);
276
321
  count += r2.changes;
277
322
  return count;
@@ -296,12 +341,12 @@ export class EngramStore {
296
341
  if (!sanitized)
297
342
  return [];
298
343
  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 ?
344
+ const rows = this.db.prepare(`
345
+ SELECT e.*, rank FROM engrams e
346
+ JOIN engrams_fts ON e.rowid = engrams_fts.rowid
347
+ WHERE engrams_fts MATCH ? AND e.agent_id = ? AND e.retracted = 0
348
+ ORDER BY rank
349
+ LIMIT ?
305
350
  `).all(sanitized, agentId, limit);
306
351
  return rows.map(r => ({
307
352
  engram: this.rowToEngram(r),
@@ -381,14 +426,14 @@ export class EngramStore {
381
426
  sql += ' AND task_status = ?';
382
427
  params.push(status);
383
428
  }
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,
429
+ sql += ` ORDER BY
430
+ CASE task_priority
431
+ WHEN 'urgent' THEN 0
432
+ WHEN 'high' THEN 1
433
+ WHEN 'medium' THEN 2
434
+ WHEN 'low' THEN 3
435
+ ELSE 4
436
+ END,
392
437
  created_at DESC`;
393
438
  return this.db.prepare(sql).all(...params).map(r => this.rowToEngram(r));
394
439
  }
@@ -396,20 +441,20 @@ export class EngramStore {
396
441
  * Get the next actionable task — highest priority that's not blocked or done.
397
442
  */
398
443
  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
444
+ const row = this.db.prepare(`
445
+ SELECT * FROM engrams
446
+ WHERE agent_id = ? AND task_status IN ('open', 'in_progress') AND retracted = 0
447
+ ORDER BY
448
+ CASE task_status WHEN 'in_progress' THEN 0 ELSE 1 END,
449
+ CASE task_priority
450
+ WHEN 'urgent' THEN 0
451
+ WHEN 'high' THEN 1
452
+ WHEN 'medium' THEN 2
453
+ WHEN 'low' THEN 3
454
+ ELSE 4
455
+ END,
456
+ created_at ASC
457
+ LIMIT 1
413
458
  `).get(agentId);
414
459
  return row ? this.rowToEngram(row) : null;
415
460
  }
@@ -432,15 +477,18 @@ export class EngramStore {
432
477
  updateMemoryClass(id, memoryClass) {
433
478
  this.db.prepare('UPDATE engrams SET memory_class = ? WHERE id = ?').run(memoryClass, id);
434
479
  }
480
+ updateTags(id, tags) {
481
+ this.db.prepare('UPDATE engrams SET tags = ? WHERE id = ?').run(JSON.stringify(tags), id);
482
+ }
435
483
  // --- Associations ---
436
484
  upsertAssociation(fromId, toId, weight, type = 'hebbian', confidence = 0.5) {
437
485
  const now = new Date().toISOString();
438
486
  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
487
+ this.db.prepare(`
488
+ INSERT INTO associations (id, from_engram_id, to_engram_id, weight, confidence, type, activation_count, created_at, last_activated)
489
+ VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)
490
+ ON CONFLICT(from_engram_id, to_engram_id) DO UPDATE SET
491
+ weight = ?, confidence = ?, last_activated = ?, activation_count = activation_count + 1
444
492
  `).run(id, fromId, toId, weight, confidence, type, now, now, weight, confidence, now);
445
493
  return this.getAssociation(fromId, toId);
446
494
  }
@@ -468,22 +516,22 @@ export class EngramStore {
468
516
  this.db.prepare('DELETE FROM associations WHERE id = ?').run(id);
469
517
  }
470
518
  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 = ?
519
+ const rows = this.db.prepare(`
520
+ SELECT a.* FROM associations a
521
+ JOIN engrams e ON a.from_engram_id = e.id
522
+ WHERE e.agent_id = ?
475
523
  `).all(agentId);
476
524
  return rows.map(r => this.rowToAssociation(r));
477
525
  }
478
526
  // --- Eviction ---
479
527
  getEvictionCandidates(agentId, limit) {
480
528
  // 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 ?
529
+ const rows = this.db.prepare(`
530
+ SELECT * FROM engrams
531
+ WHERE agent_id = ? AND stage = 'active' AND retracted = 0
532
+ ORDER BY (salience * 0.3 + confidence * 0.3 + (CAST(access_count AS REAL) / (access_count + 5)) * 0.2 +
533
+ (1.0 / (1.0 + (julianday('now') - julianday(last_accessed)))) * 0.2) ASC
534
+ LIMIT ?
487
535
  `).all(agentId, limit);
488
536
  return rows.map(r => this.rowToEngram(r));
489
537
  }
@@ -498,8 +546,8 @@ export class EngramStore {
498
546
  // --- Staging buffer ---
499
547
  getExpiredStaging() {
500
548
  const now = Date.now();
501
- const rows = this.db.prepare(`
502
- SELECT * FROM engrams WHERE stage = 'staging' AND ttl IS NOT NULL
549
+ const rows = this.db.prepare(`
550
+ SELECT * FROM engrams WHERE stage = 'staging' AND ttl IS NOT NULL
503
551
  `).all();
504
552
  return rows
505
553
  .map(r => this.rowToEngram(r))
@@ -507,53 +555,53 @@ export class EngramStore {
507
555
  }
508
556
  // --- Eval event logging ---
509
557
  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 (?, ?, ?, ?, ?, ?, ?, ?)
558
+ this.db.prepare(`
559
+ INSERT INTO activation_events (id, agent_id, timestamp, context, results_returned, top_score, latency_ms, engram_ids)
560
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
513
561
  `).run(event.id, event.agentId, event.timestamp.toISOString(), event.context, event.resultsReturned, event.topScore, event.latencyMs, JSON.stringify(event.engramIds));
514
562
  }
515
563
  logStagingEvent(event) {
516
- this.db.prepare(`
517
- INSERT INTO staging_events (engram_id, agent_id, action, resonance_score, timestamp, age_ms)
518
- VALUES (?, ?, ?, ?, ?, ?)
564
+ this.db.prepare(`
565
+ INSERT INTO staging_events (engram_id, agent_id, action, resonance_score, timestamp, age_ms)
566
+ VALUES (?, ?, ?, ?, ?, ?)
519
567
  `).run(event.engramId, event.agentId, event.action, event.resonanceScore, event.timestamp.toISOString(), event.ageMs);
520
568
  }
521
569
  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 (?, ?, ?, ?, ?, ?)
570
+ this.db.prepare(`
571
+ INSERT INTO retrieval_feedback (id, activation_event_id, engram_id, useful, context, timestamp)
572
+ VALUES (?, ?, ?, ?, ?, ?)
525
573
  `).run(randomUUID(), activationEventId, engramId, useful ? 1 : 0, context, new Date().toISOString());
526
574
  }
527
575
  // --- Eval metrics queries ---
528
576
  getRetrievalPrecision(agentId, windowHours = 24) {
529
577
  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 > ?
578
+ const row = this.db.prepare(`
579
+ SELECT
580
+ COUNT(CASE WHEN useful = 1 THEN 1 END) as useful_count,
581
+ COUNT(*) as total_count
582
+ FROM retrieval_feedback rf
583
+ LEFT JOIN activation_events ae ON rf.activation_event_id = ae.id
584
+ JOIN engrams e ON rf.engram_id = e.id
585
+ WHERE e.agent_id = ? AND rf.timestamp > ?
538
586
  `).get(agentId, since);
539
587
  return row.total_count > 0 ? row.useful_count / row.total_count : 0;
540
588
  }
541
589
  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 = ?
590
+ const row = this.db.prepare(`
591
+ SELECT
592
+ COUNT(CASE WHEN action = 'promoted' THEN 1 END) as promoted,
593
+ COUNT(CASE WHEN action = 'discarded' THEN 1 END) as discarded,
594
+ COUNT(CASE WHEN action = 'expired' THEN 1 END) as expired
595
+ FROM staging_events WHERE agent_id = ?
548
596
  `).get(agentId);
549
597
  return { promoted: row.promoted, discarded: row.discarded, expired: row.expired };
550
598
  }
551
599
  getActivationStats(agentId, windowHours = 24) {
552
600
  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
601
+ const rows = this.db.prepare(`
602
+ SELECT latency_ms FROM activation_events
603
+ WHERE agent_id = ? AND timestamp > ?
604
+ ORDER BY latency_ms ASC
557
605
  `).all(agentId, since);
558
606
  if (rows.length === 0)
559
607
  return { count: 0, avgLatencyMs: 0, p95LatencyMs: 0 };
@@ -594,6 +642,7 @@ export class EngramStore {
594
642
  tags: JSON.parse(row.tags),
595
643
  episodeId: row.episode_id ?? null,
596
644
  memoryClass: (row.memory_class ?? 'working'),
645
+ memoryType: (row.memory_type ?? 'unclassified'),
597
646
  supersededBy: row.superseded_by ?? null,
598
647
  supersedes: row.supersedes ?? null,
599
648
  taskStatus: row.task_status ?? null,
@@ -621,9 +670,9 @@ export class EngramStore {
621
670
  const embeddingBlob = input.embedding
622
671
  ? Buffer.from(new Float32Array(input.embedding).buffer)
623
672
  : 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, ?, ?, ?)
673
+ this.db.prepare(`
674
+ INSERT INTO episodes (id, agent_id, label, embedding, engram_count, start_time, end_time, created_at)
675
+ VALUES (?, ?, ?, ?, 0, ?, ?, ?)
627
676
  `).run(id, input.agentId, input.label, embeddingBlob, now, now, now);
628
677
  return this.getEpisode(id);
629
678
  }
@@ -638,19 +687,19 @@ export class EngramStore {
638
687
  getActiveEpisode(agentId, windowMs = 3600_000) {
639
688
  // Find most recent episode that ended within the time window
640
689
  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
690
+ const row = this.db.prepare(`
691
+ SELECT * FROM episodes WHERE agent_id = ? AND end_time > ?
692
+ ORDER BY end_time DESC LIMIT 1
644
693
  `).get(agentId, cutoff);
645
694
  return row ? this.rowToEpisode(row) : null;
646
695
  }
647
696
  addEngramToEpisode(engramId, episodeId) {
648
697
  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 = ?
698
+ this.db.prepare(`
699
+ UPDATE episodes SET
700
+ engram_count = engram_count + 1,
701
+ end_time = MAX(end_time, ?)
702
+ WHERE id = ?
654
703
  `).run(new Date().toISOString(), episodeId);
655
704
  }
656
705
  getEngramsByEpisode(episodeId) {
@@ -701,49 +750,49 @@ export class EngramStore {
701
750
  // --- Checkpointing ---
702
751
  updateAutoCheckpointWrite(agentId, engramId) {
703
752
  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
753
+ this.db.prepare(`
754
+ INSERT INTO conscious_state (agent_id, last_write_id, last_activity_at, write_count_since_consolidation, updated_at)
755
+ VALUES (?, ?, ?, 1, ?)
756
+ ON CONFLICT(agent_id) DO UPDATE SET
757
+ last_write_id = excluded.last_write_id,
758
+ last_activity_at = excluded.last_activity_at,
759
+ write_count_since_consolidation = write_count_since_consolidation + 1,
760
+ updated_at = excluded.updated_at
712
761
  `).run(agentId, engramId, now, now);
713
762
  }
714
763
  updateAutoCheckpointRecall(agentId, context, engramIds) {
715
764
  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
765
+ this.db.prepare(`
766
+ INSERT INTO conscious_state (agent_id, last_recall_context, last_recall_ids, last_activity_at, recall_count_since_consolidation, updated_at)
767
+ VALUES (?, ?, ?, ?, 1, ?)
768
+ ON CONFLICT(agent_id) DO UPDATE SET
769
+ last_recall_context = excluded.last_recall_context,
770
+ last_recall_ids = excluded.last_recall_ids,
771
+ last_activity_at = excluded.last_activity_at,
772
+ recall_count_since_consolidation = recall_count_since_consolidation + 1,
773
+ updated_at = excluded.updated_at
725
774
  `).run(agentId, context, JSON.stringify(engramIds), now, now);
726
775
  }
727
776
  touchActivity(agentId) {
728
777
  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
778
+ this.db.prepare(`
779
+ INSERT INTO conscious_state (agent_id, last_activity_at, updated_at)
780
+ VALUES (?, ?, ?)
781
+ ON CONFLICT(agent_id) DO UPDATE SET
782
+ last_activity_at = excluded.last_activity_at,
783
+ updated_at = excluded.updated_at
735
784
  `).run(agentId, now, now);
736
785
  }
737
786
  saveCheckpoint(agentId, state) {
738
787
  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
788
+ this.db.prepare(`
789
+ INSERT INTO conscious_state (agent_id, execution_state, checkpoint_at, last_activity_at, updated_at)
790
+ VALUES (?, ?, ?, ?, ?)
791
+ ON CONFLICT(agent_id) DO UPDATE SET
792
+ execution_state = excluded.execution_state,
793
+ checkpoint_at = excluded.checkpoint_at,
794
+ last_activity_at = excluded.last_activity_at,
795
+ updated_at = excluded.updated_at
747
796
  `).run(agentId, JSON.stringify(state), now, now, now);
748
797
  }
749
798
  getCheckpoint(agentId) {
@@ -770,20 +819,20 @@ export class EngramStore {
770
819
  markConsolidation(agentId, mini) {
771
820
  const now = new Date().toISOString();
772
821
  if (mini) {
773
- this.db.prepare(`
774
- UPDATE conscious_state SET last_mini_consolidation_at = ?, updated_at = ? WHERE agent_id = ?
822
+ this.db.prepare(`
823
+ UPDATE conscious_state SET last_mini_consolidation_at = ?, updated_at = ? WHERE agent_id = ?
775
824
  `).run(now, now, agentId);
776
825
  }
777
826
  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 = ?
827
+ this.db.prepare(`
828
+ UPDATE conscious_state SET
829
+ last_consolidation_at = ?,
830
+ last_mini_consolidation_at = ?,
831
+ write_count_since_consolidation = 0,
832
+ recall_count_since_consolidation = 0,
833
+ consolidation_cycle_count = consolidation_cycle_count + 1,
834
+ updated_at = ?
835
+ WHERE agent_id = ?
787
836
  `).run(now, now, now, agentId);
788
837
  }
789
838
  }