agent-working-memory 0.3.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 (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +311 -0
  3. package/dist/api/index.d.ts +2 -0
  4. package/dist/api/index.d.ts.map +1 -0
  5. package/dist/api/index.js +2 -0
  6. package/dist/api/index.js.map +1 -0
  7. package/dist/api/routes.d.ts +53 -0
  8. package/dist/api/routes.d.ts.map +1 -0
  9. package/dist/api/routes.js +388 -0
  10. package/dist/api/routes.js.map +1 -0
  11. package/dist/cli.d.ts +12 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +245 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/core/decay.d.ts +36 -0
  16. package/dist/core/decay.d.ts.map +1 -0
  17. package/dist/core/decay.js +38 -0
  18. package/dist/core/decay.js.map +1 -0
  19. package/dist/core/embeddings.d.ts +33 -0
  20. package/dist/core/embeddings.d.ts.map +1 -0
  21. package/dist/core/embeddings.js +76 -0
  22. package/dist/core/embeddings.js.map +1 -0
  23. package/dist/core/hebbian.d.ts +38 -0
  24. package/dist/core/hebbian.d.ts.map +1 -0
  25. package/dist/core/hebbian.js +74 -0
  26. package/dist/core/hebbian.js.map +1 -0
  27. package/dist/core/index.d.ts +4 -0
  28. package/dist/core/index.d.ts.map +1 -0
  29. package/dist/core/index.js +4 -0
  30. package/dist/core/index.js.map +1 -0
  31. package/dist/core/query-expander.d.ts +24 -0
  32. package/dist/core/query-expander.d.ts.map +1 -0
  33. package/dist/core/query-expander.js +58 -0
  34. package/dist/core/query-expander.js.map +1 -0
  35. package/dist/core/reranker.d.ts +25 -0
  36. package/dist/core/reranker.d.ts.map +1 -0
  37. package/dist/core/reranker.js +75 -0
  38. package/dist/core/reranker.js.map +1 -0
  39. package/dist/core/salience.d.ts +30 -0
  40. package/dist/core/salience.d.ts.map +1 -0
  41. package/dist/core/salience.js +81 -0
  42. package/dist/core/salience.js.map +1 -0
  43. package/dist/engine/activation.d.ts +38 -0
  44. package/dist/engine/activation.d.ts.map +1 -0
  45. package/dist/engine/activation.js +516 -0
  46. package/dist/engine/activation.js.map +1 -0
  47. package/dist/engine/connections.d.ts +31 -0
  48. package/dist/engine/connections.d.ts.map +1 -0
  49. package/dist/engine/connections.js +74 -0
  50. package/dist/engine/connections.js.map +1 -0
  51. package/dist/engine/consolidation-scheduler.d.ts +31 -0
  52. package/dist/engine/consolidation-scheduler.d.ts.map +1 -0
  53. package/dist/engine/consolidation-scheduler.js +115 -0
  54. package/dist/engine/consolidation-scheduler.js.map +1 -0
  55. package/dist/engine/consolidation.d.ts +62 -0
  56. package/dist/engine/consolidation.d.ts.map +1 -0
  57. package/dist/engine/consolidation.js +368 -0
  58. package/dist/engine/consolidation.js.map +1 -0
  59. package/dist/engine/eval.d.ts +22 -0
  60. package/dist/engine/eval.d.ts.map +1 -0
  61. package/dist/engine/eval.js +79 -0
  62. package/dist/engine/eval.js.map +1 -0
  63. package/dist/engine/eviction.d.ts +29 -0
  64. package/dist/engine/eviction.d.ts.map +1 -0
  65. package/dist/engine/eviction.js +86 -0
  66. package/dist/engine/eviction.js.map +1 -0
  67. package/dist/engine/index.d.ts +7 -0
  68. package/dist/engine/index.d.ts.map +1 -0
  69. package/dist/engine/index.js +7 -0
  70. package/dist/engine/index.js.map +1 -0
  71. package/dist/engine/retraction.d.ts +32 -0
  72. package/dist/engine/retraction.d.ts.map +1 -0
  73. package/dist/engine/retraction.js +77 -0
  74. package/dist/engine/retraction.js.map +1 -0
  75. package/dist/engine/staging.d.ts +33 -0
  76. package/dist/engine/staging.d.ts.map +1 -0
  77. package/dist/engine/staging.js +63 -0
  78. package/dist/engine/staging.js.map +1 -0
  79. package/dist/index.d.ts +2 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +95 -0
  82. package/dist/index.js.map +1 -0
  83. package/dist/mcp.d.ts +24 -0
  84. package/dist/mcp.d.ts.map +1 -0
  85. package/dist/mcp.js +532 -0
  86. package/dist/mcp.js.map +1 -0
  87. package/dist/storage/index.d.ts +2 -0
  88. package/dist/storage/index.d.ts.map +1 -0
  89. package/dist/storage/index.js +2 -0
  90. package/dist/storage/index.js.map +1 -0
  91. package/dist/storage/sqlite.d.ts +116 -0
  92. package/dist/storage/sqlite.d.ts.map +1 -0
  93. package/dist/storage/sqlite.js +750 -0
  94. package/dist/storage/sqlite.js.map +1 -0
  95. package/dist/types/agent.d.ts +30 -0
  96. package/dist/types/agent.d.ts.map +1 -0
  97. package/dist/types/agent.js +23 -0
  98. package/dist/types/agent.js.map +1 -0
  99. package/dist/types/checkpoint.d.ts +50 -0
  100. package/dist/types/checkpoint.d.ts.map +1 -0
  101. package/dist/types/checkpoint.js +8 -0
  102. package/dist/types/checkpoint.js.map +1 -0
  103. package/dist/types/engram.d.ts +165 -0
  104. package/dist/types/engram.d.ts.map +1 -0
  105. package/dist/types/engram.js +8 -0
  106. package/dist/types/engram.js.map +1 -0
  107. package/dist/types/eval.d.ts +84 -0
  108. package/dist/types/eval.d.ts.map +1 -0
  109. package/dist/types/eval.js +11 -0
  110. package/dist/types/eval.js.map +1 -0
  111. package/dist/types/index.d.ts +5 -0
  112. package/dist/types/index.d.ts.map +1 -0
  113. package/dist/types/index.js +5 -0
  114. package/dist/types/index.js.map +1 -0
  115. package/package.json +55 -0
  116. package/src/api/index.ts +1 -0
  117. package/src/api/routes.ts +528 -0
  118. package/src/cli.ts +260 -0
  119. package/src/core/decay.ts +61 -0
  120. package/src/core/embeddings.ts +82 -0
  121. package/src/core/hebbian.ts +91 -0
  122. package/src/core/index.ts +3 -0
  123. package/src/core/query-expander.ts +64 -0
  124. package/src/core/reranker.ts +99 -0
  125. package/src/core/salience.ts +95 -0
  126. package/src/engine/activation.ts +577 -0
  127. package/src/engine/connections.ts +101 -0
  128. package/src/engine/consolidation-scheduler.ts +123 -0
  129. package/src/engine/consolidation.ts +443 -0
  130. package/src/engine/eval.ts +100 -0
  131. package/src/engine/eviction.ts +99 -0
  132. package/src/engine/index.ts +6 -0
  133. package/src/engine/retraction.ts +98 -0
  134. package/src/engine/staging.ts +72 -0
  135. package/src/index.ts +100 -0
  136. package/src/mcp.ts +635 -0
  137. package/src/storage/index.ts +1 -0
  138. package/src/storage/sqlite.ts +893 -0
  139. package/src/types/agent.ts +65 -0
  140. package/src/types/checkpoint.ts +44 -0
  141. package/src/types/engram.ts +194 -0
  142. package/src/types/eval.ts +98 -0
  143. package/src/types/index.ts +4 -0
@@ -0,0 +1,528 @@
1
+ /**
2
+ * API Routes — the black box interface agents interact with.
3
+ *
4
+ * Core (agent-facing):
5
+ * POST /memory/write — write a memory (salience filter decides disposition)
6
+ * POST /memory/activate — retrieve by context activation
7
+ * POST /memory/feedback — report whether a memory was useful
8
+ * POST /memory/retract — invalidate a wrong memory
9
+ *
10
+ * Checkpointing:
11
+ * POST /memory/checkpoint — save explicit execution state
12
+ * GET /memory/restore/:agentId — restore state + targeted recall + async mini-consolidation
13
+ *
14
+ * Task management:
15
+ * POST /task/create — create a prioritized task
16
+ * POST /task/update — update status, priority, or blocking
17
+ * GET /task/list/:agentId — list tasks (filtered by status)
18
+ * GET /task/next/:agentId — get highest-priority actionable task
19
+ *
20
+ * Diagnostic (debugging/eval):
21
+ * POST /memory/search — deterministic search (not cognitive)
22
+ * GET /memory/:id — get a specific engram
23
+ * GET /agent/:id/stats — memory stats for an agent
24
+ * GET /agent/:id/metrics — eval metrics
25
+ * POST /agent/register — register a new agent
26
+ *
27
+ * System:
28
+ * POST /system/evict — trigger eviction check
29
+ * POST /system/decay — trigger edge decay
30
+ * POST /system/consolidate — run sleep cycle (strengthen, decay, sweep)
31
+ * GET /health — health check
32
+ */
33
+
34
+ import type { FastifyInstance } from 'fastify';
35
+ import type { EngramStore } from '../storage/sqlite.js';
36
+ import type { ActivationEngine } from '../engine/activation.js';
37
+ import type { ConnectionEngine } from '../engine/connections.js';
38
+ import type { EvictionEngine } from '../engine/eviction.js';
39
+ import type { RetractionEngine } from '../engine/retraction.js';
40
+ import type { EvalEngine } from '../engine/eval.js';
41
+ import type { ConsolidationEngine } from '../engine/consolidation.js';
42
+ import type { ConsolidationScheduler } from '../engine/consolidation-scheduler.js';
43
+ import { evaluateSalience } from '../core/salience.js';
44
+ import type { SalienceEventType } from '../core/salience.js';
45
+ import type { TaskStatus, TaskPriority } from '../types/engram.js';
46
+ import type { ConsciousState } from '../types/checkpoint.js';
47
+ import { DEFAULT_AGENT_CONFIG } from '../types/agent.js';
48
+ import { embed } from '../core/embeddings.js';
49
+
50
+ export interface MemoryDeps {
51
+ store: EngramStore;
52
+ activationEngine: ActivationEngine;
53
+ connectionEngine: ConnectionEngine;
54
+ evictionEngine: EvictionEngine;
55
+ retractionEngine: RetractionEngine;
56
+ evalEngine: EvalEngine;
57
+ consolidationEngine: ConsolidationEngine;
58
+ consolidationScheduler: ConsolidationScheduler;
59
+ }
60
+
61
+ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
62
+ const { store, activationEngine, connectionEngine, evictionEngine, retractionEngine, evalEngine, consolidationEngine, consolidationScheduler } = deps;
63
+
64
+ // ============================================================
65
+ // CORE — Agent-facing endpoints
66
+ // ============================================================
67
+
68
+ app.post('/memory/write', async (req, reply) => {
69
+ const body = req.body as {
70
+ agentId: string;
71
+ concept: string;
72
+ content: string;
73
+ tags?: string[];
74
+ eventType?: SalienceEventType;
75
+ surprise?: number;
76
+ decisionMade?: boolean;
77
+ causalDepth?: number;
78
+ resolutionEffort?: number;
79
+ confidence?: number;
80
+ };
81
+
82
+ const salience = evaluateSalience({
83
+ content: body.content,
84
+ eventType: body.eventType,
85
+ surprise: body.surprise,
86
+ decisionMade: body.decisionMade,
87
+ causalDepth: body.causalDepth,
88
+ resolutionEffort: body.resolutionEffort,
89
+ });
90
+
91
+ if (salience.disposition === 'discard') {
92
+ return reply.code(200).send({
93
+ stored: false,
94
+ disposition: 'discard',
95
+ salience: salience.score,
96
+ reasonCodes: salience.reasonCodes,
97
+ });
98
+ }
99
+
100
+ const engram = store.createEngram({
101
+ agentId: body.agentId,
102
+ concept: body.concept,
103
+ content: body.content,
104
+ tags: body.tags,
105
+ salience: salience.score,
106
+ confidence: body.confidence,
107
+ salienceFeatures: salience.features,
108
+ reasonCodes: salience.reasonCodes,
109
+ ttl: salience.disposition === 'staging' ? DEFAULT_AGENT_CONFIG.stagingTtlMs : undefined,
110
+ });
111
+
112
+ if (salience.disposition === 'staging') {
113
+ store.updateStage(engram.id, 'staging');
114
+ }
115
+
116
+ // Create temporal adjacency edge to previous memory (conversation thread graph)
117
+ // This enables multi-hop graph walk through conversation sequences
118
+ try {
119
+ const prev = store.getLatestEngram(body.agentId, engram.id);
120
+ if (prev) {
121
+ store.upsertAssociation(prev.id, engram.id, 0.3, 'temporal', 0.8);
122
+ }
123
+ } catch { /* Temporal edge creation is non-fatal */ }
124
+
125
+ if (salience.disposition === 'active') {
126
+ connectionEngine.enqueue(engram.id);
127
+
128
+ // Auto-assign to episode (1-hour window per agent)
129
+ try {
130
+ let episode = store.getActiveEpisode(body.agentId, 3600_000);
131
+ if (!episode) {
132
+ episode = store.createEpisode({ agentId: body.agentId, label: body.concept });
133
+ }
134
+ store.addEngramToEpisode(engram.id, episode.id);
135
+ } catch { /* Episode assignment is non-fatal */ }
136
+ }
137
+
138
+ // Generate embedding asynchronously (don't block response)
139
+ embed(`${body.concept} ${body.content}`).then(vec => {
140
+ store.updateEmbedding(engram.id, vec);
141
+ }).catch(() => {}); // Embedding failure is non-fatal
142
+
143
+ // Auto-checkpoint: track write for consolidation scheduling
144
+ try { store.updateAutoCheckpointWrite(body.agentId, engram.id); } catch { /* non-fatal */ }
145
+
146
+ return reply.code(201).send({
147
+ stored: true,
148
+ disposition: salience.disposition,
149
+ salience: salience.score,
150
+ reasonCodes: salience.reasonCodes,
151
+ engram,
152
+ });
153
+ });
154
+
155
+ app.post('/memory/activate', async (req, reply) => {
156
+ const body = req.body as {
157
+ agentId: string;
158
+ context: string;
159
+ limit?: number;
160
+ minScore?: number;
161
+ includeStaging?: boolean;
162
+ useReranker?: boolean;
163
+ useExpansion?: boolean;
164
+ abstentionThreshold?: number;
165
+ };
166
+
167
+ const results = await activationEngine.activate({
168
+ agentId: body.agentId,
169
+ context: body.context,
170
+ limit: body.limit,
171
+ minScore: body.minScore,
172
+ includeStaging: body.includeStaging,
173
+ useReranker: body.useReranker,
174
+ useExpansion: body.useExpansion,
175
+ abstentionThreshold: body.abstentionThreshold,
176
+ });
177
+
178
+ // Auto-checkpoint: track recall for consolidation scheduling
179
+ try {
180
+ const ids = results.map(r => r.engram.id);
181
+ store.updateAutoCheckpointRecall(body.agentId, body.context, ids);
182
+ } catch { /* non-fatal */ }
183
+
184
+ return reply.send({ results });
185
+ });
186
+
187
+ app.post('/memory/feedback', async (req, reply) => {
188
+ const body = req.body as {
189
+ activationEventId?: string;
190
+ engramId: string;
191
+ useful: boolean;
192
+ context?: string;
193
+ };
194
+
195
+ store.logRetrievalFeedback(
196
+ body.activationEventId ?? null,
197
+ body.engramId,
198
+ body.useful,
199
+ body.context ?? ''
200
+ );
201
+
202
+ // Update engram confidence based on feedback
203
+ const engram = store.getEngram(body.engramId);
204
+ if (engram) {
205
+ const config = DEFAULT_AGENT_CONFIG;
206
+ const delta = body.useful
207
+ ? config.feedbackPositiveBoost
208
+ : -config.feedbackNegativePenalty;
209
+ store.updateConfidence(engram.id, engram.confidence + delta);
210
+ }
211
+
212
+ // Touch activity for consolidation scheduling
213
+ if (engram) {
214
+ try { store.touchActivity(engram.agentId); } catch { /* non-fatal */ }
215
+ }
216
+
217
+ return reply.send({ recorded: true });
218
+ });
219
+
220
+ app.post('/memory/retract', async (req, reply) => {
221
+ const body = req.body as {
222
+ agentId: string;
223
+ targetEngramId: string;
224
+ reason: string;
225
+ counterContent?: string;
226
+ };
227
+
228
+ const result = retractionEngine.retract({
229
+ agentId: body.agentId,
230
+ targetEngramId: body.targetEngramId,
231
+ reason: body.reason,
232
+ counterContent: body.counterContent,
233
+ });
234
+
235
+ // Touch activity for consolidation scheduling
236
+ try { store.touchActivity(body.agentId); } catch { /* non-fatal */ }
237
+
238
+ return reply.send(result);
239
+ });
240
+
241
+ // ============================================================
242
+ // DIAGNOSTIC — Debugging and inspection
243
+ // ============================================================
244
+
245
+ app.post('/memory/search', async (req, reply) => {
246
+ const body = req.body as {
247
+ agentId: string;
248
+ text?: string;
249
+ concept?: string;
250
+ tags?: string[];
251
+ stage?: string;
252
+ retracted?: boolean;
253
+ limit?: number;
254
+ offset?: number;
255
+ };
256
+
257
+ const results = store.search({
258
+ agentId: body.agentId,
259
+ text: body.text,
260
+ concept: body.concept,
261
+ tags: body.tags,
262
+ stage: body.stage as any,
263
+ retracted: body.retracted,
264
+ limit: body.limit,
265
+ offset: body.offset,
266
+ });
267
+
268
+ return reply.send({ results, count: results.length });
269
+ });
270
+
271
+ app.get('/memory/:id', async (req, reply) => {
272
+ const { id } = req.params as { id: string };
273
+ const engram = store.getEngram(id);
274
+ if (!engram) return reply.code(404).send({ error: 'Not found' });
275
+
276
+ const associations = store.getAssociationsFor(id);
277
+ return reply.send({ engram, associations });
278
+ });
279
+
280
+ app.get('/agent/:id/stats', async (req, reply) => {
281
+ const { id } = req.params as { id: string };
282
+ const active = store.getEngramsByAgent(id, 'active');
283
+ const staging = store.getEngramsByAgent(id, 'staging');
284
+ const retracted = store.getEngramsByAgent(id, undefined, true).filter(e => e.retracted);
285
+ const associations = store.getAllAssociations(id);
286
+
287
+ return reply.send({
288
+ agentId: id,
289
+ engrams: {
290
+ active: active.length,
291
+ staging: staging.length,
292
+ retracted: retracted.length,
293
+ total: active.length + staging.length + retracted.length,
294
+ },
295
+ associations: associations.length,
296
+ avgConfidence: active.length > 0
297
+ ? +(active.reduce((s, e) => s + e.confidence, 0) / active.length).toFixed(3)
298
+ : 0,
299
+ });
300
+ });
301
+
302
+ app.get('/agent/:id/metrics', async (req, reply) => {
303
+ const { id } = req.params as { id: string };
304
+ const windowHours = parseInt((req.query as any).window ?? '24', 10);
305
+ const metrics = evalEngine.computeMetrics(id, windowHours);
306
+ return reply.send({ metrics });
307
+ });
308
+
309
+ app.post('/agent/register', async (req, reply) => {
310
+ const body = req.body as { name: string };
311
+ const id = crypto.randomUUID();
312
+ return reply.code(201).send({
313
+ id,
314
+ name: body.name,
315
+ config: DEFAULT_AGENT_CONFIG,
316
+ });
317
+ });
318
+
319
+ // ============================================================
320
+ // SYSTEM — Maintenance operations
321
+ // ============================================================
322
+
323
+ app.post('/system/evict', async (req, reply) => {
324
+ const body = req.body as { agentId: string };
325
+ const result = evictionEngine.enforceCapacity(body.agentId, DEFAULT_AGENT_CONFIG);
326
+ return reply.send(result);
327
+ });
328
+
329
+ app.post('/system/decay', async (req, reply) => {
330
+ const body = req.body as { agentId: string; halfLifeDays?: number };
331
+ const decayed = evictionEngine.decayEdges(body.agentId, body.halfLifeDays);
332
+ return reply.send({ edgesDecayed: decayed });
333
+ });
334
+
335
+ app.post('/system/consolidate', async (req, reply) => {
336
+ const body = req.body as { agentId: string };
337
+ const result = consolidationEngine.consolidate(body.agentId);
338
+ return reply.send(result);
339
+ });
340
+
341
+ // ============================================================
342
+ // CHECKPOINTING — Conscious state preservation
343
+ // ============================================================
344
+
345
+ app.post('/memory/checkpoint', async (req, reply) => {
346
+ const body = req.body as {
347
+ agentId: string;
348
+ currentTask: string;
349
+ decisions?: string[];
350
+ activeFiles?: string[];
351
+ nextSteps?: string[];
352
+ relatedMemoryIds?: string[];
353
+ notes?: string;
354
+ episodeId?: string | null;
355
+ };
356
+
357
+ const state: ConsciousState = {
358
+ currentTask: body.currentTask,
359
+ decisions: body.decisions ?? [],
360
+ activeFiles: body.activeFiles ?? [],
361
+ nextSteps: body.nextSteps ?? [],
362
+ relatedMemoryIds: body.relatedMemoryIds ?? [],
363
+ notes: body.notes ?? '',
364
+ episodeId: body.episodeId ?? null,
365
+ };
366
+
367
+ store.saveCheckpoint(body.agentId, state);
368
+ return reply.send({ saved: true, agentId: body.agentId });
369
+ });
370
+
371
+ app.get('/memory/restore/:agentId', async (req, reply) => {
372
+ const { agentId } = req.params as { agentId: string };
373
+ const checkpoint = store.getCheckpoint(agentId);
374
+
375
+ const now = Date.now();
376
+ const idleMs = checkpoint
377
+ ? now - checkpoint.auto.lastActivityAt.getTime()
378
+ : 0;
379
+
380
+ // Get last written engram for context
381
+ let lastWrite: { id: string; concept: string; content: string } | null = null;
382
+ if (checkpoint?.auto.lastWriteId) {
383
+ const engram = store.getEngram(checkpoint.auto.lastWriteId);
384
+ if (engram) {
385
+ lastWrite = { id: engram.id, concept: engram.concept, content: engram.content };
386
+ }
387
+ }
388
+
389
+ // Recall memories using last context (if available)
390
+ let recalledMemories: Array<{ id: string; concept: string; content: string; score: number }> = [];
391
+ const recallContext = checkpoint?.auto.lastRecallContext
392
+ ?? checkpoint?.executionState?.currentTask
393
+ ?? null;
394
+
395
+ if (recallContext) {
396
+ try {
397
+ const results = await activationEngine.activate({
398
+ agentId,
399
+ context: recallContext,
400
+ limit: 5,
401
+ minScore: 0.05,
402
+ useReranker: true,
403
+ useExpansion: true,
404
+ });
405
+ recalledMemories = results.map(r => ({
406
+ id: r.engram.id,
407
+ concept: r.engram.concept,
408
+ content: r.engram.content,
409
+ score: r.score,
410
+ }));
411
+ } catch { /* recall failure is non-fatal */ }
412
+ }
413
+
414
+ // Trigger mini-consolidation if idle >5min (async, fire-and-forget)
415
+ const MINI_CONSOLIDATION_IDLE_MS = 5 * 60_000;
416
+ let miniConsolidationTriggered = false;
417
+ if (idleMs > MINI_CONSOLIDATION_IDLE_MS) {
418
+ miniConsolidationTriggered = true;
419
+ consolidationScheduler.runMiniConsolidation(agentId).catch(() => {});
420
+ }
421
+
422
+ return reply.send({
423
+ executionState: checkpoint?.executionState ?? null,
424
+ checkpointAt: checkpoint?.checkpointAt ?? null,
425
+ recalledMemories,
426
+ lastWrite,
427
+ idleMs,
428
+ miniConsolidationTriggered,
429
+ });
430
+ });
431
+
432
+ // ============================================================
433
+ // TASK MANAGEMENT
434
+ // ============================================================
435
+
436
+ app.post('/task/create', async (req, reply) => {
437
+ const body = req.body as {
438
+ agentId: string;
439
+ concept: string;
440
+ content: string;
441
+ tags?: string[];
442
+ priority?: TaskPriority;
443
+ blockedBy?: string;
444
+ };
445
+
446
+ const engram = store.createEngram({
447
+ agentId: body.agentId,
448
+ concept: body.concept,
449
+ content: body.content,
450
+ tags: [...(body.tags ?? []), 'task'],
451
+ salience: 0.9,
452
+ confidence: 0.8,
453
+ salienceFeatures: {
454
+ surprise: 0.5, decisionMade: true, causalDepth: 0.5,
455
+ resolutionEffort: 0.5, eventType: 'decision',
456
+ },
457
+ reasonCodes: ['task-created'],
458
+ taskStatus: body.blockedBy ? 'blocked' : 'open',
459
+ taskPriority: body.priority ?? 'medium',
460
+ blockedBy: body.blockedBy,
461
+ });
462
+
463
+ connectionEngine.enqueue(engram.id);
464
+ embed(`${body.concept} ${body.content}`).then(vec => {
465
+ store.updateEmbedding(engram.id, vec);
466
+ }).catch(() => {});
467
+
468
+ return reply.send(engram);
469
+ });
470
+
471
+ app.post('/task/update', async (req, reply) => {
472
+ const body = req.body as {
473
+ taskId: string;
474
+ status?: TaskStatus;
475
+ priority?: TaskPriority;
476
+ blockedBy?: string | null;
477
+ };
478
+
479
+ const engram = store.getEngram(body.taskId);
480
+ if (!engram || !engram.taskStatus) {
481
+ return reply.code(404).send({ error: 'Task not found' });
482
+ }
483
+
484
+ if (body.blockedBy !== undefined) {
485
+ store.updateBlockedBy(body.taskId, body.blockedBy);
486
+ }
487
+ if (body.status) {
488
+ store.updateTaskStatus(body.taskId, body.status);
489
+ }
490
+ if (body.priority) {
491
+ store.updateTaskPriority(body.taskId, body.priority);
492
+ }
493
+
494
+ return reply.send(store.getEngram(body.taskId));
495
+ });
496
+
497
+ app.get('/task/list/:agentId', async (req, reply) => {
498
+ const { agentId } = req.params as { agentId: string };
499
+ const { status, includeDone } = req.query as { status?: TaskStatus; includeDone?: string };
500
+
501
+ let tasks = store.getTasks(agentId, status);
502
+ if (includeDone !== 'true' && !status) {
503
+ tasks = tasks.filter(t => t.taskStatus !== 'done');
504
+ }
505
+
506
+ return reply.send({ tasks, count: tasks.length });
507
+ });
508
+
509
+ app.get('/task/next/:agentId', async (req, reply) => {
510
+ const { agentId } = req.params as { agentId: string };
511
+ const next = store.getNextTask(agentId);
512
+ return reply.send(next ? { task: next } : { task: null, message: 'No actionable tasks' });
513
+ });
514
+
515
+ // Time warp — shift all timestamps backward by N days (for testing)
516
+ app.post('/system/time-warp', async (req, reply) => {
517
+ const body = req.body as { agentId: string; days: number };
518
+ const ms = body.days * 24 * 60 * 60 * 1000;
519
+ const shifted = store.timeWarp(body.agentId, ms);
520
+ return reply.send({ shifted, days: body.days });
521
+ });
522
+
523
+ app.get('/health', async () => ({
524
+ status: 'ok',
525
+ timestamp: new Date().toISOString(),
526
+ version: '0.3.0',
527
+ }));
528
+ }