agent-working-memory 0.7.16 → 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/claude-code.d.ts.map +1 -1
- package/dist/adapters/claude-code.js +2 -16
- package/dist/adapters/claude-code.js.map +1 -1
- package/dist/adapters/codex.d.ts.map +1 -1
- package/dist/adapters/codex.js +2 -11
- package/dist/adapters/codex.js.map +1 -1
- package/dist/adapters/common.d.ts +18 -0
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +254 -141
- package/dist/adapters/common.js.map +1 -1
- package/dist/adapters/cursor.d.ts.map +1 -1
- package/dist/adapters/cursor.js +2 -15
- package/dist/adapters/cursor.js.map +1 -1
- package/dist/adapters/http.d.ts.map +1 -1
- package/dist/adapters/http.js +28 -34
- package/dist/adapters/http.js.map +1 -1
- package/dist/api/routes.d.ts +1 -1
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +280 -113
- 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 +45 -1
- 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 +128 -0
- package/dist/core/write-pipeline.d.ts.map +1 -0
- package/dist/core/write-pipeline.js +405 -0
- package/dist/core/write-pipeline.js.map +1 -0
- 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 +110 -155
- 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 -234
- package/src/adapters/codex.ts +252 -262
- package/src/adapters/common.ts +411 -284
- package/src/adapters/cursor.ts +135 -150
- package/src/adapters/http.ts +93 -100
- 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 +367 -120
- package/src/cli/migrate.ts +307 -0
- package/src/cli.ts +37 -1
- 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 -0
- 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 +42 -28
- package/src/mcp.ts +105 -152
- 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
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
36
|
import type { FastifyInstance } from 'fastify';
|
|
37
|
-
import type { EngramStore } from '../storage/
|
|
37
|
+
import type { IEngramStore as EngramStore } from '../storage/store.js';
|
|
38
38
|
import type { ActivationEngine } from '../engine/activation.js';
|
|
39
39
|
import type { ConnectionEngine } from '../engine/connections.js';
|
|
40
40
|
import type { EvictionEngine } from '../engine/eviction.js';
|
|
@@ -44,6 +44,7 @@ import type { ConsolidationEngine } from '../engine/consolidation.js';
|
|
|
44
44
|
import type { ConsolidationScheduler } from '../engine/consolidation-scheduler.js';
|
|
45
45
|
import { evaluateSalience, computeNovelty } from '../core/salience.js';
|
|
46
46
|
import type { SalienceEventType } from '../core/salience.js';
|
|
47
|
+
import { performWrite } from '../core/write-pipeline.js';
|
|
47
48
|
import type { TaskStatus, TaskPriority } from '../types/engram.js';
|
|
48
49
|
import type { ConsciousState } from '../types/checkpoint.js';
|
|
49
50
|
import { DEFAULT_AGENT_CONFIG } from '../types/agent.js';
|
|
@@ -79,6 +80,34 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
79
80
|
causalDepth?: number;
|
|
80
81
|
resolutionEffort?: number;
|
|
81
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
|
+
}>;
|
|
82
111
|
// Agent-provided metadata (stored as searchable tags)
|
|
83
112
|
project?: string;
|
|
84
113
|
topic?: string;
|
|
@@ -94,24 +123,6 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
94
123
|
return reply.status(400).send({ error: 'agentId, concept, and content are required strings' });
|
|
95
124
|
}
|
|
96
125
|
|
|
97
|
-
const novelty = computeNovelty(store, body.agentId, body.concept, body.content);
|
|
98
|
-
|
|
99
|
-
const salience = evaluateSalience({
|
|
100
|
-
content: body.content,
|
|
101
|
-
eventType: body.eventType,
|
|
102
|
-
surprise: body.surprise,
|
|
103
|
-
decisionMade: body.decisionMade,
|
|
104
|
-
causalDepth: body.causalDepth,
|
|
105
|
-
resolutionEffort: body.resolutionEffort,
|
|
106
|
-
novelty,
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// v0.5.4: No longer discard — store with low confidence for ranking.
|
|
110
|
-
const isLowSalience = salience.disposition === 'discard';
|
|
111
|
-
const confidence = isLowSalience
|
|
112
|
-
? 0.25
|
|
113
|
-
: body.confidence ?? (salience.disposition === 'staging' ? 0.40 : 0.50);
|
|
114
|
-
|
|
115
126
|
// Assemble tags: user-provided + agent metadata
|
|
116
127
|
const userTags = body.tags ?? [];
|
|
117
128
|
const metaTags: string[] = [];
|
|
@@ -121,62 +132,94 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
121
132
|
if (body.confidenceLevel) metaTags.push(`conf=${body.confidenceLevel}`);
|
|
122
133
|
if (body.sessionId) metaTags.push(`sid=${body.sessionId}`);
|
|
123
134
|
if (body.intent) metaTags.push(`intent=${body.intent}`);
|
|
124
|
-
const allTags = isLowSalience
|
|
125
|
-
? [...userTags, ...metaTags, 'low-salience']
|
|
126
|
-
: [...userTags, ...metaTags];
|
|
127
135
|
|
|
128
|
-
|
|
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 }, {
|
|
129
154
|
agentId: body.agentId,
|
|
130
155
|
concept: body.concept,
|
|
131
156
|
content: body.content,
|
|
132
|
-
tags:
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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,
|
|
138
168
|
});
|
|
139
169
|
|
|
140
|
-
|
|
141
|
-
|
|
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
|
+
});
|
|
142
182
|
}
|
|
143
183
|
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
} catch { /* Temporal edge creation is non-fatal */ }
|
|
152
|
-
|
|
153
|
-
if (salience.disposition === 'active' || isLowSalience) {
|
|
154
|
-
connectionEngine.enqueue(engram.id);
|
|
155
|
-
|
|
156
|
-
// Auto-assign to episode (1-hour window per agent)
|
|
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) {
|
|
157
190
|
try {
|
|
158
|
-
|
|
159
|
-
if (
|
|
160
|
-
|
|
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);
|
|
161
194
|
}
|
|
162
|
-
|
|
163
|
-
|
|
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
|
+
}
|
|
164
207
|
}
|
|
165
208
|
|
|
166
|
-
|
|
167
|
-
embed(`${body.concept} ${body.content}`).then(vec => {
|
|
168
|
-
store.updateEmbedding(engram.id, vec);
|
|
169
|
-
}).catch(() => {}); // Embedding failure is non-fatal
|
|
170
|
-
|
|
171
|
-
// Auto-checkpoint: track write for consolidation scheduling
|
|
172
|
-
try { store.updateAutoCheckpointWrite(body.agentId, engram.id); } catch { /* non-fatal */ }
|
|
173
|
-
|
|
209
|
+
const isLowSalience = result.salience?.disposition === 'discard';
|
|
174
210
|
return reply.code(201).send({
|
|
175
211
|
stored: true,
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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,
|
|
180
223
|
});
|
|
181
224
|
});
|
|
182
225
|
|
|
@@ -210,7 +253,7 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
210
253
|
const memTags = [...(mem.tags ?? [])];
|
|
211
254
|
if (sid) memTags.push(`sid=${sid}`);
|
|
212
255
|
|
|
213
|
-
const engram = store.createEngram({
|
|
256
|
+
const engram = await store.createEngram({
|
|
214
257
|
agentId: body.agentId,
|
|
215
258
|
concept: mem.concept,
|
|
216
259
|
content: mem.content,
|
|
@@ -222,9 +265,9 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
222
265
|
|
|
223
266
|
// Handle supersession inline — archive superseded memory to remove from active pool
|
|
224
267
|
if (mem.supersedes) {
|
|
225
|
-
store.supersedeEngram(mem.supersedes, engram.id);
|
|
226
|
-
store.updateConfidence(mem.supersedes, 0.1);
|
|
227
|
-
store.updateStage(mem.supersedes, 'archived'); // Remove from active search pool
|
|
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
|
|
228
271
|
}
|
|
229
272
|
|
|
230
273
|
results.push({ id: engram.id, concept: mem.concept, disposition: 'active' });
|
|
@@ -236,12 +279,12 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
236
279
|
const vecs = await embedBatch(texts);
|
|
237
280
|
for (let i = 0; i < vecs.length; i++) {
|
|
238
281
|
if (results[i]) {
|
|
239
|
-
store.updateEmbedding(results[i].id, vecs[i]);
|
|
282
|
+
await store.updateEmbedding(results[i].id, vecs[i]);
|
|
240
283
|
}
|
|
241
284
|
}
|
|
242
285
|
} catch { /* Embedding failure is non-fatal */ }
|
|
243
286
|
|
|
244
|
-
try { store.updateAutoCheckpointWrite(body.agentId, results[results.length - 1]?.id ?? ''); } catch {}
|
|
287
|
+
try { await store.updateAutoCheckpointWrite(body.agentId, results[results.length - 1]?.id ?? ''); } catch {}
|
|
245
288
|
|
|
246
289
|
return reply.code(201).send({
|
|
247
290
|
stored: results.length,
|
|
@@ -259,8 +302,10 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
259
302
|
useReranker?: boolean;
|
|
260
303
|
useExpansion?: boolean;
|
|
261
304
|
abstentionThreshold?: number;
|
|
305
|
+
requireConfidence?: number;
|
|
262
306
|
workspace?: string;
|
|
263
307
|
bm25Only?: boolean;
|
|
308
|
+
granularity?: 'full' | 'compact' | 'auto';
|
|
264
309
|
};
|
|
265
310
|
|
|
266
311
|
const results = await activationEngine.activate({
|
|
@@ -272,17 +317,23 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
272
317
|
useReranker: body.useReranker,
|
|
273
318
|
useExpansion: body.useExpansion,
|
|
274
319
|
abstentionThreshold: body.abstentionThreshold,
|
|
320
|
+
requireConfidence: body.requireConfidence,
|
|
275
321
|
workspace: body.workspace,
|
|
276
322
|
bm25Only: body.bm25Only,
|
|
323
|
+
granularity: body.granularity,
|
|
277
324
|
});
|
|
278
325
|
|
|
279
326
|
// Auto-checkpoint: track recall for consolidation scheduling
|
|
280
327
|
try {
|
|
281
328
|
const ids = results.map(r => r.engram.id);
|
|
282
|
-
store.updateAutoCheckpointRecall(body.agentId, body.context, ids);
|
|
329
|
+
await store.updateAutoCheckpointRecall(body.agentId, body.context, ids);
|
|
283
330
|
} catch { /* non-fatal */ }
|
|
284
331
|
|
|
285
|
-
|
|
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 });
|
|
286
337
|
});
|
|
287
338
|
|
|
288
339
|
app.post('/memory/feedback', async (req, reply) => {
|
|
@@ -293,7 +344,7 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
293
344
|
context?: string;
|
|
294
345
|
};
|
|
295
346
|
|
|
296
|
-
store.logRetrievalFeedback(
|
|
347
|
+
await store.logRetrievalFeedback(
|
|
297
348
|
body.activationEventId ?? null,
|
|
298
349
|
body.engramId,
|
|
299
350
|
body.useful,
|
|
@@ -301,18 +352,18 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
301
352
|
);
|
|
302
353
|
|
|
303
354
|
// Update engram confidence based on feedback
|
|
304
|
-
const engram = store.getEngram(body.engramId);
|
|
355
|
+
const engram = await store.getEngram(body.engramId);
|
|
305
356
|
if (engram) {
|
|
306
357
|
const config = DEFAULT_AGENT_CONFIG;
|
|
307
358
|
const delta = body.useful
|
|
308
359
|
? config.feedbackPositiveBoost
|
|
309
360
|
: -config.feedbackNegativePenalty;
|
|
310
|
-
store.updateConfidence(engram.id, engram.confidence + delta);
|
|
361
|
+
await store.updateConfidence(engram.id, engram.confidence + delta);
|
|
311
362
|
}
|
|
312
363
|
|
|
313
364
|
// Touch activity for consolidation scheduling
|
|
314
365
|
if (engram) {
|
|
315
|
-
try { store.touchActivity(engram.agentId); } catch { /* non-fatal */ }
|
|
366
|
+
try { await store.touchActivity(engram.agentId); } catch { /* non-fatal */ }
|
|
316
367
|
}
|
|
317
368
|
|
|
318
369
|
return reply.send({ recorded: true });
|
|
@@ -326,7 +377,7 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
326
377
|
counterContent?: string;
|
|
327
378
|
};
|
|
328
379
|
|
|
329
|
-
const result = retractionEngine.retract({
|
|
380
|
+
const result = await retractionEngine.retract({
|
|
330
381
|
agentId: body.agentId,
|
|
331
382
|
targetEngramId: body.targetEngramId,
|
|
332
383
|
reason: body.reason,
|
|
@@ -334,38 +385,105 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
334
385
|
});
|
|
335
386
|
|
|
336
387
|
// Touch activity for consolidation scheduling
|
|
337
|
-
try { store.touchActivity(body.agentId); } catch { /* non-fatal */ }
|
|
388
|
+
try { await store.touchActivity(body.agentId); } catch { /* non-fatal */ }
|
|
338
389
|
|
|
339
390
|
return reply.send(result);
|
|
340
391
|
});
|
|
341
392
|
|
|
342
393
|
app.post('/memory/supersede', async (req, reply) => {
|
|
343
394
|
const body = req.body as {
|
|
344
|
-
|
|
345
|
-
|
|
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
|
|
346
411
|
reason?: string;
|
|
347
412
|
};
|
|
348
413
|
|
|
349
|
-
const
|
|
350
|
-
const
|
|
351
|
-
if (!oldEngram) return reply.code(404).send({ error: `Old engram ${body.oldEngramId} not found` });
|
|
352
|
-
if (!newEngram) return reply.code(404).send({ error: `New engram ${body.newEngramId} not found` });
|
|
414
|
+
const isFormA = !!(body.oldEngramId && body.newEngramId);
|
|
415
|
+
const isFormB = !!(body.matchConcept && body.newEngram && body.agentId);
|
|
353
416
|
|
|
354
|
-
|
|
355
|
-
|
|
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
|
+
}
|
|
356
448
|
|
|
357
|
-
//
|
|
358
|
-
|
|
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
|
+
});
|
|
359
471
|
|
|
360
|
-
|
|
361
|
-
|
|
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 }>);
|
|
362
479
|
|
|
363
|
-
try { store.touchActivity(
|
|
480
|
+
try { await store.touchActivity(body.agentId!); } catch { /* non-fatal */ }
|
|
364
481
|
|
|
365
|
-
return reply.send({
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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',
|
|
369
487
|
});
|
|
370
488
|
});
|
|
371
489
|
|
|
@@ -378,42 +496,166 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
378
496
|
agentId: string;
|
|
379
497
|
text?: string;
|
|
380
498
|
concept?: string;
|
|
381
|
-
tags?: 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)
|
|
382
503
|
stage?: string;
|
|
383
504
|
retracted?: boolean;
|
|
384
505
|
limit?: number;
|
|
385
506
|
offset?: number;
|
|
507
|
+
sortBy?: 'createdAt' | 'sequence' | 'salience' | 'confidence' | 'lastAccessed';
|
|
508
|
+
sortOrder?: 'asc' | 'desc';
|
|
386
509
|
};
|
|
387
510
|
|
|
388
|
-
const results = store.search({
|
|
511
|
+
const results = await store.search({
|
|
389
512
|
agentId: body.agentId,
|
|
390
513
|
text: body.text,
|
|
391
514
|
concept: body.concept,
|
|
392
515
|
tags: body.tags,
|
|
516
|
+
tagsAll: body.tagsAll,
|
|
517
|
+
tagsAny: body.tagsAny,
|
|
518
|
+
tagsNone: body.tagsNone,
|
|
393
519
|
stage: body.stage as any,
|
|
394
520
|
retracted: body.retracted,
|
|
395
521
|
limit: body.limit,
|
|
396
522
|
offset: body.offset,
|
|
523
|
+
sortBy: body.sortBy,
|
|
524
|
+
sortOrder: body.sortOrder,
|
|
397
525
|
});
|
|
398
526
|
|
|
399
527
|
return reply.send({ results, count: results.length });
|
|
400
528
|
});
|
|
401
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
|
+
|
|
402
644
|
app.get('/memory/:id', async (req, reply) => {
|
|
403
645
|
const { id } = req.params as { id: string };
|
|
404
|
-
const engram = store.getEngram(id);
|
|
646
|
+
const engram = await store.getEngram(id);
|
|
405
647
|
if (!engram) return reply.code(404).send({ error: 'Not found' });
|
|
406
648
|
|
|
407
|
-
const associations = store.getAssociationsFor(id);
|
|
649
|
+
const associations = await store.getAssociationsFor(id);
|
|
408
650
|
return reply.send({ engram, associations });
|
|
409
651
|
});
|
|
410
652
|
|
|
411
653
|
app.get('/agent/:id/stats', async (req, reply) => {
|
|
412
654
|
const { id } = req.params as { id: string };
|
|
413
|
-
const active = store.getEngramsByAgent(id, 'active');
|
|
414
|
-
const staging = store.getEngramsByAgent(id, 'staging');
|
|
415
|
-
const retracted = store.getEngramsByAgent(id, undefined, true).filter(e => e.retracted);
|
|
416
|
-
const associations = store.getAllAssociations(id);
|
|
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);
|
|
417
659
|
|
|
418
660
|
return reply.send({
|
|
419
661
|
agentId: id,
|
|
@@ -433,7 +675,7 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
433
675
|
app.get('/agent/:id/metrics', async (req, reply) => {
|
|
434
676
|
const { id } = req.params as { id: string };
|
|
435
677
|
const windowHours = parseInt((req.query as any).window ?? '24', 10);
|
|
436
|
-
const metrics = evalEngine.computeMetrics(id, windowHours);
|
|
678
|
+
const metrics = await evalEngine.computeMetrics(id, windowHours);
|
|
437
679
|
return reply.send({ metrics });
|
|
438
680
|
});
|
|
439
681
|
|
|
@@ -453,13 +695,13 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
453
695
|
|
|
454
696
|
app.post('/system/evict', async (req, reply) => {
|
|
455
697
|
const body = req.body as { agentId: string };
|
|
456
|
-
const result = evictionEngine.enforceCapacity(body.agentId, DEFAULT_AGENT_CONFIG);
|
|
698
|
+
const result = await evictionEngine.enforceCapacity(body.agentId, DEFAULT_AGENT_CONFIG);
|
|
457
699
|
return reply.send(result);
|
|
458
700
|
});
|
|
459
701
|
|
|
460
702
|
app.post('/system/decay', async (req, reply) => {
|
|
461
703
|
const body = req.body as { agentId: string; halfLifeDays?: number };
|
|
462
|
-
const decayed = evictionEngine.decayEdges(body.agentId, body.halfLifeDays);
|
|
704
|
+
const decayed = await evictionEngine.decayEdges(body.agentId, body.halfLifeDays);
|
|
463
705
|
return reply.send({ edgesDecayed: decayed });
|
|
464
706
|
});
|
|
465
707
|
|
|
@@ -501,7 +743,7 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
501
743
|
|
|
502
744
|
app.get('/memory/restore/:agentId', async (req, reply) => {
|
|
503
745
|
const { agentId } = req.params as { agentId: string };
|
|
504
|
-
const checkpoint = store.getCheckpoint(agentId);
|
|
746
|
+
const checkpoint = await store.getCheckpoint(agentId);
|
|
505
747
|
|
|
506
748
|
const now = Date.now();
|
|
507
749
|
const idleMs = checkpoint
|
|
@@ -511,7 +753,7 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
511
753
|
// Get last written engram for context
|
|
512
754
|
let lastWrite: { id: string; concept: string; content: string } | null = null;
|
|
513
755
|
if (checkpoint?.auto.lastWriteId) {
|
|
514
|
-
const engram = store.getEngram(checkpoint.auto.lastWriteId);
|
|
756
|
+
const engram = await store.getEngram(checkpoint.auto.lastWriteId);
|
|
515
757
|
if (engram) {
|
|
516
758
|
lastWrite = { id: engram.id, concept: engram.concept, content: engram.content };
|
|
517
759
|
}
|
|
@@ -574,7 +816,7 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
574
816
|
blockedBy?: string;
|
|
575
817
|
};
|
|
576
818
|
|
|
577
|
-
const engram = store.createEngram({
|
|
819
|
+
const engram = await store.createEngram({
|
|
578
820
|
agentId: body.agentId,
|
|
579
821
|
concept: body.concept,
|
|
580
822
|
content: body.content,
|
|
@@ -592,8 +834,8 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
592
834
|
});
|
|
593
835
|
|
|
594
836
|
connectionEngine.enqueue(engram.id);
|
|
595
|
-
embed(`${body.concept} ${body.content}`).then(vec => {
|
|
596
|
-
store.updateEmbedding(engram.id, vec);
|
|
837
|
+
embed(`${body.concept} ${body.content}`).then(async vec => {
|
|
838
|
+
await store.updateEmbedding(engram.id, vec);
|
|
597
839
|
}).catch(() => {});
|
|
598
840
|
|
|
599
841
|
return reply.send(engram);
|
|
@@ -607,29 +849,29 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
607
849
|
blockedBy?: string | null;
|
|
608
850
|
};
|
|
609
851
|
|
|
610
|
-
const engram = store.getEngram(body.taskId);
|
|
852
|
+
const engram = await store.getEngram(body.taskId);
|
|
611
853
|
if (!engram || !engram.taskStatus) {
|
|
612
854
|
return reply.code(404).send({ error: 'Task not found' });
|
|
613
855
|
}
|
|
614
856
|
|
|
615
857
|
if (body.blockedBy !== undefined) {
|
|
616
|
-
store.updateBlockedBy(body.taskId, body.blockedBy);
|
|
858
|
+
await store.updateBlockedBy(body.taskId, body.blockedBy);
|
|
617
859
|
}
|
|
618
860
|
if (body.status) {
|
|
619
|
-
store.updateTaskStatus(body.taskId, body.status);
|
|
861
|
+
await store.updateTaskStatus(body.taskId, body.status);
|
|
620
862
|
}
|
|
621
863
|
if (body.priority) {
|
|
622
|
-
store.updateTaskPriority(body.taskId, body.priority);
|
|
864
|
+
await store.updateTaskPriority(body.taskId, body.priority);
|
|
623
865
|
}
|
|
624
866
|
|
|
625
|
-
return reply.send(store.getEngram(body.taskId));
|
|
867
|
+
return reply.send(await store.getEngram(body.taskId));
|
|
626
868
|
});
|
|
627
869
|
|
|
628
870
|
app.get('/task/list/:agentId', async (req, reply) => {
|
|
629
871
|
const { agentId } = req.params as { agentId: string };
|
|
630
872
|
const { status, includeDone } = req.query as { status?: TaskStatus; includeDone?: string };
|
|
631
873
|
|
|
632
|
-
let tasks = store.getTasks(agentId, status);
|
|
874
|
+
let tasks = await store.getTasks(agentId, status);
|
|
633
875
|
if (includeDone !== 'true' && !status) {
|
|
634
876
|
tasks = tasks.filter(t => t.taskStatus !== 'done');
|
|
635
877
|
}
|
|
@@ -639,7 +881,7 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
639
881
|
|
|
640
882
|
app.get('/task/next/:agentId', async (req, reply) => {
|
|
641
883
|
const { agentId } = req.params as { agentId: string };
|
|
642
|
-
const next = store.getNextTask(agentId);
|
|
884
|
+
const next = await store.getNextTask(agentId);
|
|
643
885
|
return reply.send(next ? { task: next } : { task: null, message: 'No actionable tasks' });
|
|
644
886
|
});
|
|
645
887
|
|
|
@@ -647,7 +889,7 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
647
889
|
app.post('/system/time-warp', async (req, reply) => {
|
|
648
890
|
const body = req.body as { agentId: string; days: number };
|
|
649
891
|
const ms = body.days * 24 * 60 * 60 * 1000;
|
|
650
|
-
const shifted = store.timeWarp(body.agentId, ms);
|
|
892
|
+
const shifted = await store.timeWarp(body.agentId, ms);
|
|
651
893
|
return reply.send({ shifted, days: body.days });
|
|
652
894
|
});
|
|
653
895
|
|
|
@@ -656,7 +898,12 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
656
898
|
app.get('/memory/export', async (req, reply) => {
|
|
657
899
|
const { agentId, all } = req.query as { agentId?: string; all?: string };
|
|
658
900
|
const includeAll = all === 'true';
|
|
659
|
-
|
|
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();
|
|
660
907
|
|
|
661
908
|
let engramSql = `SELECT id, agent_id, concept, content, confidence, salience, access_count,
|
|
662
909
|
last_accessed, created_at, salience_features, reason_codes, stage, ttl,
|
|
@@ -705,12 +952,12 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
705
952
|
const base: Record<string, unknown> = {
|
|
706
953
|
status: 'ok',
|
|
707
954
|
timestamp: new Date().toISOString(),
|
|
708
|
-
version: '0.
|
|
955
|
+
version: '0.8.5',
|
|
709
956
|
coordination: coordEnabled,
|
|
710
957
|
};
|
|
711
|
-
if (coordEnabled) {
|
|
958
|
+
if (coordEnabled && typeof (deps.store as any).getDb === 'function') {
|
|
712
959
|
try {
|
|
713
|
-
const db = deps.store.getDb();
|
|
960
|
+
const db = (deps.store as any).getDb();
|
|
714
961
|
const stats = db.prepare(`SELECT
|
|
715
962
|
(SELECT COUNT(*) FROM coord_agents WHERE status != 'dead') AS agents_alive,
|
|
716
963
|
(SELECT COUNT(*) FROM coord_assignments WHERE status = 'pending') AS pending_tasks,
|