agent-working-memory 0.7.17 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +156 -15
- package/dist/adapters/common.js +202 -202
- package/dist/adapters/http.js +22 -22
- package/dist/api/routes.d.ts +1 -1
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +258 -79
- package/dist/api/routes.js.map +1 -1
- package/dist/cli/migrate.d.ts +42 -0
- package/dist/cli/migrate.d.ts.map +1 -0
- package/dist/cli/migrate.js +268 -0
- package/dist/cli/migrate.js.map +1 -0
- package/dist/cli.js +147 -103
- package/dist/cli.js.map +1 -1
- package/dist/coordination/circuit-breaker.d.ts +24 -0
- package/dist/coordination/circuit-breaker.d.ts.map +1 -0
- package/dist/coordination/circuit-breaker.js +70 -0
- package/dist/coordination/circuit-breaker.js.map +1 -0
- package/dist/coordination/failure-modes.d.ts +21 -0
- package/dist/coordination/failure-modes.d.ts.map +1 -0
- package/dist/coordination/failure-modes.js +49 -0
- package/dist/coordination/failure-modes.js.map +1 -0
- package/dist/coordination/peer-decisions.js +7 -7
- package/dist/coordination/routes.d.ts.map +1 -1
- package/dist/coordination/routes.js +44 -1
- package/dist/coordination/routes.js.map +1 -1
- package/dist/coordination/schema.d.ts.map +1 -1
- package/dist/coordination/schema.js +18 -0
- package/dist/coordination/schema.js.map +1 -1
- package/dist/coordination/schemas.d.ts +9 -9
- package/dist/coordination/stale.d.ts +9 -1
- package/dist/coordination/stale.d.ts.map +1 -1
- package/dist/coordination/stale.js +53 -9
- package/dist/coordination/stale.js.map +1 -1
- package/dist/core/embeddings.d.ts +16 -10
- package/dist/core/embeddings.d.ts.map +1 -1
- package/dist/core/embeddings.js +54 -38
- package/dist/core/embeddings.js.map +1 -1
- package/dist/core/ml-worker-entry.d.ts +17 -0
- package/dist/core/ml-worker-entry.d.ts.map +1 -0
- package/dist/core/ml-worker-entry.js +193 -0
- package/dist/core/ml-worker-entry.js.map +1 -0
- package/dist/core/ml-worker.d.ts +59 -0
- package/dist/core/ml-worker.d.ts.map +1 -0
- package/dist/core/ml-worker.js +253 -0
- package/dist/core/ml-worker.js.map +1 -0
- package/dist/core/query-expander.d.ts +13 -15
- package/dist/core/query-expander.d.ts.map +1 -1
- package/dist/core/query-expander.js +48 -54
- package/dist/core/query-expander.js.map +1 -1
- package/dist/core/reranker.d.ts +9 -19
- package/dist/core/reranker.d.ts.map +1 -1
- package/dist/core/reranker.js +33 -35
- package/dist/core/reranker.js.map +1 -1
- package/dist/core/salience.d.ts +32 -5
- package/dist/core/salience.d.ts.map +1 -1
- package/dist/core/salience.js +201 -28
- package/dist/core/salience.js.map +1 -1
- package/dist/core/write-pipeline.d.ts +10 -2
- package/dist/core/write-pipeline.d.ts.map +1 -1
- package/dist/core/write-pipeline.js +218 -49
- package/dist/core/write-pipeline.js.map +1 -1
- package/dist/engine/activation.d.ts +2 -2
- package/dist/engine/activation.d.ts.map +1 -1
- package/dist/engine/activation.js +238 -115
- package/dist/engine/activation.js.map +1 -1
- package/dist/engine/confidence.d.ts +62 -0
- package/dist/engine/confidence.d.ts.map +1 -0
- package/dist/engine/confidence.js +100 -0
- package/dist/engine/confidence.js.map +1 -0
- package/dist/engine/connections.d.ts +47 -8
- package/dist/engine/connections.d.ts.map +1 -1
- package/dist/engine/connections.js +83 -22
- package/dist/engine/connections.js.map +1 -1
- package/dist/engine/consolidation-scheduler.d.ts +46 -10
- package/dist/engine/consolidation-scheduler.d.ts.map +1 -1
- package/dist/engine/consolidation-scheduler.js +170 -50
- package/dist/engine/consolidation-scheduler.js.map +1 -1
- package/dist/engine/consolidation.d.ts +16 -2
- package/dist/engine/consolidation.d.ts.map +1 -1
- package/dist/engine/consolidation.js +143 -44
- package/dist/engine/consolidation.js.map +1 -1
- package/dist/engine/eval.d.ts +2 -2
- package/dist/engine/eval.d.ts.map +1 -1
- package/dist/engine/eval.js +9 -9
- package/dist/engine/eval.js.map +1 -1
- package/dist/engine/eviction.d.ts +4 -4
- package/dist/engine/eviction.d.ts.map +1 -1
- package/dist/engine/eviction.js +15 -15
- package/dist/engine/eviction.js.map +1 -1
- package/dist/engine/retraction.d.ts +93 -5
- package/dist/engine/retraction.d.ts.map +1 -1
- package/dist/engine/retraction.js +238 -19
- package/dist/engine/retraction.js.map +1 -1
- package/dist/engine/staging.d.ts +1 -1
- package/dist/engine/staging.d.ts.map +1 -1
- package/dist/engine/staging.js +3 -3
- package/dist/engine/staging.js.map +1 -1
- package/dist/hooks/sidecar.d.ts +1 -1
- package/dist/hooks/sidecar.d.ts.map +1 -1
- package/dist/hooks/sidecar.js +4 -4
- package/dist/hooks/sidecar.js.map +1 -1
- package/dist/index.js +48 -31
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +167 -141
- package/dist/mcp.js.map +1 -1
- package/dist/storage/factory.d.ts +55 -0
- package/dist/storage/factory.d.ts.map +1 -0
- package/dist/storage/factory.js +135 -0
- package/dist/storage/factory.js.map +1 -0
- package/dist/storage/pglite-schema.d.ts +20 -0
- package/dist/storage/pglite-schema.d.ts.map +1 -0
- package/dist/storage/pglite-schema.js +165 -0
- package/dist/storage/pglite-schema.js.map +1 -0
- package/dist/storage/pglite.d.ts +187 -0
- package/dist/storage/pglite.d.ts.map +1 -0
- package/dist/storage/pglite.js +1114 -0
- package/dist/storage/pglite.js.map +1 -0
- package/dist/storage/sqlite.d.ts +117 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +357 -8
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/storage/store.d.ts +63 -0
- package/dist/storage/store.d.ts.map +1 -0
- package/dist/storage/store.js +27 -0
- package/dist/storage/store.js.map +1 -0
- package/dist/types/engram.d.ts +92 -2
- package/dist/types/engram.d.ts.map +1 -1
- package/dist/types/engram.js.map +1 -1
- package/package.json +3 -1
- package/src/adapters/claude-code.ts +218 -218
- package/src/adapters/codex.ts +252 -252
- package/src/adapters/common.ts +411 -411
- package/src/adapters/cursor.ts +135 -135
- package/src/adapters/http.ts +93 -93
- package/src/adapters/index.ts +31 -31
- package/src/adapters/types.ts +75 -75
- package/src/api/index.ts +3 -3
- package/src/api/routes.ts +970 -714
- package/src/cli/migrate.ts +307 -0
- package/src/cli.ts +755 -719
- package/src/coordination/circuit-breaker.ts +83 -0
- package/src/coordination/events.ts +90 -90
- package/src/coordination/failure-modes.ts +50 -0
- package/src/coordination/peer-decisions.ts +105 -105
- package/src/coordination/plugin-loader.ts +60 -60
- package/src/coordination/plugin.ts +44 -44
- package/src/coordination/routes.ts +56 -1
- package/src/coordination/schema.ts +13 -0
- package/src/coordination/stale.ts +80 -11
- package/src/coordination/types.ts +311 -311
- package/src/coordination/write-mutex.ts +69 -69
- package/src/core/auto-tagger.ts +168 -168
- package/src/core/decay.ts +63 -63
- package/src/core/embeddings.ts +110 -93
- package/src/core/index.ts +5 -5
- package/src/core/logger.ts +36 -36
- package/src/core/ml-worker-entry.ts +194 -0
- package/src/core/ml-worker.ts +281 -0
- package/src/core/query-expander.ts +122 -128
- package/src/core/reranker.ts +119 -125
- package/src/core/salience.ts +200 -33
- package/src/core/write-pipeline.ts +521 -343
- package/src/engine/activation.ts +235 -109
- package/src/engine/confidence.ts +120 -0
- package/src/engine/connections.ts +162 -103
- package/src/engine/consolidation-scheduler.ts +242 -125
- package/src/engine/consolidation.ts +138 -45
- package/src/engine/eval.ts +102 -102
- package/src/engine/eviction.ts +101 -101
- package/src/engine/index.ts +8 -8
- package/src/engine/retraction.ts +366 -116
- package/src/engine/staging.ts +74 -74
- package/src/hooks/sidecar.ts +5 -5
- package/src/index.ts +226 -212
- package/src/mcp.ts +1145 -1121
- package/src/storage/factory.ts +147 -0
- package/src/storage/index.ts +3 -3
- package/src/storage/pglite-schema.ts +166 -0
- package/src/storage/pglite.ts +1363 -0
- package/src/storage/sqlite.ts +402 -7
- package/src/storage/store.ts +80 -0
- package/src/types/agent.ts +67 -67
- package/src/types/checkpoint.ts +46 -46
- package/src/types/engram.ts +101 -3
- package/src/types/eval.ts +100 -100
- package/src/types/index.ts +6 -6
package/src/api/routes.ts
CHANGED
|
@@ -1,714 +1,970 @@
|
|
|
1
|
-
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
/**
|
|
4
|
-
* API Routes — the black box interface agents interact with.
|
|
5
|
-
*
|
|
6
|
-
* Core (agent-facing):
|
|
7
|
-
* POST /memory/write — write a memory (salience filter decides disposition)
|
|
8
|
-
* POST /memory/activate — retrieve by context activation
|
|
9
|
-
* POST /memory/feedback — report whether a memory was useful
|
|
10
|
-
* POST /memory/retract — invalidate a wrong memory
|
|
11
|
-
*
|
|
12
|
-
* Checkpointing:
|
|
13
|
-
* POST /memory/checkpoint — save explicit execution state
|
|
14
|
-
* GET /memory/restore/:agentId — restore state + targeted recall + async mini-consolidation
|
|
15
|
-
*
|
|
16
|
-
* Task management:
|
|
17
|
-
* POST /task/create — create a prioritized task
|
|
18
|
-
* POST /task/update — update status, priority, or blocking
|
|
19
|
-
* GET /task/list/:agentId — list tasks (filtered by status)
|
|
20
|
-
* GET /task/next/:agentId — get highest-priority actionable task
|
|
21
|
-
*
|
|
22
|
-
* Diagnostic (debugging/eval):
|
|
23
|
-
* POST /memory/search — deterministic search (not cognitive)
|
|
24
|
-
* GET /memory/:id — get a specific engram
|
|
25
|
-
* GET /agent/:id/stats — memory stats for an agent
|
|
26
|
-
* GET /agent/:id/metrics — eval metrics
|
|
27
|
-
* POST /agent/register — register a new agent
|
|
28
|
-
*
|
|
29
|
-
* System:
|
|
30
|
-
* POST /system/evict — trigger eviction check
|
|
31
|
-
* POST /system/decay — trigger edge decay
|
|
32
|
-
* POST /system/consolidate — run sleep cycle (strengthen, decay, sweep)
|
|
33
|
-
* GET /health — health check
|
|
34
|
-
*/
|
|
35
|
-
|
|
36
|
-
import type { FastifyInstance } from 'fastify';
|
|
37
|
-
import type { EngramStore } from '../storage/
|
|
38
|
-
import type { ActivationEngine } from '../engine/activation.js';
|
|
39
|
-
import type { ConnectionEngine } from '../engine/connections.js';
|
|
40
|
-
import type { EvictionEngine } from '../engine/eviction.js';
|
|
41
|
-
import type { RetractionEngine } from '../engine/retraction.js';
|
|
42
|
-
import type { EvalEngine } from '../engine/eval.js';
|
|
43
|
-
import type { ConsolidationEngine } from '../engine/consolidation.js';
|
|
44
|
-
import type { ConsolidationScheduler } from '../engine/consolidation-scheduler.js';
|
|
45
|
-
import { evaluateSalience, computeNovelty } from '../core/salience.js';
|
|
46
|
-
import type { SalienceEventType } from '../core/salience.js';
|
|
47
|
-
import { performWrite } from '../core/write-pipeline.js';
|
|
48
|
-
import type { TaskStatus, TaskPriority } from '../types/engram.js';
|
|
49
|
-
import type { ConsciousState } from '../types/checkpoint.js';
|
|
50
|
-
import { DEFAULT_AGENT_CONFIG } from '../types/agent.js';
|
|
51
|
-
import { embed, embedBatch } from '../core/embeddings.js';
|
|
52
|
-
|
|
53
|
-
export interface MemoryDeps {
|
|
54
|
-
store: EngramStore;
|
|
55
|
-
activationEngine: ActivationEngine;
|
|
56
|
-
connectionEngine: ConnectionEngine;
|
|
57
|
-
evictionEngine: EvictionEngine;
|
|
58
|
-
retractionEngine: RetractionEngine;
|
|
59
|
-
evalEngine: EvalEngine;
|
|
60
|
-
consolidationEngine: ConsolidationEngine;
|
|
61
|
-
consolidationScheduler: ConsolidationScheduler;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
65
|
-
const { store, activationEngine, connectionEngine, evictionEngine, retractionEngine, evalEngine, consolidationEngine, consolidationScheduler } = deps;
|
|
66
|
-
|
|
67
|
-
// ============================================================
|
|
68
|
-
// CORE — Agent-facing endpoints
|
|
69
|
-
// ============================================================
|
|
70
|
-
|
|
71
|
-
app.post('/memory/write', async (req, reply) => {
|
|
72
|
-
const body = req.body as {
|
|
73
|
-
agentId: string;
|
|
74
|
-
concept: string;
|
|
75
|
-
content: string;
|
|
76
|
-
tags?: string[];
|
|
77
|
-
eventType?: SalienceEventType;
|
|
78
|
-
surprise?: number;
|
|
79
|
-
decisionMade?: boolean;
|
|
80
|
-
causalDepth?: number;
|
|
81
|
-
resolutionEffort?: number;
|
|
82
|
-
confidence?: number;
|
|
83
|
-
// Memory class — canonical bypasses salience filter; structural is
|
|
84
|
-
// for system-written event-log records (see 0.8 spec). Restored in
|
|
85
|
-
// 0.
|
|
86
|
-
// the 0.7.x refactor — core/salience.ts:88-89,124,187 and
|
|
87
|
-
// core/write-pipeline.ts:77 still expect and honor it, so HTTP
|
|
88
|
-
// callers were silently losing the canonical-bypass signal.
|
|
89
|
-
memory_class?: 'canonical' | 'working' | 'ephemeral';
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
store.
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
);
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
return reply.send(
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
app.
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
};
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
const
|
|
569
|
-
agentId:
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
if (body.
|
|
607
|
-
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
app.
|
|
639
|
-
const
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
const
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
});
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
//
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
1
|
+
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* API Routes — the black box interface agents interact with.
|
|
5
|
+
*
|
|
6
|
+
* Core (agent-facing):
|
|
7
|
+
* POST /memory/write — write a memory (salience filter decides disposition)
|
|
8
|
+
* POST /memory/activate — retrieve by context activation
|
|
9
|
+
* POST /memory/feedback — report whether a memory was useful
|
|
10
|
+
* POST /memory/retract — invalidate a wrong memory
|
|
11
|
+
*
|
|
12
|
+
* Checkpointing:
|
|
13
|
+
* POST /memory/checkpoint — save explicit execution state
|
|
14
|
+
* GET /memory/restore/:agentId — restore state + targeted recall + async mini-consolidation
|
|
15
|
+
*
|
|
16
|
+
* Task management:
|
|
17
|
+
* POST /task/create — create a prioritized task
|
|
18
|
+
* POST /task/update — update status, priority, or blocking
|
|
19
|
+
* GET /task/list/:agentId — list tasks (filtered by status)
|
|
20
|
+
* GET /task/next/:agentId — get highest-priority actionable task
|
|
21
|
+
*
|
|
22
|
+
* Diagnostic (debugging/eval):
|
|
23
|
+
* POST /memory/search — deterministic search (not cognitive)
|
|
24
|
+
* GET /memory/:id — get a specific engram
|
|
25
|
+
* GET /agent/:id/stats — memory stats for an agent
|
|
26
|
+
* GET /agent/:id/metrics — eval metrics
|
|
27
|
+
* POST /agent/register — register a new agent
|
|
28
|
+
*
|
|
29
|
+
* System:
|
|
30
|
+
* POST /system/evict — trigger eviction check
|
|
31
|
+
* POST /system/decay — trigger edge decay
|
|
32
|
+
* POST /system/consolidate — run sleep cycle (strengthen, decay, sweep)
|
|
33
|
+
* GET /health — health check
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import type { FastifyInstance } from 'fastify';
|
|
37
|
+
import type { IEngramStore as EngramStore } from '../storage/store.js';
|
|
38
|
+
import type { ActivationEngine } from '../engine/activation.js';
|
|
39
|
+
import type { ConnectionEngine } from '../engine/connections.js';
|
|
40
|
+
import type { EvictionEngine } from '../engine/eviction.js';
|
|
41
|
+
import type { RetractionEngine } from '../engine/retraction.js';
|
|
42
|
+
import type { EvalEngine } from '../engine/eval.js';
|
|
43
|
+
import type { ConsolidationEngine } from '../engine/consolidation.js';
|
|
44
|
+
import type { ConsolidationScheduler } from '../engine/consolidation-scheduler.js';
|
|
45
|
+
import { evaluateSalience, computeNovelty } from '../core/salience.js';
|
|
46
|
+
import type { SalienceEventType } from '../core/salience.js';
|
|
47
|
+
import { performWrite } from '../core/write-pipeline.js';
|
|
48
|
+
import type { TaskStatus, TaskPriority } from '../types/engram.js';
|
|
49
|
+
import type { ConsciousState } from '../types/checkpoint.js';
|
|
50
|
+
import { DEFAULT_AGENT_CONFIG } from '../types/agent.js';
|
|
51
|
+
import { embed, embedBatch } from '../core/embeddings.js';
|
|
52
|
+
|
|
53
|
+
export interface MemoryDeps {
|
|
54
|
+
store: EngramStore;
|
|
55
|
+
activationEngine: ActivationEngine;
|
|
56
|
+
connectionEngine: ConnectionEngine;
|
|
57
|
+
evictionEngine: EvictionEngine;
|
|
58
|
+
retractionEngine: RetractionEngine;
|
|
59
|
+
evalEngine: EvalEngine;
|
|
60
|
+
consolidationEngine: ConsolidationEngine;
|
|
61
|
+
consolidationScheduler: ConsolidationScheduler;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
65
|
+
const { store, activationEngine, connectionEngine, evictionEngine, retractionEngine, evalEngine, consolidationEngine, consolidationScheduler } = deps;
|
|
66
|
+
|
|
67
|
+
// ============================================================
|
|
68
|
+
// CORE — Agent-facing endpoints
|
|
69
|
+
// ============================================================
|
|
70
|
+
|
|
71
|
+
app.post('/memory/write', async (req, reply) => {
|
|
72
|
+
const body = req.body as {
|
|
73
|
+
agentId: string;
|
|
74
|
+
concept: string;
|
|
75
|
+
content: string;
|
|
76
|
+
tags?: string[];
|
|
77
|
+
eventType?: SalienceEventType;
|
|
78
|
+
surprise?: number;
|
|
79
|
+
decisionMade?: boolean;
|
|
80
|
+
causalDepth?: number;
|
|
81
|
+
resolutionEffort?: number;
|
|
82
|
+
confidence?: number;
|
|
83
|
+
// Memory class — canonical bypasses salience filter; structural is
|
|
84
|
+
// for system-written event-log records (see 0.8 spec). Restored in
|
|
85
|
+
// 0.8.0 after the field was dropped from the HTTP body schema during
|
|
86
|
+
// the 0.7.x refactor — core/salience.ts:88-89,124,187 and
|
|
87
|
+
// core/write-pipeline.ts:77 still expect and honor it, so HTTP
|
|
88
|
+
// callers were silently losing the canonical-bypass signal.
|
|
89
|
+
memory_class?: 'canonical' | 'working' | 'ephemeral' | 'structural';
|
|
90
|
+
// Optional story-time / sequence ordering (0.8 Cluster A). NULL by
|
|
91
|
+
// default. Used by sortBy: "sequence" on /memory/search and by
|
|
92
|
+
// /memory/latest-by-tag (0.8 Cluster C).
|
|
93
|
+
sequence?: number;
|
|
94
|
+
// Force embedding for structural-class writes. Defaults to false for
|
|
95
|
+
// structural (deterministic retrieval only) — opt in here when cognitive
|
|
96
|
+
// recall over a structural engram is desired. (0.8 Cluster A)
|
|
97
|
+
embed?: boolean;
|
|
98
|
+
// Typed cross-record links (0.8 Cluster D). Each reference is
|
|
99
|
+
// {type, matchEngramId?, matchConcept?}. If matchConcept is given
|
|
100
|
+
// without matchEngramId, AWM resolves it to the most recent active
|
|
101
|
+
// engram at write time and stores BOTH on the reference (stable link).
|
|
102
|
+
// If no match found, stores just matchConcept (caller may be linking
|
|
103
|
+
// to a future or deleted engram). Types: advances | resolves |
|
|
104
|
+
// subverts | abandons | extends | supersedes.
|
|
105
|
+
references?: Array<{
|
|
106
|
+
type: 'advances' | 'resolves' | 'subverts' | 'abandons' | 'extends' | 'supersedes';
|
|
107
|
+
matchEngramId?: string;
|
|
108
|
+
matchConcept?: string;
|
|
109
|
+
matchTags?: string[];
|
|
110
|
+
}>;
|
|
111
|
+
// Agent-provided metadata (stored as searchable tags)
|
|
112
|
+
project?: string;
|
|
113
|
+
topic?: string;
|
|
114
|
+
source?: string;
|
|
115
|
+
confidenceLevel?: string;
|
|
116
|
+
sessionId?: string;
|
|
117
|
+
intent?: string;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
if (!body.agentId || typeof body.agentId !== 'string' ||
|
|
121
|
+
!body.concept || typeof body.concept !== 'string' ||
|
|
122
|
+
!body.content || typeof body.content !== 'string') {
|
|
123
|
+
return reply.status(400).send({ error: 'agentId, concept, and content are required strings' });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Assemble tags: user-provided + agent metadata
|
|
127
|
+
const userTags = body.tags ?? [];
|
|
128
|
+
const metaTags: string[] = [];
|
|
129
|
+
if (body.project) metaTags.push(`proj=${body.project}`);
|
|
130
|
+
if (body.topic) metaTags.push(`topic=${body.topic}`);
|
|
131
|
+
if (body.source) metaTags.push(`src=${body.source}`);
|
|
132
|
+
if (body.confidenceLevel) metaTags.push(`conf=${body.confidenceLevel}`);
|
|
133
|
+
if (body.sessionId) metaTags.push(`sid=${body.sessionId}`);
|
|
134
|
+
if (body.intent) metaTags.push(`intent=${body.intent}`);
|
|
135
|
+
|
|
136
|
+
// Resolve references (0.8 Cluster D): if matchConcept is given without
|
|
137
|
+
// matchEngramId, look up the most recent active engram with that concept
|
|
138
|
+
// (+ optional matchTags) and store both. Stable link survives concept
|
|
139
|
+
// edits. No match found → store just matchConcept so the intent
|
|
140
|
+
// (link-to-future or link-to-deleted) is preserved.
|
|
141
|
+
const resolvedRefs = await Promise.all((body.references ?? []).map(async ref => {
|
|
142
|
+
if (!ref.matchEngramId && ref.matchConcept) {
|
|
143
|
+
const matched = await store.findActiveMatchByConcept(
|
|
144
|
+
body.agentId, ref.matchConcept, ref.matchTags,
|
|
145
|
+
);
|
|
146
|
+
if (matched) {
|
|
147
|
+
return { type: ref.type, matchEngramId: matched.id, matchConcept: ref.matchConcept };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return { type: ref.type, matchEngramId: ref.matchEngramId, matchConcept: ref.matchConcept };
|
|
151
|
+
}));
|
|
152
|
+
|
|
153
|
+
const result = await performWrite({ store, connectionEngine }, {
|
|
154
|
+
agentId: body.agentId,
|
|
155
|
+
concept: body.concept,
|
|
156
|
+
content: body.content,
|
|
157
|
+
tags: [...userTags, ...metaTags],
|
|
158
|
+
memoryClass: body.memory_class,
|
|
159
|
+
eventType: body.eventType,
|
|
160
|
+
surprise: body.surprise,
|
|
161
|
+
decisionMade: body.decisionMade,
|
|
162
|
+
causalDepth: body.causalDepth,
|
|
163
|
+
resolutionEffort: body.resolutionEffort,
|
|
164
|
+
confidence: body.confidence,
|
|
165
|
+
sequence: body.sequence,
|
|
166
|
+
embed: body.embed,
|
|
167
|
+
references: resolvedRefs.length > 0 ? resolvedRefs : undefined,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Auto-checkpoint always (covers create, reinforce, and supersede).
|
|
171
|
+
try { await store.updateAutoCheckpointWrite(body.agentId, result.engram.id); } catch { /* non-fatal */ }
|
|
172
|
+
|
|
173
|
+
if (result.action === 'reinforce') {
|
|
174
|
+
return reply.code(200).send({
|
|
175
|
+
stored: false,
|
|
176
|
+
action: 'reinforce',
|
|
177
|
+
disposition: 'reinforced',
|
|
178
|
+
engram: result.engram,
|
|
179
|
+
reinforce: result.reinforce,
|
|
180
|
+
novelty: result.noveltyResult.novelty,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// create / supersede paths follow legacy temporal-edge + episode logic.
|
|
185
|
+
// Structural-class writes (0.8 Cluster A) skip these: they're system-written
|
|
186
|
+
// event-log records, not conversational beats, so keeping them out of the
|
|
187
|
+
// temporal graph + episode index keeps cognitive retrieval clean.
|
|
188
|
+
const isStructural = body.memory_class === 'structural';
|
|
189
|
+
if (!isStructural) {
|
|
190
|
+
try {
|
|
191
|
+
const prev = await store.getLatestEngram(body.agentId, result.engram.id);
|
|
192
|
+
if (prev) {
|
|
193
|
+
await store.upsertAssociation(prev.id, result.engram.id, 0.3, 'temporal', 0.8);
|
|
194
|
+
}
|
|
195
|
+
} catch { /* Temporal edge creation is non-fatal */ }
|
|
196
|
+
|
|
197
|
+
if (result.salience
|
|
198
|
+
&& (result.salience.disposition === 'active' || result.salience.disposition === 'discard')) {
|
|
199
|
+
try {
|
|
200
|
+
let episode = await store.getActiveEpisode(body.agentId, 3600_000);
|
|
201
|
+
if (!episode) {
|
|
202
|
+
episode = await store.createEpisode({ agentId: body.agentId, label: body.concept });
|
|
203
|
+
}
|
|
204
|
+
await store.addEngramToEpisode(result.engram.id, episode.id);
|
|
205
|
+
} catch { /* Episode assignment is non-fatal */ }
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const isLowSalience = result.salience?.disposition === 'discard';
|
|
210
|
+
return reply.code(201).send({
|
|
211
|
+
stored: true,
|
|
212
|
+
action: result.action,
|
|
213
|
+
// disposition: legacy field — 'low-salience' is returned for discard so
|
|
214
|
+
// callers know the engram was kept but marked low-value. The raw inner
|
|
215
|
+
// salience decision is exposed as `salienceDisposition` for callers
|
|
216
|
+
// (and tests) that want the unmapped value.
|
|
217
|
+
disposition: isLowSalience ? 'low-salience' : (result.salience?.disposition ?? 'active'),
|
|
218
|
+
salienceDisposition: result.salience?.disposition ?? null,
|
|
219
|
+
salience: result.salience?.score ?? 0,
|
|
220
|
+
reasonCodes: result.salience?.reasonCodes ?? [],
|
|
221
|
+
engram: result.engram,
|
|
222
|
+
supersedeOf: result.supersedeOf,
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Bulk write — accepts many facts in one request.
|
|
228
|
+
* Creates engrams in a single transaction, embeds in batch.
|
|
229
|
+
* Returns all IDs for downstream supersession calls.
|
|
230
|
+
*/
|
|
231
|
+
app.post('/memory/write-batch', async (req, reply) => {
|
|
232
|
+
const body = req.body as {
|
|
233
|
+
agentId: string;
|
|
234
|
+
sessionId?: string; // Shared session ID for all memories in this batch
|
|
235
|
+
memories: Array<{
|
|
236
|
+
concept: string;
|
|
237
|
+
content: string;
|
|
238
|
+
tags?: string[];
|
|
239
|
+
supersedes?: string;
|
|
240
|
+
sessionId?: string; // Per-memory session override
|
|
241
|
+
}>;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
if (!body.agentId || !body.memories || body.memories.length === 0) {
|
|
245
|
+
return reply.code(400).send({ error: 'agentId and non-empty memories array required' });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const results: Array<{ id: string; concept: string; disposition: string }> = [];
|
|
249
|
+
|
|
250
|
+
for (const mem of body.memories) {
|
|
251
|
+
// Add session ID tag if provided (batch-level or per-memory)
|
|
252
|
+
const sid = mem.sessionId ?? body.sessionId;
|
|
253
|
+
const memTags = [...(mem.tags ?? [])];
|
|
254
|
+
if (sid) memTags.push(`sid=${sid}`);
|
|
255
|
+
|
|
256
|
+
const engram = await store.createEngram({
|
|
257
|
+
agentId: body.agentId,
|
|
258
|
+
concept: mem.concept,
|
|
259
|
+
content: mem.content,
|
|
260
|
+
tags: memTags,
|
|
261
|
+
salience: 0.5,
|
|
262
|
+
confidence: 0.5,
|
|
263
|
+
supersedes: mem.supersedes ?? undefined,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Handle supersession inline — archive superseded memory to remove from active pool
|
|
267
|
+
if (mem.supersedes) {
|
|
268
|
+
await store.supersedeEngram(mem.supersedes, engram.id);
|
|
269
|
+
await store.updateConfidence(mem.supersedes, 0.1);
|
|
270
|
+
await store.updateStage(mem.supersedes, 'archived'); // Remove from active search pool
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
results.push({ id: engram.id, concept: mem.concept, disposition: 'active' });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Batch embed synchronously — ensures embeddings are ready before queries hit
|
|
277
|
+
const texts = body.memories.map((m, i) => `${m.concept} ${m.content}`);
|
|
278
|
+
try {
|
|
279
|
+
const vecs = await embedBatch(texts);
|
|
280
|
+
for (let i = 0; i < vecs.length; i++) {
|
|
281
|
+
if (results[i]) {
|
|
282
|
+
await store.updateEmbedding(results[i].id, vecs[i]);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
} catch { /* Embedding failure is non-fatal */ }
|
|
286
|
+
|
|
287
|
+
try { await store.updateAutoCheckpointWrite(body.agentId, results[results.length - 1]?.id ?? ''); } catch {}
|
|
288
|
+
|
|
289
|
+
return reply.code(201).send({
|
|
290
|
+
stored: results.length,
|
|
291
|
+
results,
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
app.post('/memory/activate', async (req, reply) => {
|
|
296
|
+
const body = req.body as {
|
|
297
|
+
agentId: string;
|
|
298
|
+
context: string;
|
|
299
|
+
limit?: number;
|
|
300
|
+
minScore?: number;
|
|
301
|
+
includeStaging?: boolean;
|
|
302
|
+
useReranker?: boolean;
|
|
303
|
+
useExpansion?: boolean;
|
|
304
|
+
abstentionThreshold?: number;
|
|
305
|
+
requireConfidence?: number;
|
|
306
|
+
workspace?: string;
|
|
307
|
+
bm25Only?: boolean;
|
|
308
|
+
granularity?: 'full' | 'compact' | 'auto';
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const results = await activationEngine.activate({
|
|
312
|
+
agentId: body.agentId,
|
|
313
|
+
context: body.context,
|
|
314
|
+
limit: body.limit,
|
|
315
|
+
minScore: body.minScore,
|
|
316
|
+
includeStaging: body.includeStaging,
|
|
317
|
+
useReranker: body.useReranker,
|
|
318
|
+
useExpansion: body.useExpansion,
|
|
319
|
+
abstentionThreshold: body.abstentionThreshold,
|
|
320
|
+
requireConfidence: body.requireConfidence,
|
|
321
|
+
workspace: body.workspace,
|
|
322
|
+
bm25Only: body.bm25Only,
|
|
323
|
+
granularity: body.granularity,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// Auto-checkpoint: track recall for consolidation scheduling
|
|
327
|
+
try {
|
|
328
|
+
const ids = results.map(r => r.engram.id);
|
|
329
|
+
await store.updateAutoCheckpointRecall(body.agentId, body.context, ids);
|
|
330
|
+
} catch { /* non-fatal */ }
|
|
331
|
+
|
|
332
|
+
// Surface recall confidence as a top-level field too — same value is on
|
|
333
|
+
// every result, but it describes the recall as a whole, so exposing it
|
|
334
|
+
// once is easier for consumers (and lets them inspect 0-result recalls).
|
|
335
|
+
const confidence = results[0]?.confidence ?? 0;
|
|
336
|
+
return reply.send({ results, confidence });
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
app.post('/memory/feedback', async (req, reply) => {
|
|
340
|
+
const body = req.body as {
|
|
341
|
+
activationEventId?: string;
|
|
342
|
+
engramId: string;
|
|
343
|
+
useful: boolean;
|
|
344
|
+
context?: string;
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
await store.logRetrievalFeedback(
|
|
348
|
+
body.activationEventId ?? null,
|
|
349
|
+
body.engramId,
|
|
350
|
+
body.useful,
|
|
351
|
+
body.context ?? ''
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
// Update engram confidence based on feedback
|
|
355
|
+
const engram = await store.getEngram(body.engramId);
|
|
356
|
+
if (engram) {
|
|
357
|
+
const config = DEFAULT_AGENT_CONFIG;
|
|
358
|
+
const delta = body.useful
|
|
359
|
+
? config.feedbackPositiveBoost
|
|
360
|
+
: -config.feedbackNegativePenalty;
|
|
361
|
+
await store.updateConfidence(engram.id, engram.confidence + delta);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Touch activity for consolidation scheduling
|
|
365
|
+
if (engram) {
|
|
366
|
+
try { await store.touchActivity(engram.agentId); } catch { /* non-fatal */ }
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return reply.send({ recorded: true });
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
app.post('/memory/retract', async (req, reply) => {
|
|
373
|
+
const body = req.body as {
|
|
374
|
+
agentId: string;
|
|
375
|
+
targetEngramId: string;
|
|
376
|
+
reason: string;
|
|
377
|
+
counterContent?: string;
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const result = await retractionEngine.retract({
|
|
381
|
+
agentId: body.agentId,
|
|
382
|
+
targetEngramId: body.targetEngramId,
|
|
383
|
+
reason: body.reason,
|
|
384
|
+
counterContent: body.counterContent,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Touch activity for consolidation scheduling
|
|
388
|
+
try { await store.touchActivity(body.agentId); } catch { /* non-fatal */ }
|
|
389
|
+
|
|
390
|
+
return reply.send(result);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
app.post('/memory/supersede', async (req, reply) => {
|
|
394
|
+
const body = req.body as {
|
|
395
|
+
// ── Form A — supersede by existing engram IDs (pre-0.8 behavior) ──
|
|
396
|
+
oldEngramId?: string;
|
|
397
|
+
newEngramId?: string;
|
|
398
|
+
// ── Form B — atomic write-and-supersede by concept match (0.8 Cluster D) ──
|
|
399
|
+
agentId?: string;
|
|
400
|
+
matchConcept?: string;
|
|
401
|
+
matchTags?: string[];
|
|
402
|
+
newEngram?: {
|
|
403
|
+
concept: string;
|
|
404
|
+
content: string;
|
|
405
|
+
tags?: string[];
|
|
406
|
+
memory_class?: 'canonical' | 'working' | 'ephemeral' | 'structural';
|
|
407
|
+
sequence?: number;
|
|
408
|
+
eventType?: SalienceEventType;
|
|
409
|
+
};
|
|
410
|
+
// Common
|
|
411
|
+
reason?: string;
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
const isFormA = !!(body.oldEngramId && body.newEngramId);
|
|
415
|
+
const isFormB = !!(body.matchConcept && body.newEngram && body.agentId);
|
|
416
|
+
|
|
417
|
+
if (isFormA && isFormB) {
|
|
418
|
+
return reply.code(400).send({
|
|
419
|
+
error: 'Pass either {oldEngramId, newEngramId} (Form A) OR ' +
|
|
420
|
+
'{agentId, matchConcept, newEngram} (Form B), not both.',
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
if (!isFormA && !isFormB) {
|
|
424
|
+
return reply.code(400).send({
|
|
425
|
+
error: 'Missing required fields. Form A: {oldEngramId, newEngramId}. ' +
|
|
426
|
+
'Form B: {agentId, matchConcept, newEngram}.',
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// ── Form A — by IDs ──
|
|
431
|
+
if (isFormA) {
|
|
432
|
+
const oldEngram = await store.getEngram(body.oldEngramId!);
|
|
433
|
+
const newEngram = await store.getEngram(body.newEngramId!);
|
|
434
|
+
if (!oldEngram) return reply.code(404).send({ error: `Old engram ${body.oldEngramId} not found` });
|
|
435
|
+
if (!newEngram) return reply.code(404).send({ error: `New engram ${body.newEngramId} not found` });
|
|
436
|
+
|
|
437
|
+
await store.upsertAssociation(body.newEngramId!, body.oldEngramId!, 0.8, 'causal', 1.0);
|
|
438
|
+
await store.updateConfidence(body.oldEngramId!, oldEngram.confidence * 0.2);
|
|
439
|
+
await store.supersedeEngram(body.oldEngramId!, body.newEngramId!);
|
|
440
|
+
try { await store.touchActivity(oldEngram.agentId); } catch { /* non-fatal */ }
|
|
441
|
+
|
|
442
|
+
return reply.send({
|
|
443
|
+
superseded: body.oldEngramId,
|
|
444
|
+
supersededBy: body.newEngramId,
|
|
445
|
+
reason: body.reason ?? 'outdated',
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// ── Form B — atomic write-and-supersede by concept match ──
|
|
450
|
+
// Find old (most recent active match by concept + optional tags), write
|
|
451
|
+
// new engram via performWrite, link them — all in one SQL transaction.
|
|
452
|
+
// If no match: write new anyway, return { superseded: null }.
|
|
453
|
+
// AWM 0.8.x P4b follow-up: Form B atomicity via withTransaction.
|
|
454
|
+
// Holds the SQLite/PGlite lock across the async write + supersede pair
|
|
455
|
+
// so callers never observe a half-completed state.
|
|
456
|
+
const result = await (store.withTransaction(async () => {
|
|
457
|
+
const matched = await store.findActiveMatchByConcept(
|
|
458
|
+
body.agentId!, body.matchConcept!, body.matchTags,
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
const writeRes = await performWrite({ store, connectionEngine }, {
|
|
462
|
+
agentId: body.agentId!,
|
|
463
|
+
concept: body.newEngram!.concept,
|
|
464
|
+
content: body.newEngram!.content,
|
|
465
|
+
tags: body.newEngram!.tags ?? [],
|
|
466
|
+
memoryClass: body.newEngram!.memory_class,
|
|
467
|
+
sequence: body.newEngram!.sequence,
|
|
468
|
+
eventType: body.newEngram!.eventType,
|
|
469
|
+
enableReinforcement: false,
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
if (matched) {
|
|
473
|
+
await store.upsertAssociation(writeRes.engram.id, matched.id, 0.8, 'causal', 1.0);
|
|
474
|
+
await store.updateConfidence(matched.id, matched.confidence * 0.2);
|
|
475
|
+
await store.supersedeEngram(matched.id, writeRes.engram.id);
|
|
476
|
+
}
|
|
477
|
+
return { writeRes, matched };
|
|
478
|
+
}) as Promise<{ writeRes: Awaited<ReturnType<typeof performWrite>>; matched: any }>);
|
|
479
|
+
|
|
480
|
+
try { await store.touchActivity(body.agentId!); } catch { /* non-fatal */ }
|
|
481
|
+
|
|
482
|
+
return reply.code(201).send({
|
|
483
|
+
newEngram: result.writeRes.engram,
|
|
484
|
+
superseded: result.matched ? result.matched.id : null,
|
|
485
|
+
supersededBy: result.writeRes.engram.id,
|
|
486
|
+
reason: body.reason ?? 'resolved by concept match',
|
|
487
|
+
});
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
// ============================================================
|
|
491
|
+
// DIAGNOSTIC — Debugging and inspection
|
|
492
|
+
// ============================================================
|
|
493
|
+
|
|
494
|
+
app.post('/memory/search', async (req, reply) => {
|
|
495
|
+
const body = req.body as {
|
|
496
|
+
agentId: string;
|
|
497
|
+
text?: string;
|
|
498
|
+
concept?: string;
|
|
499
|
+
tags?: string[]; // legacy AND-filter — preserved, equivalent to tagsAll
|
|
500
|
+
tagsAll?: string[]; // 0.8 Cluster B — explicit AND
|
|
501
|
+
tagsAny?: string[]; // 0.8 Cluster B — OR (at least one)
|
|
502
|
+
tagsNone?: string[]; // 0.8 Cluster B — NOT (exclude all)
|
|
503
|
+
stage?: string;
|
|
504
|
+
retracted?: boolean;
|
|
505
|
+
limit?: number;
|
|
506
|
+
offset?: number;
|
|
507
|
+
sortBy?: 'createdAt' | 'sequence' | 'salience' | 'confidence' | 'lastAccessed';
|
|
508
|
+
sortOrder?: 'asc' | 'desc';
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
const results = await store.search({
|
|
512
|
+
agentId: body.agentId,
|
|
513
|
+
text: body.text,
|
|
514
|
+
concept: body.concept,
|
|
515
|
+
tags: body.tags,
|
|
516
|
+
tagsAll: body.tagsAll,
|
|
517
|
+
tagsAny: body.tagsAny,
|
|
518
|
+
tagsNone: body.tagsNone,
|
|
519
|
+
stage: body.stage as any,
|
|
520
|
+
retracted: body.retracted,
|
|
521
|
+
limit: body.limit,
|
|
522
|
+
offset: body.offset,
|
|
523
|
+
sortBy: body.sortBy,
|
|
524
|
+
sortOrder: body.sortOrder,
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
return reply.send({ results, count: results.length });
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
// ============================================================
|
|
531
|
+
// 0.8 Cluster C — materialized-view + atomic-counter endpoints
|
|
532
|
+
// ============================================================
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* For each distinct value of `tagKey`, return the most recent active
|
|
536
|
+
* engram. Used by NovelForge for "latest emotional state per character",
|
|
537
|
+
* "latest motif phase per motif", etc.
|
|
538
|
+
*/
|
|
539
|
+
app.post('/memory/latest-by-tag', async (req, reply) => {
|
|
540
|
+
const body = req.body as {
|
|
541
|
+
agentId: string;
|
|
542
|
+
tagKey: string; // e.g. "character=", "motif="
|
|
543
|
+
scopeTagsAll?: string[]; // optional narrowing
|
|
544
|
+
retracted?: boolean;
|
|
545
|
+
sortBy?: 'createdAt' | 'sequence';
|
|
546
|
+
limit?: number;
|
|
547
|
+
};
|
|
548
|
+
if (!body.agentId || !body.tagKey) {
|
|
549
|
+
return reply.code(400).send({ error: 'agentId and tagKey are required' });
|
|
550
|
+
}
|
|
551
|
+
const results = await store.getLatestByTag({
|
|
552
|
+
agentId: body.agentId,
|
|
553
|
+
tagKeyPrefix: body.tagKey,
|
|
554
|
+
scopeTagsAll: body.scopeTagsAll,
|
|
555
|
+
retracted: body.retracted ?? false,
|
|
556
|
+
sortBy: body.sortBy,
|
|
557
|
+
limit: body.limit,
|
|
558
|
+
});
|
|
559
|
+
return reply.send({ results, count: results.length });
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Filter by tag-set operators, sort by numeric value extracted from a
|
|
564
|
+
* tag prefix, return top N. Used by NovelForge for "top N active
|
|
565
|
+
* promises by weight".
|
|
566
|
+
*/
|
|
567
|
+
app.post('/memory/top-by', async (req, reply) => {
|
|
568
|
+
const body = req.body as {
|
|
569
|
+
agentId: string;
|
|
570
|
+
sortField: string; // tag prefix, e.g. "weight="
|
|
571
|
+
order?: 'asc' | 'desc'; // default desc
|
|
572
|
+
filterTagsAll?: string[];
|
|
573
|
+
filterTagsAny?: string[];
|
|
574
|
+
filterTagsNone?: string[];
|
|
575
|
+
retracted?: boolean;
|
|
576
|
+
limit?: number;
|
|
577
|
+
};
|
|
578
|
+
if (!body.agentId || !body.sortField) {
|
|
579
|
+
return reply.code(400).send({ error: 'agentId and sortField are required' });
|
|
580
|
+
}
|
|
581
|
+
const results = await store.getTopBy({
|
|
582
|
+
agentId: body.agentId,
|
|
583
|
+
sortField: body.sortField,
|
|
584
|
+
order: body.order ?? 'desc',
|
|
585
|
+
filterTagsAll: body.filterTagsAll,
|
|
586
|
+
filterTagsAny: body.filterTagsAny,
|
|
587
|
+
filterTagsNone: body.filterTagsNone,
|
|
588
|
+
retracted: body.retracted ?? false,
|
|
589
|
+
limit: body.limit,
|
|
590
|
+
});
|
|
591
|
+
return reply.send({ results, count: results.length });
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Compute effective state of an engram from referenced events. Two
|
|
596
|
+
* targeting modes: by ID, or by concept match (same semantics as Form B's
|
|
597
|
+
* findActiveMatchByConcept).
|
|
598
|
+
*/
|
|
599
|
+
app.post('/memory/resolve', async (req, reply) => {
|
|
600
|
+
const body = req.body as {
|
|
601
|
+
agentId: string;
|
|
602
|
+
targetEngramId?: string;
|
|
603
|
+
matchConcept?: string;
|
|
604
|
+
matchTags?: string[];
|
|
605
|
+
};
|
|
606
|
+
if (!body.agentId) {
|
|
607
|
+
return reply.code(400).send({ error: 'agentId is required' });
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
let targetId = body.targetEngramId;
|
|
611
|
+
if (!targetId && body.matchConcept) {
|
|
612
|
+
const matched = await store.findActiveMatchByConcept(
|
|
613
|
+
body.agentId, body.matchConcept, body.matchTags,
|
|
614
|
+
);
|
|
615
|
+
if (!matched) {
|
|
616
|
+
return reply.code(404).send({
|
|
617
|
+
error: `No active engram matches concept "${body.matchConcept}"`,
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
targetId = matched.id;
|
|
621
|
+
}
|
|
622
|
+
if (!targetId) {
|
|
623
|
+
return reply.code(400).send({
|
|
624
|
+
error: 'Provide either targetEngramId or matchConcept',
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const result = await store.resolveEffectiveState(targetId);
|
|
629
|
+
if (!result) return reply.code(404).send({ error: `Engram ${targetId} not found` });
|
|
630
|
+
return reply.send(result);
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Race-free next-sequence allocator. Caller writes the engram with the
|
|
635
|
+
* returned value in `sequence`. Doesn't reserve — concurrent allocations
|
|
636
|
+
* always serialize via BEGIN IMMEDIATE.
|
|
637
|
+
*/
|
|
638
|
+
app.get('/memory/sequence/:agentId/next', async (req, reply) => {
|
|
639
|
+
const { agentId } = req.params as { agentId: string };
|
|
640
|
+
const next = await store.allocateNextSequence(agentId);
|
|
641
|
+
return reply.send({ agentId, next });
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
app.get('/memory/:id', async (req, reply) => {
|
|
645
|
+
const { id } = req.params as { id: string };
|
|
646
|
+
const engram = await store.getEngram(id);
|
|
647
|
+
if (!engram) return reply.code(404).send({ error: 'Not found' });
|
|
648
|
+
|
|
649
|
+
const associations = await store.getAssociationsFor(id);
|
|
650
|
+
return reply.send({ engram, associations });
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
app.get('/agent/:id/stats', async (req, reply) => {
|
|
654
|
+
const { id } = req.params as { id: string };
|
|
655
|
+
const active = await store.getEngramsByAgent(id, 'active');
|
|
656
|
+
const staging = await store.getEngramsByAgent(id, 'staging');
|
|
657
|
+
const retracted = (await store.getEngramsByAgent(id, undefined, true)).filter(e => e.retracted);
|
|
658
|
+
const associations = await store.getAllAssociations(id);
|
|
659
|
+
|
|
660
|
+
return reply.send({
|
|
661
|
+
agentId: id,
|
|
662
|
+
engrams: {
|
|
663
|
+
active: active.length,
|
|
664
|
+
staging: staging.length,
|
|
665
|
+
retracted: retracted.length,
|
|
666
|
+
total: active.length + staging.length + retracted.length,
|
|
667
|
+
},
|
|
668
|
+
associations: associations.length,
|
|
669
|
+
avgConfidence: active.length > 0
|
|
670
|
+
? +(active.reduce((s, e) => s + e.confidence, 0) / active.length).toFixed(3)
|
|
671
|
+
: 0,
|
|
672
|
+
});
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
app.get('/agent/:id/metrics', async (req, reply) => {
|
|
676
|
+
const { id } = req.params as { id: string };
|
|
677
|
+
const windowHours = parseInt((req.query as any).window ?? '24', 10);
|
|
678
|
+
const metrics = await evalEngine.computeMetrics(id, windowHours);
|
|
679
|
+
return reply.send({ metrics });
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
app.post('/agent/register', async (req, reply) => {
|
|
683
|
+
const body = req.body as { name: string };
|
|
684
|
+
const id = crypto.randomUUID();
|
|
685
|
+
return reply.code(201).send({
|
|
686
|
+
id,
|
|
687
|
+
name: body.name,
|
|
688
|
+
config: DEFAULT_AGENT_CONFIG,
|
|
689
|
+
});
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
// ============================================================
|
|
693
|
+
// SYSTEM — Maintenance operations
|
|
694
|
+
// ============================================================
|
|
695
|
+
|
|
696
|
+
app.post('/system/evict', async (req, reply) => {
|
|
697
|
+
const body = req.body as { agentId: string };
|
|
698
|
+
const result = await evictionEngine.enforceCapacity(body.agentId, DEFAULT_AGENT_CONFIG);
|
|
699
|
+
return reply.send(result);
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
app.post('/system/decay', async (req, reply) => {
|
|
703
|
+
const body = req.body as { agentId: string; halfLifeDays?: number };
|
|
704
|
+
const decayed = await evictionEngine.decayEdges(body.agentId, body.halfLifeDays);
|
|
705
|
+
return reply.send({ edgesDecayed: decayed });
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
app.post('/system/consolidate', async (req, reply) => {
|
|
709
|
+
const body = req.body as { agentId: string };
|
|
710
|
+
const result = await consolidationEngine.consolidate(body.agentId);
|
|
711
|
+
return reply.send(result);
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
// ============================================================
|
|
715
|
+
// CHECKPOINTING — Conscious state preservation
|
|
716
|
+
// ============================================================
|
|
717
|
+
|
|
718
|
+
app.post('/memory/checkpoint', async (req, reply) => {
|
|
719
|
+
const body = req.body as {
|
|
720
|
+
agentId: string;
|
|
721
|
+
currentTask: string;
|
|
722
|
+
decisions?: string[];
|
|
723
|
+
activeFiles?: string[];
|
|
724
|
+
nextSteps?: string[];
|
|
725
|
+
relatedMemoryIds?: string[];
|
|
726
|
+
notes?: string;
|
|
727
|
+
episodeId?: string | null;
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
const state: ConsciousState = {
|
|
731
|
+
currentTask: body.currentTask,
|
|
732
|
+
decisions: body.decisions ?? [],
|
|
733
|
+
activeFiles: body.activeFiles ?? [],
|
|
734
|
+
nextSteps: body.nextSteps ?? [],
|
|
735
|
+
relatedMemoryIds: body.relatedMemoryIds ?? [],
|
|
736
|
+
notes: body.notes ?? '',
|
|
737
|
+
episodeId: body.episodeId ?? null,
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
store.saveCheckpoint(body.agentId, state);
|
|
741
|
+
return reply.send({ saved: true, agentId: body.agentId });
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
app.get('/memory/restore/:agentId', async (req, reply) => {
|
|
745
|
+
const { agentId } = req.params as { agentId: string };
|
|
746
|
+
const checkpoint = await store.getCheckpoint(agentId);
|
|
747
|
+
|
|
748
|
+
const now = Date.now();
|
|
749
|
+
const idleMs = checkpoint
|
|
750
|
+
? now - checkpoint.auto.lastActivityAt.getTime()
|
|
751
|
+
: 0;
|
|
752
|
+
|
|
753
|
+
// Get last written engram for context
|
|
754
|
+
let lastWrite: { id: string; concept: string; content: string } | null = null;
|
|
755
|
+
if (checkpoint?.auto.lastWriteId) {
|
|
756
|
+
const engram = await store.getEngram(checkpoint.auto.lastWriteId);
|
|
757
|
+
if (engram) {
|
|
758
|
+
lastWrite = { id: engram.id, concept: engram.concept, content: engram.content };
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Recall memories using last context (if available)
|
|
763
|
+
let recalledMemories: Array<{ id: string; concept: string; content: string; score: number }> = [];
|
|
764
|
+
const recallContext = checkpoint?.auto.lastRecallContext
|
|
765
|
+
?? checkpoint?.executionState?.currentTask
|
|
766
|
+
?? null;
|
|
767
|
+
|
|
768
|
+
if (recallContext) {
|
|
769
|
+
try {
|
|
770
|
+
const results = await activationEngine.activate({
|
|
771
|
+
agentId,
|
|
772
|
+
context: recallContext,
|
|
773
|
+
limit: 5,
|
|
774
|
+
minScore: 0.05,
|
|
775
|
+
useReranker: true,
|
|
776
|
+
useExpansion: true,
|
|
777
|
+
});
|
|
778
|
+
recalledMemories = results.map(r => ({
|
|
779
|
+
id: r.engram.id,
|
|
780
|
+
concept: r.engram.concept,
|
|
781
|
+
content: r.engram.content,
|
|
782
|
+
score: r.score,
|
|
783
|
+
}));
|
|
784
|
+
} catch { /* recall failure is non-fatal */ }
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Trigger mini-consolidation if idle >5min (async, fire-and-forget)
|
|
788
|
+
const MINI_CONSOLIDATION_IDLE_MS = 5 * 60_000;
|
|
789
|
+
let miniConsolidationTriggered = false;
|
|
790
|
+
if (idleMs > MINI_CONSOLIDATION_IDLE_MS) {
|
|
791
|
+
miniConsolidationTriggered = true;
|
|
792
|
+
consolidationScheduler.runMiniConsolidation(agentId).catch(() => {});
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
return reply.send({
|
|
796
|
+
executionState: checkpoint?.executionState ?? null,
|
|
797
|
+
checkpointAt: checkpoint?.checkpointAt ?? null,
|
|
798
|
+
recalledMemories,
|
|
799
|
+
lastWrite,
|
|
800
|
+
idleMs,
|
|
801
|
+
miniConsolidationTriggered,
|
|
802
|
+
});
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
// ============================================================
|
|
806
|
+
// TASK MANAGEMENT
|
|
807
|
+
// ============================================================
|
|
808
|
+
|
|
809
|
+
app.post('/task/create', async (req, reply) => {
|
|
810
|
+
const body = req.body as {
|
|
811
|
+
agentId: string;
|
|
812
|
+
concept: string;
|
|
813
|
+
content: string;
|
|
814
|
+
tags?: string[];
|
|
815
|
+
priority?: TaskPriority;
|
|
816
|
+
blockedBy?: string;
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
const engram = await store.createEngram({
|
|
820
|
+
agentId: body.agentId,
|
|
821
|
+
concept: body.concept,
|
|
822
|
+
content: body.content,
|
|
823
|
+
tags: [...(body.tags ?? []), 'task'],
|
|
824
|
+
salience: 0.9,
|
|
825
|
+
confidence: 0.8,
|
|
826
|
+
salienceFeatures: {
|
|
827
|
+
surprise: 0.5, decisionMade: true, causalDepth: 0.5,
|
|
828
|
+
resolutionEffort: 0.5, eventType: 'decision',
|
|
829
|
+
},
|
|
830
|
+
reasonCodes: ['task-created'],
|
|
831
|
+
taskStatus: body.blockedBy ? 'blocked' : 'open',
|
|
832
|
+
taskPriority: body.priority ?? 'medium',
|
|
833
|
+
blockedBy: body.blockedBy,
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
connectionEngine.enqueue(engram.id);
|
|
837
|
+
embed(`${body.concept} ${body.content}`).then(async vec => {
|
|
838
|
+
await store.updateEmbedding(engram.id, vec);
|
|
839
|
+
}).catch(() => {});
|
|
840
|
+
|
|
841
|
+
return reply.send(engram);
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
app.post('/task/update', async (req, reply) => {
|
|
845
|
+
const body = req.body as {
|
|
846
|
+
taskId: string;
|
|
847
|
+
status?: TaskStatus;
|
|
848
|
+
priority?: TaskPriority;
|
|
849
|
+
blockedBy?: string | null;
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
const engram = await store.getEngram(body.taskId);
|
|
853
|
+
if (!engram || !engram.taskStatus) {
|
|
854
|
+
return reply.code(404).send({ error: 'Task not found' });
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
if (body.blockedBy !== undefined) {
|
|
858
|
+
await store.updateBlockedBy(body.taskId, body.blockedBy);
|
|
859
|
+
}
|
|
860
|
+
if (body.status) {
|
|
861
|
+
await store.updateTaskStatus(body.taskId, body.status);
|
|
862
|
+
}
|
|
863
|
+
if (body.priority) {
|
|
864
|
+
await store.updateTaskPriority(body.taskId, body.priority);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
return reply.send(await store.getEngram(body.taskId));
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
app.get('/task/list/:agentId', async (req, reply) => {
|
|
871
|
+
const { agentId } = req.params as { agentId: string };
|
|
872
|
+
const { status, includeDone } = req.query as { status?: TaskStatus; includeDone?: string };
|
|
873
|
+
|
|
874
|
+
let tasks = await store.getTasks(agentId, status);
|
|
875
|
+
if (includeDone !== 'true' && !status) {
|
|
876
|
+
tasks = tasks.filter(t => t.taskStatus !== 'done');
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
return reply.send({ tasks, count: tasks.length });
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
app.get('/task/next/:agentId', async (req, reply) => {
|
|
883
|
+
const { agentId } = req.params as { agentId: string };
|
|
884
|
+
const next = await store.getNextTask(agentId);
|
|
885
|
+
return reply.send(next ? { task: next } : { task: null, message: 'No actionable tasks' });
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
// Time warp — shift all timestamps backward by N days (for testing)
|
|
889
|
+
app.post('/system/time-warp', async (req, reply) => {
|
|
890
|
+
const body = req.body as { agentId: string; days: number };
|
|
891
|
+
const ms = body.days * 24 * 60 * 60 * 1000;
|
|
892
|
+
const shifted = await store.timeWarp(body.agentId, ms);
|
|
893
|
+
return reply.send({ shifted, days: body.days });
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
// ─── Export ─────────────────────────────────────────────────────────────
|
|
897
|
+
|
|
898
|
+
app.get('/memory/export', async (req, reply) => {
|
|
899
|
+
const { agentId, all } = req.query as { agentId?: string; all?: string };
|
|
900
|
+
const includeAll = all === 'true';
|
|
901
|
+
// /memory/export uses raw SQL — SQLite-only. On PGlite, callers should use
|
|
902
|
+
// the awm CLI export/merge tools instead.
|
|
903
|
+
if (typeof (store as any).getDb !== 'function') {
|
|
904
|
+
return reply.code(501).send({ error: 'export endpoint requires the SQLite backend' });
|
|
905
|
+
}
|
|
906
|
+
const db = (store as any).getDb();
|
|
907
|
+
|
|
908
|
+
let engramSql = `SELECT id, agent_id, concept, content, confidence, salience, access_count,
|
|
909
|
+
last_accessed, created_at, salience_features, reason_codes, stage, ttl,
|
|
910
|
+
retracted, retracted_by, retracted_at, tags
|
|
911
|
+
FROM engrams`;
|
|
912
|
+
const conditions: string[] = [];
|
|
913
|
+
const params: string[] = [];
|
|
914
|
+
|
|
915
|
+
if (agentId) {
|
|
916
|
+
conditions.push('agent_id = ?');
|
|
917
|
+
params.push(agentId);
|
|
918
|
+
}
|
|
919
|
+
if (!includeAll) {
|
|
920
|
+
conditions.push('retracted = 0');
|
|
921
|
+
conditions.push("stage = 'active'");
|
|
922
|
+
}
|
|
923
|
+
if (conditions.length > 0) {
|
|
924
|
+
engramSql += ' WHERE ' + conditions.join(' AND ');
|
|
925
|
+
}
|
|
926
|
+
engramSql += ' ORDER BY created_at ASC';
|
|
927
|
+
|
|
928
|
+
const engrams = db.prepare(engramSql).all(...params) as { id: string }[];
|
|
929
|
+
|
|
930
|
+
const engramIds = new Set(engrams.map(e => e.id));
|
|
931
|
+
const allAssocs = db.prepare(
|
|
932
|
+
`SELECT id, from_engram_id, to_engram_id, weight, confidence, type, activation_count, created_at, last_activated
|
|
933
|
+
FROM associations`
|
|
934
|
+
).all() as { from_engram_id: string; to_engram_id: string }[];
|
|
935
|
+
const associations = allAssocs.filter(a => engramIds.has(a.from_engram_id) && engramIds.has(a.to_engram_id));
|
|
936
|
+
|
|
937
|
+
return reply.send({
|
|
938
|
+
exported_at: new Date().toISOString(),
|
|
939
|
+
agent_id: agentId ?? null,
|
|
940
|
+
include_all: includeAll,
|
|
941
|
+
engrams_count: engrams.length,
|
|
942
|
+
associations_count: associations.length,
|
|
943
|
+
engrams,
|
|
944
|
+
associations,
|
|
945
|
+
});
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
// ─── Health ─────────────────────────────────────────────────────────────
|
|
949
|
+
|
|
950
|
+
app.get('/health', async () => {
|
|
951
|
+
const coordEnabled = process.env.AWM_COORDINATION === 'true' || process.env.AWM_COORDINATION === '1';
|
|
952
|
+
const base: Record<string, unknown> = {
|
|
953
|
+
status: 'ok',
|
|
954
|
+
timestamp: new Date().toISOString(),
|
|
955
|
+
version: '0.8.5',
|
|
956
|
+
coordination: coordEnabled,
|
|
957
|
+
};
|
|
958
|
+
if (coordEnabled && typeof (deps.store as any).getDb === 'function') {
|
|
959
|
+
try {
|
|
960
|
+
const db = (deps.store as any).getDb();
|
|
961
|
+
const stats = db.prepare(`SELECT
|
|
962
|
+
(SELECT COUNT(*) FROM coord_agents WHERE status != 'dead') AS agents_alive,
|
|
963
|
+
(SELECT COUNT(*) FROM coord_assignments WHERE status = 'pending') AS pending_tasks,
|
|
964
|
+
(SELECT COUNT(*) FROM coord_locks) AS active_locks`).get() as { agents_alive: number; pending_tasks: number; active_locks: number };
|
|
965
|
+
Object.assign(base, stats);
|
|
966
|
+
} catch { /* tables may not exist yet */ }
|
|
967
|
+
}
|
|
968
|
+
return base;
|
|
969
|
+
});
|
|
970
|
+
}
|