agent-working-memory 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -5
- package/dist/adapters/common.d.ts +34 -0
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +528 -446
- package/dist/adapters/common.js.map +1 -1
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +19 -0
- package/dist/api/routes.js.map +1 -1
- package/dist/cli/migrate.d.ts.map +1 -1
- package/dist/cli/migrate.js +31 -29
- package/dist/cli/migrate.js.map +1 -1
- package/dist/core/asked-value.d.ts +37 -0
- package/dist/core/asked-value.d.ts.map +1 -0
- package/dist/core/asked-value.js +77 -0
- package/dist/core/asked-value.js.map +1 -0
- package/dist/core/conflicts.d.ts +89 -0
- package/dist/core/conflicts.d.ts.map +1 -0
- package/dist/core/conflicts.js +267 -0
- package/dist/core/conflicts.js.map +1 -0
- package/dist/core/embeddings.d.ts +14 -2
- package/dist/core/embeddings.d.ts.map +1 -1
- package/dist/core/embeddings.js +32 -10
- package/dist/core/embeddings.js.map +1 -1
- package/dist/core/format-recall.d.ts +21 -1
- package/dist/core/format-recall.d.ts.map +1 -1
- package/dist/core/format-recall.js +60 -5
- package/dist/core/format-recall.js.map +1 -1
- package/dist/core/ml-worker.d.ts +24 -1
- package/dist/core/ml-worker.d.ts.map +1 -1
- package/dist/core/ml-worker.js +165 -4
- package/dist/core/ml-worker.js.map +1 -1
- package/dist/core/model-daemon-client.d.ts +77 -0
- package/dist/core/model-daemon-client.d.ts.map +1 -0
- package/dist/core/model-daemon-client.js +383 -0
- package/dist/core/model-daemon-client.js.map +1 -0
- package/dist/core/model-daemon-server.d.ts +71 -0
- package/dist/core/model-daemon-server.d.ts.map +1 -0
- package/dist/core/model-daemon-server.js +363 -0
- package/dist/core/model-daemon-server.js.map +1 -0
- package/dist/core/model-daemon.d.ts +137 -0
- package/dist/core/model-daemon.d.ts.map +1 -0
- package/dist/core/model-daemon.js +231 -0
- package/dist/core/model-daemon.js.map +1 -0
- package/dist/core/query-expander.d.ts +5 -0
- package/dist/core/query-expander.d.ts.map +1 -1
- package/dist/core/query-expander.js +3 -2
- package/dist/core/query-expander.js.map +1 -1
- package/dist/core/recall-config.d.ts +1 -1
- package/dist/core/recall-config.d.ts.map +1 -1
- package/dist/core/recall-config.js +5 -0
- package/dist/core/recall-config.js.map +1 -1
- package/dist/core/rerank-window.d.ts +7 -2
- package/dist/core/rerank-window.d.ts.map +1 -1
- package/dist/core/rerank-window.js +9 -4
- package/dist/core/rerank-window.js.map +1 -1
- package/dist/core/rerank2.d.ts +52 -2
- package/dist/core/rerank2.d.ts.map +1 -1
- package/dist/core/rerank2.js +60 -3
- package/dist/core/rerank2.js.map +1 -1
- package/dist/core/reranker.d.ts +19 -0
- package/dist/core/reranker.d.ts.map +1 -1
- package/dist/core/reranker.js +90 -13
- package/dist/core/reranker.js.map +1 -1
- package/dist/core/trust.d.ts +32 -0
- package/dist/core/trust.d.ts.map +1 -0
- package/dist/core/trust.js +66 -0
- package/dist/core/trust.js.map +1 -0
- package/dist/core/validity.d.ts +21 -0
- package/dist/core/validity.d.ts.map +1 -0
- package/dist/core/validity.js +41 -0
- package/dist/core/validity.js.map +1 -0
- package/dist/core/whoami.d.ts +3 -0
- package/dist/core/whoami.d.ts.map +1 -1
- package/dist/core/whoami.js +14 -0
- package/dist/core/whoami.js.map +1 -1
- package/dist/core/write-pipeline.d.ts +48 -0
- package/dist/core/write-pipeline.d.ts.map +1 -1
- package/dist/core/write-pipeline.js +80 -3
- package/dist/core/write-pipeline.js.map +1 -1
- package/dist/engine/activation.d.ts.map +1 -1
- package/dist/engine/activation.js +27 -4
- package/dist/engine/activation.js.map +1 -1
- package/dist/engine/consolidation.d.ts.map +1 -1
- package/dist/engine/consolidation.js +53 -4
- package/dist/engine/consolidation.js.map +1 -1
- package/dist/hooks/prime-query.d.ts +57 -0
- package/dist/hooks/prime-query.d.ts.map +1 -0
- package/dist/hooks/prime-query.js +388 -0
- package/dist/hooks/prime-query.js.map +1 -0
- package/dist/hooks/prime.d.ts +8 -0
- package/dist/hooks/prime.d.ts.map +1 -1
- package/dist/hooks/prime.js +21 -2
- package/dist/hooks/prime.js.map +1 -1
- package/dist/hooks/sidecar.d.ts +18 -0
- package/dist/hooks/sidecar.d.ts.map +1 -1
- package/dist/hooks/sidecar.js +58 -2
- package/dist/hooks/sidecar.js.map +1 -1
- package/dist/index.js +15 -3
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +157 -104
- package/dist/mcp.js.map +1 -1
- package/dist/model-daemon.d.ts +8 -0
- package/dist/model-daemon.d.ts.map +1 -0
- package/dist/model-daemon.js +45 -0
- package/dist/model-daemon.js.map +1 -0
- package/dist/storage/factory.d.ts.map +1 -1
- package/dist/storage/factory.js +5 -0
- package/dist/storage/factory.js.map +1 -1
- package/dist/storage/pglite-schema.d.ts.map +1 -1
- package/dist/storage/pglite-schema.js +176 -170
- package/dist/storage/pglite-schema.js.map +1 -1
- package/dist/storage/pglite.d.ts +5 -0
- package/dist/storage/pglite.d.ts.map +1 -1
- package/dist/storage/pglite.js +165 -153
- package/dist/storage/pglite.js.map +1 -1
- package/dist/storage/postgres.d.ts +5 -0
- package/dist/storage/postgres.d.ts.map +1 -1
- package/dist/storage/postgres.js +165 -153
- package/dist/storage/postgres.js.map +1 -1
- package/dist/storage/sqlite.d.ts +22 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +415 -363
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/types/engram.d.ts +33 -0
- package/dist/types/engram.d.ts.map +1 -1
- package/dist/types/engram.js.map +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/README.md +1 -1
- package/plugin/bin/awm-mcp-launcher.cjs +2 -2
- package/plugin/skills/awm-memory/SKILL.md +11 -2
- package/src/adapters/common.ts +881 -802
- package/src/api/routes.ts +20 -0
- package/src/cli/migrate.ts +309 -307
- package/src/core/asked-value.ts +74 -0
- package/src/core/conflicts.ts +280 -0
- package/src/core/embeddings.ts +35 -10
- package/src/core/format-recall.ts +83 -25
- package/src/core/ml-worker.ts +458 -281
- package/src/core/model-daemon-client.ts +374 -0
- package/src/core/model-daemon-server.ts +344 -0
- package/src/core/model-daemon.ts +243 -0
- package/src/core/query-expander.ts +127 -126
- package/src/core/recall-config.ts +5 -0
- package/src/core/rerank-window.ts +164 -159
- package/src/core/rerank2.ts +143 -82
- package/src/core/reranker.ts +92 -12
- package/src/core/trust.ts +65 -0
- package/src/core/validity.ts +47 -0
- package/src/core/whoami.ts +136 -120
- package/src/core/write-pipeline.ts +119 -3
- package/src/engine/activation.ts +27 -4
- package/src/engine/consolidation.ts +950 -899
- package/src/hooks/prime-query.ts +375 -0
- package/src/hooks/prime.ts +25 -2
- package/src/hooks/sidecar.ts +57 -3
- package/src/index.ts +258 -248
- package/src/mcp.ts +1529 -1475
- package/src/model-daemon.ts +48 -0
- package/src/storage/factory.ts +25 -19
- package/src/storage/pglite-schema.ts +199 -193
- package/src/storage/pglite.ts +1513 -1498
- package/src/storage/postgres.ts +1613 -1598
- package/src/storage/sqlite.ts +2069 -2019
- package/src/types/engram.ts +30 -0
package/dist/storage/sqlite.js
CHANGED
|
@@ -37,6 +37,21 @@ export class EngramStore {
|
|
|
37
37
|
this.db.pragma('busy_timeout = 5000');
|
|
38
38
|
this.db.pragma('synchronous = NORMAL');
|
|
39
39
|
this.db.pragma('wal_autocheckpoint = 1000');
|
|
40
|
+
// Page cache and mmap (0.19). Until now the store ran on SQLite's 2 MB default
|
|
41
|
+
// page cache against a ~270 MB file, so the BM25 and association-stats
|
|
42
|
+
// phases of a recall paid page faults on every query: 42 ms / 14 ms at p50
|
|
43
|
+
// but 156 ms / 167 ms at p95 in the 2026-10-19 profile. 64 MB of cache
|
|
44
|
+
// holds the hot FTS and association pages; mmap lets reads come straight
|
|
45
|
+
// from the OS file cache without a copy. Both are per-connection settings
|
|
46
|
+
// with no effect on results; mmap is safe with WAL and multiple processes.
|
|
47
|
+
// AWM_SQLITE_CACHE_MB / AWM_SQLITE_MMAP_MB override (0 = SQLite default).
|
|
48
|
+
const cacheMb = Number(process.env.AWM_SQLITE_CACHE_MB ?? 64);
|
|
49
|
+
const mmapMb = Number(process.env.AWM_SQLITE_MMAP_MB ?? 256);
|
|
50
|
+
if (Number.isFinite(cacheMb) && cacheMb > 0)
|
|
51
|
+
this.db.pragma(`cache_size = ${-Math.round(cacheMb * 1024)}`);
|
|
52
|
+
if (Number.isFinite(mmapMb) && mmapMb > 0)
|
|
53
|
+
this.db.pragma(`mmap_size = ${Math.round(mmapMb * 1024 * 1024)}`);
|
|
54
|
+
this.db.pragma('temp_store = MEMORY');
|
|
40
55
|
this.init();
|
|
41
56
|
this.startWalCheckpointTimer();
|
|
42
57
|
}
|
|
@@ -154,123 +169,123 @@ export class EngramStore {
|
|
|
154
169
|
}
|
|
155
170
|
}
|
|
156
171
|
init() {
|
|
157
|
-
this.db.exec(`
|
|
158
|
-
CREATE TABLE IF NOT EXISTS engrams (
|
|
159
|
-
id TEXT PRIMARY KEY,
|
|
160
|
-
agent_id TEXT NOT NULL,
|
|
161
|
-
concept TEXT NOT NULL,
|
|
162
|
-
content TEXT NOT NULL,
|
|
163
|
-
embedding BLOB,
|
|
164
|
-
confidence REAL NOT NULL DEFAULT 0.5,
|
|
165
|
-
salience REAL NOT NULL DEFAULT 0.5,
|
|
166
|
-
access_count INTEGER NOT NULL DEFAULT 0,
|
|
167
|
-
last_accessed TEXT NOT NULL,
|
|
168
|
-
created_at TEXT NOT NULL,
|
|
169
|
-
salience_features TEXT NOT NULL DEFAULT '{}',
|
|
170
|
-
reason_codes TEXT NOT NULL DEFAULT '[]',
|
|
171
|
-
stage TEXT NOT NULL DEFAULT 'active',
|
|
172
|
-
ttl INTEGER,
|
|
173
|
-
retracted INTEGER NOT NULL DEFAULT 0,
|
|
174
|
-
retracted_by TEXT,
|
|
175
|
-
retracted_at TEXT,
|
|
176
|
-
tags TEXT NOT NULL DEFAULT '[]',
|
|
177
|
-
memory_type TEXT NOT NULL DEFAULT 'unclassified'
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
CREATE INDEX IF NOT EXISTS idx_engrams_agent ON engrams(agent_id);
|
|
181
|
-
CREATE INDEX IF NOT EXISTS idx_engrams_stage ON engrams(agent_id, stage);
|
|
182
|
-
CREATE INDEX IF NOT EXISTS idx_engrams_concept ON engrams(concept);
|
|
183
|
-
CREATE INDEX IF NOT EXISTS idx_engrams_retracted ON engrams(agent_id, retracted);
|
|
184
|
-
-- Serves getTopAccessedTags: the recall path's pronoun branch needs the ten
|
|
185
|
-
-- most-accessed engrams, and without this it filters then sorts the whole
|
|
186
|
-
-- agent partition on every query containing "it", "this" or "that".
|
|
187
|
-
CREATE INDEX IF NOT EXISTS idx_engrams_access ON engrams(agent_id, stage, access_count DESC);
|
|
188
|
-
|
|
189
|
-
CREATE TABLE IF NOT EXISTS associations (
|
|
190
|
-
id TEXT PRIMARY KEY,
|
|
191
|
-
from_engram_id TEXT NOT NULL REFERENCES engrams(id) ON DELETE CASCADE,
|
|
192
|
-
to_engram_id TEXT NOT NULL REFERENCES engrams(id) ON DELETE CASCADE,
|
|
193
|
-
weight REAL NOT NULL DEFAULT 0.1,
|
|
194
|
-
confidence REAL NOT NULL DEFAULT 0.5,
|
|
195
|
-
type TEXT NOT NULL DEFAULT 'hebbian',
|
|
196
|
-
activation_count INTEGER NOT NULL DEFAULT 0,
|
|
197
|
-
created_at TEXT NOT NULL,
|
|
198
|
-
last_activated TEXT NOT NULL
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
CREATE INDEX IF NOT EXISTS idx_assoc_from ON associations(from_engram_id);
|
|
202
|
-
CREATE INDEX IF NOT EXISTS idx_assoc_to ON associations(to_engram_id);
|
|
203
|
-
CREATE UNIQUE INDEX IF NOT EXISTS idx_assoc_pair ON associations(from_engram_id, to_engram_id);
|
|
204
|
-
|
|
205
|
-
CREATE TABLE IF NOT EXISTS agents (
|
|
206
|
-
id TEXT PRIMARY KEY,
|
|
207
|
-
name TEXT NOT NULL,
|
|
208
|
-
created_at TEXT NOT NULL,
|
|
209
|
-
config TEXT NOT NULL DEFAULT '{}'
|
|
210
|
-
);
|
|
211
|
-
|
|
212
|
-
-- FTS5 for full-text search (BM25 ranking built in)
|
|
213
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS engrams_fts USING fts5(
|
|
214
|
-
concept, content, tags,
|
|
215
|
-
content=engrams,
|
|
216
|
-
content_rowid=rowid
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
-- Triggers to keep FTS in sync
|
|
220
|
-
CREATE TRIGGER IF NOT EXISTS engrams_ai AFTER INSERT ON engrams BEGIN
|
|
221
|
-
INSERT INTO engrams_fts(rowid, concept, content, tags) VALUES (new.rowid, new.concept, new.content, new.tags);
|
|
222
|
-
END;
|
|
223
|
-
CREATE TRIGGER IF NOT EXISTS engrams_ad AFTER DELETE ON engrams BEGIN
|
|
224
|
-
INSERT INTO engrams_fts(engrams_fts, rowid, concept, content, tags) VALUES('delete', old.rowid, old.concept, old.content, old.tags);
|
|
225
|
-
END;
|
|
226
|
-
CREATE TRIGGER IF NOT EXISTS engrams_au AFTER UPDATE ON engrams BEGIN
|
|
227
|
-
INSERT INTO engrams_fts(engrams_fts, rowid, concept, content, tags) VALUES('delete', old.rowid, old.concept, old.content, old.tags);
|
|
228
|
-
INSERT INTO engrams_fts(rowid, concept, content, tags) VALUES (new.rowid, new.concept, new.content, new.tags);
|
|
229
|
-
END;
|
|
230
|
-
|
|
231
|
-
-- Eval event logs
|
|
232
|
-
CREATE TABLE IF NOT EXISTS activation_events (
|
|
233
|
-
id TEXT PRIMARY KEY,
|
|
234
|
-
agent_id TEXT NOT NULL,
|
|
235
|
-
timestamp TEXT NOT NULL,
|
|
236
|
-
context TEXT NOT NULL,
|
|
237
|
-
results_returned INTEGER NOT NULL,
|
|
238
|
-
top_score REAL,
|
|
239
|
-
latency_ms REAL NOT NULL,
|
|
240
|
-
engram_ids TEXT NOT NULL DEFAULT '[]'
|
|
241
|
-
);
|
|
242
|
-
|
|
243
|
-
CREATE TABLE IF NOT EXISTS staging_events (
|
|
244
|
-
engram_id TEXT NOT NULL,
|
|
245
|
-
agent_id TEXT NOT NULL,
|
|
246
|
-
action TEXT NOT NULL,
|
|
247
|
-
resonance_score REAL,
|
|
248
|
-
timestamp TEXT NOT NULL,
|
|
249
|
-
age_ms INTEGER NOT NULL
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
CREATE TABLE IF NOT EXISTS retrieval_feedback (
|
|
253
|
-
id TEXT PRIMARY KEY,
|
|
254
|
-
activation_event_id TEXT,
|
|
255
|
-
engram_id TEXT NOT NULL,
|
|
256
|
-
useful INTEGER NOT NULL,
|
|
257
|
-
context TEXT,
|
|
258
|
-
timestamp TEXT NOT NULL
|
|
259
|
-
);
|
|
260
|
-
|
|
261
|
-
CREATE TABLE IF NOT EXISTS episodes (
|
|
262
|
-
id TEXT PRIMARY KEY,
|
|
263
|
-
agent_id TEXT NOT NULL,
|
|
264
|
-
label TEXT NOT NULL,
|
|
265
|
-
embedding BLOB,
|
|
266
|
-
engram_count INTEGER NOT NULL DEFAULT 0,
|
|
267
|
-
start_time TEXT NOT NULL,
|
|
268
|
-
end_time TEXT NOT NULL,
|
|
269
|
-
created_at TEXT NOT NULL
|
|
270
|
-
);
|
|
271
|
-
|
|
272
|
-
CREATE INDEX IF NOT EXISTS idx_episodes_agent ON episodes(agent_id);
|
|
273
|
-
CREATE INDEX IF NOT EXISTS idx_episodes_time ON episodes(agent_id, end_time);
|
|
172
|
+
this.db.exec(`
|
|
173
|
+
CREATE TABLE IF NOT EXISTS engrams (
|
|
174
|
+
id TEXT PRIMARY KEY,
|
|
175
|
+
agent_id TEXT NOT NULL,
|
|
176
|
+
concept TEXT NOT NULL,
|
|
177
|
+
content TEXT NOT NULL,
|
|
178
|
+
embedding BLOB,
|
|
179
|
+
confidence REAL NOT NULL DEFAULT 0.5,
|
|
180
|
+
salience REAL NOT NULL DEFAULT 0.5,
|
|
181
|
+
access_count INTEGER NOT NULL DEFAULT 0,
|
|
182
|
+
last_accessed TEXT NOT NULL,
|
|
183
|
+
created_at TEXT NOT NULL,
|
|
184
|
+
salience_features TEXT NOT NULL DEFAULT '{}',
|
|
185
|
+
reason_codes TEXT NOT NULL DEFAULT '[]',
|
|
186
|
+
stage TEXT NOT NULL DEFAULT 'active',
|
|
187
|
+
ttl INTEGER,
|
|
188
|
+
retracted INTEGER NOT NULL DEFAULT 0,
|
|
189
|
+
retracted_by TEXT,
|
|
190
|
+
retracted_at TEXT,
|
|
191
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
192
|
+
memory_type TEXT NOT NULL DEFAULT 'unclassified'
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
CREATE INDEX IF NOT EXISTS idx_engrams_agent ON engrams(agent_id);
|
|
196
|
+
CREATE INDEX IF NOT EXISTS idx_engrams_stage ON engrams(agent_id, stage);
|
|
197
|
+
CREATE INDEX IF NOT EXISTS idx_engrams_concept ON engrams(concept);
|
|
198
|
+
CREATE INDEX IF NOT EXISTS idx_engrams_retracted ON engrams(agent_id, retracted);
|
|
199
|
+
-- Serves getTopAccessedTags: the recall path's pronoun branch needs the ten
|
|
200
|
+
-- most-accessed engrams, and without this it filters then sorts the whole
|
|
201
|
+
-- agent partition on every query containing "it", "this" or "that".
|
|
202
|
+
CREATE INDEX IF NOT EXISTS idx_engrams_access ON engrams(agent_id, stage, access_count DESC);
|
|
203
|
+
|
|
204
|
+
CREATE TABLE IF NOT EXISTS associations (
|
|
205
|
+
id TEXT PRIMARY KEY,
|
|
206
|
+
from_engram_id TEXT NOT NULL REFERENCES engrams(id) ON DELETE CASCADE,
|
|
207
|
+
to_engram_id TEXT NOT NULL REFERENCES engrams(id) ON DELETE CASCADE,
|
|
208
|
+
weight REAL NOT NULL DEFAULT 0.1,
|
|
209
|
+
confidence REAL NOT NULL DEFAULT 0.5,
|
|
210
|
+
type TEXT NOT NULL DEFAULT 'hebbian',
|
|
211
|
+
activation_count INTEGER NOT NULL DEFAULT 0,
|
|
212
|
+
created_at TEXT NOT NULL,
|
|
213
|
+
last_activated TEXT NOT NULL
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
CREATE INDEX IF NOT EXISTS idx_assoc_from ON associations(from_engram_id);
|
|
217
|
+
CREATE INDEX IF NOT EXISTS idx_assoc_to ON associations(to_engram_id);
|
|
218
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_assoc_pair ON associations(from_engram_id, to_engram_id);
|
|
219
|
+
|
|
220
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
221
|
+
id TEXT PRIMARY KEY,
|
|
222
|
+
name TEXT NOT NULL,
|
|
223
|
+
created_at TEXT NOT NULL,
|
|
224
|
+
config TEXT NOT NULL DEFAULT '{}'
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
-- FTS5 for full-text search (BM25 ranking built in)
|
|
228
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS engrams_fts USING fts5(
|
|
229
|
+
concept, content, tags,
|
|
230
|
+
content=engrams,
|
|
231
|
+
content_rowid=rowid
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
-- Triggers to keep FTS in sync
|
|
235
|
+
CREATE TRIGGER IF NOT EXISTS engrams_ai AFTER INSERT ON engrams BEGIN
|
|
236
|
+
INSERT INTO engrams_fts(rowid, concept, content, tags) VALUES (new.rowid, new.concept, new.content, new.tags);
|
|
237
|
+
END;
|
|
238
|
+
CREATE TRIGGER IF NOT EXISTS engrams_ad AFTER DELETE ON engrams BEGIN
|
|
239
|
+
INSERT INTO engrams_fts(engrams_fts, rowid, concept, content, tags) VALUES('delete', old.rowid, old.concept, old.content, old.tags);
|
|
240
|
+
END;
|
|
241
|
+
CREATE TRIGGER IF NOT EXISTS engrams_au AFTER UPDATE ON engrams BEGIN
|
|
242
|
+
INSERT INTO engrams_fts(engrams_fts, rowid, concept, content, tags) VALUES('delete', old.rowid, old.concept, old.content, old.tags);
|
|
243
|
+
INSERT INTO engrams_fts(rowid, concept, content, tags) VALUES (new.rowid, new.concept, new.content, new.tags);
|
|
244
|
+
END;
|
|
245
|
+
|
|
246
|
+
-- Eval event logs
|
|
247
|
+
CREATE TABLE IF NOT EXISTS activation_events (
|
|
248
|
+
id TEXT PRIMARY KEY,
|
|
249
|
+
agent_id TEXT NOT NULL,
|
|
250
|
+
timestamp TEXT NOT NULL,
|
|
251
|
+
context TEXT NOT NULL,
|
|
252
|
+
results_returned INTEGER NOT NULL,
|
|
253
|
+
top_score REAL,
|
|
254
|
+
latency_ms REAL NOT NULL,
|
|
255
|
+
engram_ids TEXT NOT NULL DEFAULT '[]'
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
CREATE TABLE IF NOT EXISTS staging_events (
|
|
259
|
+
engram_id TEXT NOT NULL,
|
|
260
|
+
agent_id TEXT NOT NULL,
|
|
261
|
+
action TEXT NOT NULL,
|
|
262
|
+
resonance_score REAL,
|
|
263
|
+
timestamp TEXT NOT NULL,
|
|
264
|
+
age_ms INTEGER NOT NULL
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
CREATE TABLE IF NOT EXISTS retrieval_feedback (
|
|
268
|
+
id TEXT PRIMARY KEY,
|
|
269
|
+
activation_event_id TEXT,
|
|
270
|
+
engram_id TEXT NOT NULL,
|
|
271
|
+
useful INTEGER NOT NULL,
|
|
272
|
+
context TEXT,
|
|
273
|
+
timestamp TEXT NOT NULL
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
CREATE TABLE IF NOT EXISTS episodes (
|
|
277
|
+
id TEXT PRIMARY KEY,
|
|
278
|
+
agent_id TEXT NOT NULL,
|
|
279
|
+
label TEXT NOT NULL,
|
|
280
|
+
embedding BLOB,
|
|
281
|
+
engram_count INTEGER NOT NULL DEFAULT 0,
|
|
282
|
+
start_time TEXT NOT NULL,
|
|
283
|
+
end_time TEXT NOT NULL,
|
|
284
|
+
created_at TEXT NOT NULL
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
CREATE INDEX IF NOT EXISTS idx_episodes_agent ON episodes(agent_id);
|
|
288
|
+
CREATE INDEX IF NOT EXISTS idx_episodes_time ON episodes(agent_id, end_time);
|
|
274
289
|
`);
|
|
275
290
|
// Migration: add episode_id column if missing
|
|
276
291
|
try {
|
|
@@ -284,10 +299,10 @@ export class EngramStore {
|
|
|
284
299
|
this.db.prepare('SELECT task_status FROM engrams LIMIT 0').get();
|
|
285
300
|
}
|
|
286
301
|
catch {
|
|
287
|
-
this.db.exec(`
|
|
288
|
-
ALTER TABLE engrams ADD COLUMN task_status TEXT;
|
|
289
|
-
ALTER TABLE engrams ADD COLUMN task_priority TEXT;
|
|
290
|
-
ALTER TABLE engrams ADD COLUMN blocked_by TEXT;
|
|
302
|
+
this.db.exec(`
|
|
303
|
+
ALTER TABLE engrams ADD COLUMN task_status TEXT;
|
|
304
|
+
ALTER TABLE engrams ADD COLUMN task_priority TEXT;
|
|
305
|
+
ALTER TABLE engrams ADD COLUMN blocked_by TEXT;
|
|
291
306
|
`);
|
|
292
307
|
this.db.exec('CREATE INDEX IF NOT EXISTS idx_engrams_task ON engrams(agent_id, task_status)');
|
|
293
308
|
}
|
|
@@ -296,10 +311,10 @@ export class EngramStore {
|
|
|
296
311
|
this.db.prepare('SELECT memory_class FROM engrams LIMIT 0').get();
|
|
297
312
|
}
|
|
298
313
|
catch {
|
|
299
|
-
this.db.exec(`
|
|
300
|
-
ALTER TABLE engrams ADD COLUMN memory_class TEXT NOT NULL DEFAULT 'working';
|
|
301
|
-
ALTER TABLE engrams ADD COLUMN superseded_by TEXT;
|
|
302
|
-
ALTER TABLE engrams ADD COLUMN supersedes TEXT;
|
|
314
|
+
this.db.exec(`
|
|
315
|
+
ALTER TABLE engrams ADD COLUMN memory_class TEXT NOT NULL DEFAULT 'working';
|
|
316
|
+
ALTER TABLE engrams ADD COLUMN superseded_by TEXT;
|
|
317
|
+
ALTER TABLE engrams ADD COLUMN supersedes TEXT;
|
|
303
318
|
`);
|
|
304
319
|
}
|
|
305
320
|
// Migration: add embedding_model for version tracking (prevents drift on model change)
|
|
@@ -311,49 +326,57 @@ export class EngramStore {
|
|
|
311
326
|
}
|
|
312
327
|
// Entity inverted index + alias table (D9, 2026-07-30) — write-time
|
|
313
328
|
// bookkeeping only; retrieval unchanged until D11. Idempotent CREATEs.
|
|
314
|
-
this.db.exec(`
|
|
315
|
-
CREATE TABLE IF NOT EXISTS entity_mentions (
|
|
316
|
-
entity TEXT NOT NULL,
|
|
317
|
-
engram_id TEXT NOT NULL,
|
|
318
|
-
agent_id TEXT NOT NULL,
|
|
319
|
-
PRIMARY KEY (entity, engram_id)
|
|
320
|
-
);
|
|
321
|
-
CREATE INDEX IF NOT EXISTS idx_entity_mentions_agent ON entity_mentions(agent_id, entity);
|
|
322
|
-
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
323
|
-
alias TEXT PRIMARY KEY,
|
|
324
|
-
entity TEXT NOT NULL
|
|
325
|
-
);
|
|
329
|
+
this.db.exec(`
|
|
330
|
+
CREATE TABLE IF NOT EXISTS entity_mentions (
|
|
331
|
+
entity TEXT NOT NULL,
|
|
332
|
+
engram_id TEXT NOT NULL,
|
|
333
|
+
agent_id TEXT NOT NULL,
|
|
334
|
+
PRIMARY KEY (entity, engram_id)
|
|
335
|
+
);
|
|
336
|
+
CREATE INDEX IF NOT EXISTS idx_entity_mentions_agent ON entity_mentions(agent_id, entity);
|
|
337
|
+
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
338
|
+
alias TEXT PRIMARY KEY,
|
|
339
|
+
entity TEXT NOT NULL
|
|
340
|
+
);
|
|
326
341
|
`);
|
|
327
342
|
// Migration: memory-spine provenance + temporal validity (D5/D8, 2026-07-30)
|
|
328
343
|
try {
|
|
329
344
|
this.db.prepare('SELECT origin_class FROM engrams LIMIT 0').get();
|
|
330
345
|
}
|
|
331
346
|
catch {
|
|
332
|
-
this.db.exec(`
|
|
333
|
-
ALTER TABLE engrams ADD COLUMN origin_class TEXT;
|
|
334
|
-
ALTER TABLE engrams ADD COLUMN writer_session TEXT;
|
|
335
|
-
ALTER TABLE engrams ADD COLUMN recipe_id TEXT;
|
|
336
|
-
ALTER TABLE engrams ADD COLUMN valid_from TEXT;
|
|
337
|
-
ALTER TABLE engrams ADD COLUMN valid_to TEXT;
|
|
347
|
+
this.db.exec(`
|
|
348
|
+
ALTER TABLE engrams ADD COLUMN origin_class TEXT;
|
|
349
|
+
ALTER TABLE engrams ADD COLUMN writer_session TEXT;
|
|
350
|
+
ALTER TABLE engrams ADD COLUMN recipe_id TEXT;
|
|
351
|
+
ALTER TABLE engrams ADD COLUMN valid_from TEXT;
|
|
352
|
+
ALTER TABLE engrams ADD COLUMN valid_to TEXT;
|
|
338
353
|
`);
|
|
339
354
|
}
|
|
355
|
+
// Migration: stage-transition timestamp (A-4, 2026-10-12). Additive, nullable,
|
|
356
|
+
// no backfill — null means "never transitioned", so readers fall back to created_at.
|
|
357
|
+
try {
|
|
358
|
+
this.db.prepare('SELECT stage_changed_at FROM engrams LIMIT 0').get();
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
this.db.exec(`ALTER TABLE engrams ADD COLUMN stage_changed_at TEXT`);
|
|
362
|
+
}
|
|
340
363
|
// Migration: add conscious_state table for checkpointing
|
|
341
|
-
this.db.exec(`
|
|
342
|
-
CREATE TABLE IF NOT EXISTS conscious_state (
|
|
343
|
-
agent_id TEXT PRIMARY KEY,
|
|
344
|
-
last_write_id TEXT,
|
|
345
|
-
last_recall_context TEXT,
|
|
346
|
-
last_recall_ids TEXT NOT NULL DEFAULT '[]',
|
|
347
|
-
last_activity_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
348
|
-
write_count_since_consolidation INTEGER NOT NULL DEFAULT 0,
|
|
349
|
-
recall_count_since_consolidation INTEGER NOT NULL DEFAULT 0,
|
|
350
|
-
execution_state TEXT,
|
|
351
|
-
checkpoint_at TEXT,
|
|
352
|
-
last_consolidation_at TEXT,
|
|
353
|
-
last_mini_consolidation_at TEXT,
|
|
354
|
-
consolidation_cycle_count INTEGER NOT NULL DEFAULT 0,
|
|
355
|
-
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
356
|
-
)
|
|
364
|
+
this.db.exec(`
|
|
365
|
+
CREATE TABLE IF NOT EXISTS conscious_state (
|
|
366
|
+
agent_id TEXT PRIMARY KEY,
|
|
367
|
+
last_write_id TEXT,
|
|
368
|
+
last_recall_context TEXT,
|
|
369
|
+
last_recall_ids TEXT NOT NULL DEFAULT '[]',
|
|
370
|
+
last_activity_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
371
|
+
write_count_since_consolidation INTEGER NOT NULL DEFAULT 0,
|
|
372
|
+
recall_count_since_consolidation INTEGER NOT NULL DEFAULT 0,
|
|
373
|
+
execution_state TEXT,
|
|
374
|
+
checkpoint_at TEXT,
|
|
375
|
+
last_consolidation_at TEXT,
|
|
376
|
+
last_mini_consolidation_at TEXT,
|
|
377
|
+
consolidation_cycle_count INTEGER NOT NULL DEFAULT 0,
|
|
378
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
379
|
+
)
|
|
357
380
|
`);
|
|
358
381
|
// Migration: add consolidation_cycle_count if missing (existing DBs)
|
|
359
382
|
try {
|
|
@@ -374,7 +397,7 @@ export class EngramStore {
|
|
|
374
397
|
}
|
|
375
398
|
catch {
|
|
376
399
|
this.db.exec(`ALTER TABLE engrams ADD COLUMN sequence INTEGER`);
|
|
377
|
-
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_engrams_agent_sequence
|
|
400
|
+
this.db.exec(`CREATE INDEX IF NOT EXISTS idx_engrams_agent_sequence
|
|
378
401
|
ON engrams(agent_id, sequence) WHERE sequence IS NOT NULL`);
|
|
379
402
|
}
|
|
380
403
|
// Migration (0.8 Cluster A): references_json column for typed cross-record
|
|
@@ -393,12 +416,12 @@ export class EngramStore {
|
|
|
393
416
|
const embeddingBlob = input.embedding
|
|
394
417
|
? Buffer.from(new Float32Array(input.embedding).buffer)
|
|
395
418
|
: null;
|
|
396
|
-
this.db.prepare(`
|
|
397
|
-
INSERT INTO engrams (id, agent_id, concept, content, embedding, confidence, salience,
|
|
398
|
-
access_count, last_accessed, created_at, salience_features, reason_codes, stage, tags, episode_id,
|
|
399
|
-
ttl, memory_class, supersedes, task_status, task_priority, blocked_by, memory_type,
|
|
400
|
-
sequence, references_json, origin_class, writer_session, recipe_id, valid_from, valid_to)
|
|
401
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, 'active', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
419
|
+
this.db.prepare(`
|
|
420
|
+
INSERT INTO engrams (id, agent_id, concept, content, embedding, confidence, salience,
|
|
421
|
+
access_count, last_accessed, created_at, salience_features, reason_codes, stage, tags, episode_id,
|
|
422
|
+
ttl, memory_class, supersedes, task_status, task_priority, blocked_by, memory_type,
|
|
423
|
+
sequence, references_json, origin_class, writer_session, recipe_id, valid_from, valid_to)
|
|
424
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, 'active', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
402
425
|
`).run(id, input.agentId, input.concept, input.content, embeddingBlob, input.confidence ?? 0.5, input.salience ?? 0.5, now, now, JSON.stringify(input.salienceFeatures ?? DEFAULT_SALIENCE_FEATURES), JSON.stringify(input.reasonCodes ?? []), JSON.stringify(input.tags ?? []), input.episodeId ?? null, input.ttl ?? null, input.memoryClass ?? 'working', input.supersedes ?? null, input.taskStatus ?? null, input.taskPriority ?? null, input.blockedBy ?? null, input.memoryType ?? 'unclassified', input.sequence ?? null, input.references && input.references.length > 0
|
|
403
426
|
? JSON.stringify(input.references) : null, input.originClass ?? null, input.writerSession ?? null, input.recipeId ?? null, input.validFrom ?? null, input.validTo ?? null);
|
|
404
427
|
// Add to slim cache (skip if not yet populated — first slim fetch will load it)
|
|
@@ -578,11 +601,11 @@ export class EngramStore {
|
|
|
578
601
|
getTopAccessedTags(agentIds, topN, stage = 'active') {
|
|
579
602
|
if (agentIds.length === 0 || topN <= 0)
|
|
580
603
|
return [];
|
|
581
|
-
const stmt = this.db.prepare(`
|
|
582
|
-
SELECT tags, access_count AS accessCount, id FROM engrams
|
|
583
|
-
WHERE agent_id = ? AND stage = ? AND retracted = 0
|
|
584
|
-
ORDER BY access_count DESC, id
|
|
585
|
-
LIMIT ?
|
|
604
|
+
const stmt = this.db.prepare(`
|
|
605
|
+
SELECT tags, access_count AS accessCount, id FROM engrams
|
|
606
|
+
WHERE agent_id = ? AND stage = ? AND retracted = 0
|
|
607
|
+
ORDER BY access_count DESC, id
|
|
608
|
+
LIMIT ?
|
|
586
609
|
`);
|
|
587
610
|
const rows = [];
|
|
588
611
|
for (const agentId of agentIds) {
|
|
@@ -620,15 +643,15 @@ export class EngramStore {
|
|
|
620
643
|
try {
|
|
621
644
|
const placeholders = agentIds.map(() => '?').join(',');
|
|
622
645
|
const innerLimit = Math.max(limit * 5, 50);
|
|
623
|
-
const rows = this.db.prepare(`
|
|
624
|
-
WITH top_fts AS (
|
|
625
|
-
SELECT rowid, rank FROM engrams_fts WHERE engrams_fts MATCH ? ORDER BY rank LIMIT ?
|
|
626
|
-
)
|
|
627
|
-
SELECT e.*, top_fts.rank FROM top_fts
|
|
628
|
-
JOIN engrams e ON e.rowid = top_fts.rowid
|
|
629
|
-
WHERE e.agent_id IN (${placeholders}) AND e.retracted = 0
|
|
630
|
-
ORDER BY top_fts.rank
|
|
631
|
-
LIMIT ?
|
|
646
|
+
const rows = this.db.prepare(`
|
|
647
|
+
WITH top_fts AS (
|
|
648
|
+
SELECT rowid, rank FROM engrams_fts WHERE engrams_fts MATCH ? ORDER BY rank LIMIT ?
|
|
649
|
+
)
|
|
650
|
+
SELECT e.*, top_fts.rank FROM top_fts
|
|
651
|
+
JOIN engrams e ON e.rowid = top_fts.rowid
|
|
652
|
+
WHERE e.agent_id IN (${placeholders}) AND e.retracted = 0
|
|
653
|
+
ORDER BY top_fts.rank
|
|
654
|
+
LIMIT ?
|
|
632
655
|
`).all(sanitized, innerLimit, ...agentIds, limit);
|
|
633
656
|
return rows.map(r => ({
|
|
634
657
|
engram: this.rowToEngram(r),
|
|
@@ -667,16 +690,22 @@ export class EngramStore {
|
|
|
667
690
|
* Boost: +0.02 per access, scaled by 1/sqrt(accessCount+1), capped at 0.85.
|
|
668
691
|
*/
|
|
669
692
|
touchEngram(id) {
|
|
670
|
-
this.db.prepare(`
|
|
671
|
-
UPDATE engrams
|
|
672
|
-
SET access_count = access_count + 1,
|
|
673
|
-
last_accessed = ?,
|
|
674
|
-
confidence = MIN(0.85, confidence + 0.02 / (1.0 + sqrt(access_count)))
|
|
675
|
-
WHERE id = ?
|
|
693
|
+
this.db.prepare(`
|
|
694
|
+
UPDATE engrams
|
|
695
|
+
SET access_count = access_count + 1,
|
|
696
|
+
last_accessed = ?,
|
|
697
|
+
confidence = MIN(0.85, confidence + 0.02 / (1.0 + sqrt(access_count)))
|
|
698
|
+
WHERE id = ?
|
|
676
699
|
`).run(new Date().toISOString(), id);
|
|
677
700
|
}
|
|
701
|
+
/**
|
|
702
|
+
* Move an engram to a new stage, recording WHEN. The timestamp is written here
|
|
703
|
+
* rather than at the six consolidation call sites so a future transition cannot
|
|
704
|
+
* forget it; see `Engram.stageChangedAt` for why the column exists.
|
|
705
|
+
*/
|
|
678
706
|
updateStage(id, stage) {
|
|
679
|
-
this.db.prepare('UPDATE engrams SET stage = ? WHERE id = ?')
|
|
707
|
+
this.db.prepare('UPDATE engrams SET stage = ?, stage_changed_at = ? WHERE id = ?')
|
|
708
|
+
.run(stage, new Date().toISOString(), id);
|
|
680
709
|
this.cacheUpdateStage(id, stage);
|
|
681
710
|
}
|
|
682
711
|
/**
|
|
@@ -702,13 +731,35 @@ export class EngramStore {
|
|
|
702
731
|
this.cacheUpdateEmbedding(id, embedding);
|
|
703
732
|
}
|
|
704
733
|
retractEngram(id, retractedBy) {
|
|
705
|
-
this.db.prepare(`
|
|
706
|
-
UPDATE engrams SET retracted = 1, retracted_by = ?, retracted_at = ? WHERE id = ?
|
|
734
|
+
this.db.prepare(`
|
|
735
|
+
UPDATE engrams SET retracted = 1, retracted_by = ?, retracted_at = ? WHERE id = ?
|
|
707
736
|
`).run(retractedBy, new Date().toISOString(), id);
|
|
708
737
|
this.cacheRetract(id);
|
|
709
738
|
}
|
|
739
|
+
/**
|
|
740
|
+
* Delete an engram and everything that points at it.
|
|
741
|
+
*
|
|
742
|
+
* `associations` cascades (its FKs declare ON DELETE CASCADE), but
|
|
743
|
+
* `retrieval_feedback`, `entity_mentions` and `staging_events` carry a bare
|
|
744
|
+
* `engram_id TEXT` with no foreign key, so deleting an engram used to strand their
|
|
745
|
+
* rows. Measured on the live store 2026-10-12: **571 orphaned retrieval_feedback
|
|
746
|
+
* rows and 3 orphaned entity_mentions**, present in the pre-purge backup too, so
|
|
747
|
+
* they accumulated over time rather than in one incident. Inert for recall, but
|
|
748
|
+
* they corrupt any join that assumes referential integrity — including the
|
|
749
|
+
* feedback-weighted scoring channel's own counts.
|
|
750
|
+
*
|
|
751
|
+
* One transaction, so a crash cannot leave the half-deleted state this is fixing.
|
|
752
|
+
* `activation_events` is deliberately NOT touched: its `engram_ids` is a JSON array
|
|
753
|
+
* describing a recall that really happened, and editing history to remove one id
|
|
754
|
+
* would falsify the event rather than tidy it.
|
|
755
|
+
*/
|
|
710
756
|
deleteEngram(id) {
|
|
711
|
-
this.db.
|
|
757
|
+
this.db.transaction(() => {
|
|
758
|
+
this.db.prepare('DELETE FROM retrieval_feedback WHERE engram_id = ?').run(id);
|
|
759
|
+
this.db.prepare('DELETE FROM entity_mentions WHERE engram_id = ?').run(id);
|
|
760
|
+
this.db.prepare('DELETE FROM staging_events WHERE engram_id = ?').run(id);
|
|
761
|
+
this.db.prepare('DELETE FROM engrams WHERE id = ?').run(id);
|
|
762
|
+
})();
|
|
712
763
|
this.cacheRemove(id);
|
|
713
764
|
}
|
|
714
765
|
/**
|
|
@@ -720,20 +771,20 @@ export class EngramStore {
|
|
|
720
771
|
let count = 0;
|
|
721
772
|
const shiftSec = Math.round(ms / 1000);
|
|
722
773
|
// Shift engram timestamps
|
|
723
|
-
const r1 = this.db.prepare(`
|
|
724
|
-
UPDATE engrams SET
|
|
725
|
-
created_at = datetime(created_at, '-${shiftSec} seconds'),
|
|
726
|
-
last_accessed = datetime(last_accessed, '-${shiftSec} seconds')
|
|
727
|
-
WHERE agent_id = ?
|
|
774
|
+
const r1 = this.db.prepare(`
|
|
775
|
+
UPDATE engrams SET
|
|
776
|
+
created_at = datetime(created_at, '-${shiftSec} seconds'),
|
|
777
|
+
last_accessed = datetime(last_accessed, '-${shiftSec} seconds')
|
|
778
|
+
WHERE agent_id = ?
|
|
728
779
|
`).run(agentId);
|
|
729
780
|
count += r1.changes;
|
|
730
781
|
// Shift association timestamps
|
|
731
|
-
const r2 = this.db.prepare(`
|
|
732
|
-
UPDATE associations SET
|
|
733
|
-
created_at = datetime(created_at, '-${shiftSec} seconds'),
|
|
734
|
-
last_activated = datetime(last_activated, '-${shiftSec} seconds')
|
|
735
|
-
WHERE from_engram_id IN (SELECT id FROM engrams WHERE agent_id = ?)
|
|
736
|
-
OR to_engram_id IN (SELECT id FROM engrams WHERE agent_id = ?)
|
|
782
|
+
const r2 = this.db.prepare(`
|
|
783
|
+
UPDATE associations SET
|
|
784
|
+
created_at = datetime(created_at, '-${shiftSec} seconds'),
|
|
785
|
+
last_activated = datetime(last_activated, '-${shiftSec} seconds')
|
|
786
|
+
WHERE from_engram_id IN (SELECT id FROM engrams WHERE agent_id = ?)
|
|
787
|
+
OR to_engram_id IN (SELECT id FROM engrams WHERE agent_id = ?)
|
|
737
788
|
`).run(agentId, agentId);
|
|
738
789
|
count += r2.changes;
|
|
739
790
|
return count;
|
|
@@ -812,15 +863,15 @@ export class EngramStore {
|
|
|
812
863
|
// dominant, low retracted rate).
|
|
813
864
|
try {
|
|
814
865
|
const innerLimit = Math.max(limit * 5, 50);
|
|
815
|
-
const rows = this.db.prepare(`
|
|
816
|
-
WITH top_fts AS (
|
|
817
|
-
SELECT rowid, rank FROM engrams_fts WHERE engrams_fts MATCH ? ORDER BY rank LIMIT ?
|
|
818
|
-
)
|
|
819
|
-
SELECT e.*, top_fts.rank FROM top_fts
|
|
820
|
-
JOIN engrams e ON e.rowid = top_fts.rowid
|
|
821
|
-
WHERE e.agent_id = ? AND e.retracted = 0
|
|
822
|
-
ORDER BY top_fts.rank
|
|
823
|
-
LIMIT ?
|
|
866
|
+
const rows = this.db.prepare(`
|
|
867
|
+
WITH top_fts AS (
|
|
868
|
+
SELECT rowid, rank FROM engrams_fts WHERE engrams_fts MATCH ? ORDER BY rank LIMIT ?
|
|
869
|
+
)
|
|
870
|
+
SELECT e.*, top_fts.rank FROM top_fts
|
|
871
|
+
JOIN engrams e ON e.rowid = top_fts.rowid
|
|
872
|
+
WHERE e.agent_id = ? AND e.retracted = 0
|
|
873
|
+
ORDER BY top_fts.rank
|
|
874
|
+
LIMIT ?
|
|
824
875
|
`).all(sanitized, innerLimit, agentId, limit);
|
|
825
876
|
return rows.map(r => ({
|
|
826
877
|
engram: this.rowToEngram(r),
|
|
@@ -940,14 +991,14 @@ export class EngramStore {
|
|
|
940
991
|
sql += ' AND task_status = ?';
|
|
941
992
|
params.push(status);
|
|
942
993
|
}
|
|
943
|
-
sql += ` ORDER BY
|
|
944
|
-
CASE task_priority
|
|
945
|
-
WHEN 'urgent' THEN 0
|
|
946
|
-
WHEN 'high' THEN 1
|
|
947
|
-
WHEN 'medium' THEN 2
|
|
948
|
-
WHEN 'low' THEN 3
|
|
949
|
-
ELSE 4
|
|
950
|
-
END,
|
|
994
|
+
sql += ` ORDER BY
|
|
995
|
+
CASE task_priority
|
|
996
|
+
WHEN 'urgent' THEN 0
|
|
997
|
+
WHEN 'high' THEN 1
|
|
998
|
+
WHEN 'medium' THEN 2
|
|
999
|
+
WHEN 'low' THEN 3
|
|
1000
|
+
ELSE 4
|
|
1001
|
+
END,
|
|
951
1002
|
created_at DESC`;
|
|
952
1003
|
return this.db.prepare(sql).all(...params).map(r => this.rowToEngram(r));
|
|
953
1004
|
}
|
|
@@ -955,20 +1006,20 @@ export class EngramStore {
|
|
|
955
1006
|
* Get the next actionable task — highest priority that's not blocked or done.
|
|
956
1007
|
*/
|
|
957
1008
|
getNextTask(agentId) {
|
|
958
|
-
const row = this.db.prepare(`
|
|
959
|
-
SELECT * FROM engrams
|
|
960
|
-
WHERE agent_id = ? AND task_status IN ('open', 'in_progress') AND retracted = 0
|
|
961
|
-
ORDER BY
|
|
962
|
-
CASE task_status WHEN 'in_progress' THEN 0 ELSE 1 END,
|
|
963
|
-
CASE task_priority
|
|
964
|
-
WHEN 'urgent' THEN 0
|
|
965
|
-
WHEN 'high' THEN 1
|
|
966
|
-
WHEN 'medium' THEN 2
|
|
967
|
-
WHEN 'low' THEN 3
|
|
968
|
-
ELSE 4
|
|
969
|
-
END,
|
|
970
|
-
created_at ASC
|
|
971
|
-
LIMIT 1
|
|
1009
|
+
const row = this.db.prepare(`
|
|
1010
|
+
SELECT * FROM engrams
|
|
1011
|
+
WHERE agent_id = ? AND task_status IN ('open', 'in_progress') AND retracted = 0
|
|
1012
|
+
ORDER BY
|
|
1013
|
+
CASE task_status WHEN 'in_progress' THEN 0 ELSE 1 END,
|
|
1014
|
+
CASE task_priority
|
|
1015
|
+
WHEN 'urgent' THEN 0
|
|
1016
|
+
WHEN 'high' THEN 1
|
|
1017
|
+
WHEN 'medium' THEN 2
|
|
1018
|
+
WHEN 'low' THEN 3
|
|
1019
|
+
ELSE 4
|
|
1020
|
+
END,
|
|
1021
|
+
created_at ASC
|
|
1022
|
+
LIMIT 1
|
|
972
1023
|
`).get(agentId);
|
|
973
1024
|
return row ? this.rowToEngram(row) : null;
|
|
974
1025
|
}
|
|
@@ -990,11 +1041,11 @@ export class EngramStore {
|
|
|
990
1041
|
* Returns null if no match.
|
|
991
1042
|
*/
|
|
992
1043
|
findActiveMatchByConcept(agentId, concept, requiredTags) {
|
|
993
|
-
let sql = `SELECT * FROM engrams
|
|
994
|
-
WHERE agent_id = ?
|
|
995
|
-
AND LOWER(TRIM(concept)) = LOWER(TRIM(?))
|
|
996
|
-
AND stage = 'active'
|
|
997
|
-
AND retracted = 0
|
|
1044
|
+
let sql = `SELECT * FROM engrams
|
|
1045
|
+
WHERE agent_id = ?
|
|
1046
|
+
AND LOWER(TRIM(concept)) = LOWER(TRIM(?))
|
|
1047
|
+
AND stage = 'active'
|
|
1048
|
+
AND retracted = 0
|
|
998
1049
|
AND superseded_by IS NULL`;
|
|
999
1050
|
const params = [agentId, concept];
|
|
1000
1051
|
if (requiredTags && requiredTags.length > 0) {
|
|
@@ -1029,11 +1080,11 @@ export class EngramStore {
|
|
|
1029
1080
|
upsertAssociation(fromId, toId, weight, type = 'hebbian', confidence = 0.5) {
|
|
1030
1081
|
const now = new Date().toISOString();
|
|
1031
1082
|
const id = randomUUID();
|
|
1032
|
-
this.db.prepare(`
|
|
1033
|
-
INSERT INTO associations (id, from_engram_id, to_engram_id, weight, confidence, type, activation_count, created_at, last_activated)
|
|
1034
|
-
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)
|
|
1035
|
-
ON CONFLICT(from_engram_id, to_engram_id) DO UPDATE SET
|
|
1036
|
-
weight = ?, confidence = ?, last_activated = ?, activation_count = activation_count + 1
|
|
1083
|
+
this.db.prepare(`
|
|
1084
|
+
INSERT INTO associations (id, from_engram_id, to_engram_id, weight, confidence, type, activation_count, created_at, last_activated)
|
|
1085
|
+
VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)
|
|
1086
|
+
ON CONFLICT(from_engram_id, to_engram_id) DO UPDATE SET
|
|
1087
|
+
weight = ?, confidence = ?, last_activated = ?, activation_count = activation_count + 1
|
|
1037
1088
|
`).run(id, fromId, toId, weight, confidence, type, now, now, weight, confidence, now);
|
|
1038
1089
|
return this.getAssociation(fromId, toId);
|
|
1039
1090
|
}
|
|
@@ -1073,12 +1124,12 @@ export class EngramStore {
|
|
|
1073
1124
|
// associations under both their from and to engram. Self-loops would be
|
|
1074
1125
|
// double-counted, but they're rare in practice and the prior code handled them
|
|
1075
1126
|
// identically.
|
|
1076
|
-
const rows = this.db.prepare(`SELECT id, SUM(cnt) AS count, SUM(sw) AS sum_weight FROM (
|
|
1077
|
-
SELECT from_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE from_engram_id IN (${placeholders})
|
|
1078
|
-
UNION ALL
|
|
1079
|
-
SELECT to_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE to_engram_id IN (${placeholders})
|
|
1080
|
-
)
|
|
1081
|
-
WHERE id IN (${placeholders})
|
|
1127
|
+
const rows = this.db.prepare(`SELECT id, SUM(cnt) AS count, SUM(sw) AS sum_weight FROM (
|
|
1128
|
+
SELECT from_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE from_engram_id IN (${placeholders})
|
|
1129
|
+
UNION ALL
|
|
1130
|
+
SELECT to_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE to_engram_id IN (${placeholders})
|
|
1131
|
+
)
|
|
1132
|
+
WHERE id IN (${placeholders})
|
|
1082
1133
|
GROUP BY id`).all(...chunk, ...chunk, ...chunk);
|
|
1083
1134
|
for (const r of rows) {
|
|
1084
1135
|
result.set(r.id, { count: r.count, sumWeight: r.sum_weight });
|
|
@@ -1109,7 +1160,7 @@ export class EngramStore {
|
|
|
1109
1160
|
for (let i = 0; i < engramIds.length; i += CHUNK) {
|
|
1110
1161
|
const chunk = engramIds.slice(i, i + CHUNK);
|
|
1111
1162
|
const placeholders = chunk.map(() => '?').join(',');
|
|
1112
|
-
const rows = this.db.prepare(`SELECT * FROM associations
|
|
1163
|
+
const rows = this.db.prepare(`SELECT * FROM associations
|
|
1113
1164
|
WHERE from_engram_id IN (${placeholders}) OR to_engram_id IN (${placeholders})`).all(...chunk, ...chunk);
|
|
1114
1165
|
for (const r of rows) {
|
|
1115
1166
|
const a = this.rowToAssociation(r);
|
|
@@ -1148,22 +1199,22 @@ export class EngramStore {
|
|
|
1148
1199
|
this.db.prepare('DELETE FROM associations WHERE id = ?').run(id);
|
|
1149
1200
|
}
|
|
1150
1201
|
getAllAssociations(agentId) {
|
|
1151
|
-
const rows = this.db.prepare(`
|
|
1152
|
-
SELECT a.* FROM associations a
|
|
1153
|
-
JOIN engrams e ON a.from_engram_id = e.id
|
|
1154
|
-
WHERE e.agent_id = ?
|
|
1202
|
+
const rows = this.db.prepare(`
|
|
1203
|
+
SELECT a.* FROM associations a
|
|
1204
|
+
JOIN engrams e ON a.from_engram_id = e.id
|
|
1205
|
+
WHERE e.agent_id = ?
|
|
1155
1206
|
`).all(agentId);
|
|
1156
1207
|
return rows.map(r => this.rowToAssociation(r));
|
|
1157
1208
|
}
|
|
1158
1209
|
// --- Eviction ---
|
|
1159
1210
|
getEvictionCandidates(agentId, limit) {
|
|
1160
1211
|
// Lowest combined score: low salience + low access + low confidence + oldest
|
|
1161
|
-
const rows = this.db.prepare(`
|
|
1162
|
-
SELECT * FROM engrams
|
|
1163
|
-
WHERE agent_id = ? AND stage = 'active' AND retracted = 0
|
|
1164
|
-
ORDER BY (salience * 0.3 + confidence * 0.3 + (CAST(access_count AS REAL) / (access_count + 5)) * 0.2 +
|
|
1165
|
-
(1.0 / (1.0 + (julianday('now') - julianday(last_accessed)))) * 0.2) ASC
|
|
1166
|
-
LIMIT ?
|
|
1212
|
+
const rows = this.db.prepare(`
|
|
1213
|
+
SELECT * FROM engrams
|
|
1214
|
+
WHERE agent_id = ? AND stage = 'active' AND retracted = 0
|
|
1215
|
+
ORDER BY (salience * 0.3 + confidence * 0.3 + (CAST(access_count AS REAL) / (access_count + 5)) * 0.2 +
|
|
1216
|
+
(1.0 / (1.0 + (julianday('now') - julianday(last_accessed)))) * 0.2) ASC
|
|
1217
|
+
LIMIT ?
|
|
1167
1218
|
`).all(agentId, limit);
|
|
1168
1219
|
return rows.map(r => this.rowToEngram(r));
|
|
1169
1220
|
}
|
|
@@ -1178,8 +1229,8 @@ export class EngramStore {
|
|
|
1178
1229
|
// --- Staging buffer ---
|
|
1179
1230
|
getExpiredStaging() {
|
|
1180
1231
|
const now = Date.now();
|
|
1181
|
-
const rows = this.db.prepare(`
|
|
1182
|
-
SELECT * FROM engrams WHERE stage = 'staging' AND ttl IS NOT NULL
|
|
1232
|
+
const rows = this.db.prepare(`
|
|
1233
|
+
SELECT * FROM engrams WHERE stage = 'staging' AND ttl IS NOT NULL
|
|
1183
1234
|
`).all();
|
|
1184
1235
|
return rows
|
|
1185
1236
|
.map(r => this.rowToEngram(r))
|
|
@@ -1187,53 +1238,53 @@ export class EngramStore {
|
|
|
1187
1238
|
}
|
|
1188
1239
|
// --- Eval event logging ---
|
|
1189
1240
|
logActivationEvent(event) {
|
|
1190
|
-
this.db.prepare(`
|
|
1191
|
-
INSERT INTO activation_events (id, agent_id, timestamp, context, results_returned, top_score, latency_ms, engram_ids)
|
|
1192
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1241
|
+
this.db.prepare(`
|
|
1242
|
+
INSERT INTO activation_events (id, agent_id, timestamp, context, results_returned, top_score, latency_ms, engram_ids)
|
|
1243
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1193
1244
|
`).run(event.id, event.agentId, event.timestamp.toISOString(), event.context, event.resultsReturned, event.topScore, event.latencyMs, JSON.stringify(event.engramIds));
|
|
1194
1245
|
}
|
|
1195
1246
|
logStagingEvent(event) {
|
|
1196
|
-
this.db.prepare(`
|
|
1197
|
-
INSERT INTO staging_events (engram_id, agent_id, action, resonance_score, timestamp, age_ms)
|
|
1198
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
1247
|
+
this.db.prepare(`
|
|
1248
|
+
INSERT INTO staging_events (engram_id, agent_id, action, resonance_score, timestamp, age_ms)
|
|
1249
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
1199
1250
|
`).run(event.engramId, event.agentId, event.action, event.resonanceScore, event.timestamp.toISOString(), event.ageMs);
|
|
1200
1251
|
}
|
|
1201
1252
|
logRetrievalFeedback(activationEventId, engramId, useful, context) {
|
|
1202
|
-
this.db.prepare(`
|
|
1203
|
-
INSERT INTO retrieval_feedback (id, activation_event_id, engram_id, useful, context, timestamp)
|
|
1204
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
1253
|
+
this.db.prepare(`
|
|
1254
|
+
INSERT INTO retrieval_feedback (id, activation_event_id, engram_id, useful, context, timestamp)
|
|
1255
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
1205
1256
|
`).run(randomUUID(), activationEventId, engramId, useful ? 1 : 0, context, new Date().toISOString());
|
|
1206
1257
|
}
|
|
1207
1258
|
// --- Eval metrics queries ---
|
|
1208
1259
|
getRetrievalPrecision(agentId, windowHours = 24) {
|
|
1209
1260
|
const since = new Date(Date.now() - windowHours * 3600_000).toISOString();
|
|
1210
|
-
const row = this.db.prepare(`
|
|
1211
|
-
SELECT
|
|
1212
|
-
COUNT(CASE WHEN useful = 1 THEN 1 END) as useful_count,
|
|
1213
|
-
COUNT(*) as total_count
|
|
1214
|
-
FROM retrieval_feedback rf
|
|
1215
|
-
LEFT JOIN activation_events ae ON rf.activation_event_id = ae.id
|
|
1216
|
-
JOIN engrams e ON rf.engram_id = e.id
|
|
1217
|
-
WHERE e.agent_id = ? AND rf.timestamp > ?
|
|
1261
|
+
const row = this.db.prepare(`
|
|
1262
|
+
SELECT
|
|
1263
|
+
COUNT(CASE WHEN useful = 1 THEN 1 END) as useful_count,
|
|
1264
|
+
COUNT(*) as total_count
|
|
1265
|
+
FROM retrieval_feedback rf
|
|
1266
|
+
LEFT JOIN activation_events ae ON rf.activation_event_id = ae.id
|
|
1267
|
+
JOIN engrams e ON rf.engram_id = e.id
|
|
1268
|
+
WHERE e.agent_id = ? AND rf.timestamp > ?
|
|
1218
1269
|
`).get(agentId, since);
|
|
1219
1270
|
return row.total_count > 0 ? row.useful_count / row.total_count : 0;
|
|
1220
1271
|
}
|
|
1221
1272
|
getStagingMetrics(agentId) {
|
|
1222
|
-
const row = this.db.prepare(`
|
|
1223
|
-
SELECT
|
|
1224
|
-
COUNT(CASE WHEN action = 'promoted' THEN 1 END) as promoted,
|
|
1225
|
-
COUNT(CASE WHEN action = 'discarded' THEN 1 END) as discarded,
|
|
1226
|
-
COUNT(CASE WHEN action = 'expired' THEN 1 END) as expired
|
|
1227
|
-
FROM staging_events WHERE agent_id = ?
|
|
1273
|
+
const row = this.db.prepare(`
|
|
1274
|
+
SELECT
|
|
1275
|
+
COUNT(CASE WHEN action = 'promoted' THEN 1 END) as promoted,
|
|
1276
|
+
COUNT(CASE WHEN action = 'discarded' THEN 1 END) as discarded,
|
|
1277
|
+
COUNT(CASE WHEN action = 'expired' THEN 1 END) as expired
|
|
1278
|
+
FROM staging_events WHERE agent_id = ?
|
|
1228
1279
|
`).get(agentId);
|
|
1229
1280
|
return { promoted: row.promoted, discarded: row.discarded, expired: row.expired };
|
|
1230
1281
|
}
|
|
1231
1282
|
getActivationStats(agentId, windowHours = 24) {
|
|
1232
1283
|
const since = new Date(Date.now() - windowHours * 3600_000).toISOString();
|
|
1233
|
-
const rows = this.db.prepare(`
|
|
1234
|
-
SELECT latency_ms FROM activation_events
|
|
1235
|
-
WHERE agent_id = ? AND timestamp > ?
|
|
1236
|
-
ORDER BY latency_ms ASC
|
|
1284
|
+
const rows = this.db.prepare(`
|
|
1285
|
+
SELECT latency_ms FROM activation_events
|
|
1286
|
+
WHERE agent_id = ? AND timestamp > ?
|
|
1287
|
+
ORDER BY latency_ms ASC
|
|
1237
1288
|
`).all(agentId, since);
|
|
1238
1289
|
if (rows.length === 0)
|
|
1239
1290
|
return { count: 0, avgLatencyMs: 0, p50LatencyMs: 0, p90LatencyMs: 0, p95LatencyMs: 0 };
|
|
@@ -1258,11 +1309,11 @@ export class EngramStore {
|
|
|
1258
1309
|
*/
|
|
1259
1310
|
getLinkedFeedbackStats(agentId, windowHours = 24 * 7) {
|
|
1260
1311
|
const since = new Date(Date.now() - windowHours * 3600_000).toISOString();
|
|
1261
|
-
const row = this.db.prepare(`
|
|
1262
|
-
SELECT COUNT(*) AS total, COUNT(CASE WHEN rf.useful = 1 THEN 1 END) AS useful
|
|
1263
|
-
FROM retrieval_feedback rf
|
|
1264
|
-
JOIN activation_events ae ON ae.id = rf.activation_event_id
|
|
1265
|
-
WHERE ae.agent_id = ? AND rf.timestamp > ?
|
|
1312
|
+
const row = this.db.prepare(`
|
|
1313
|
+
SELECT COUNT(*) AS total, COUNT(CASE WHEN rf.useful = 1 THEN 1 END) AS useful
|
|
1314
|
+
FROM retrieval_feedback rf
|
|
1315
|
+
JOIN activation_events ae ON ae.id = rf.activation_event_id
|
|
1316
|
+
WHERE ae.agent_id = ? AND rf.timestamp > ?
|
|
1266
1317
|
`).get(agentId, since);
|
|
1267
1318
|
return { total: row.total, useful: row.useful };
|
|
1268
1319
|
}
|
|
@@ -1308,6 +1359,7 @@ export class EngramStore {
|
|
|
1308
1359
|
recipeId: row.recipe_id ?? null,
|
|
1309
1360
|
validFrom: row.valid_from ?? null,
|
|
1310
1361
|
validTo: row.valid_to ?? null,
|
|
1362
|
+
stageChangedAt: row.stage_changed_at ?? null,
|
|
1311
1363
|
};
|
|
1312
1364
|
}
|
|
1313
1365
|
rowToAssociation(row) {
|
|
@@ -1330,9 +1382,9 @@ export class EngramStore {
|
|
|
1330
1382
|
const embeddingBlob = input.embedding
|
|
1331
1383
|
? Buffer.from(new Float32Array(input.embedding).buffer)
|
|
1332
1384
|
: null;
|
|
1333
|
-
this.db.prepare(`
|
|
1334
|
-
INSERT INTO episodes (id, agent_id, label, embedding, engram_count, start_time, end_time, created_at)
|
|
1335
|
-
VALUES (?, ?, ?, ?, 0, ?, ?, ?)
|
|
1385
|
+
this.db.prepare(`
|
|
1386
|
+
INSERT INTO episodes (id, agent_id, label, embedding, engram_count, start_time, end_time, created_at)
|
|
1387
|
+
VALUES (?, ?, ?, ?, 0, ?, ?, ?)
|
|
1336
1388
|
`).run(id, input.agentId, input.label, embeddingBlob, now, now, now);
|
|
1337
1389
|
return this.getEpisode(id);
|
|
1338
1390
|
}
|
|
@@ -1347,19 +1399,19 @@ export class EngramStore {
|
|
|
1347
1399
|
getActiveEpisode(agentId, windowMs = 3600_000) {
|
|
1348
1400
|
// Find most recent episode that ended within the time window
|
|
1349
1401
|
const cutoff = new Date(Date.now() - windowMs).toISOString();
|
|
1350
|
-
const row = this.db.prepare(`
|
|
1351
|
-
SELECT * FROM episodes WHERE agent_id = ? AND end_time > ?
|
|
1352
|
-
ORDER BY end_time DESC LIMIT 1
|
|
1402
|
+
const row = this.db.prepare(`
|
|
1403
|
+
SELECT * FROM episodes WHERE agent_id = ? AND end_time > ?
|
|
1404
|
+
ORDER BY end_time DESC LIMIT 1
|
|
1353
1405
|
`).get(agentId, cutoff);
|
|
1354
1406
|
return row ? this.rowToEpisode(row) : null;
|
|
1355
1407
|
}
|
|
1356
1408
|
addEngramToEpisode(engramId, episodeId) {
|
|
1357
1409
|
this.db.prepare('UPDATE engrams SET episode_id = ? WHERE id = ?').run(episodeId, engramId);
|
|
1358
|
-
this.db.prepare(`
|
|
1359
|
-
UPDATE episodes SET
|
|
1360
|
-
engram_count = engram_count + 1,
|
|
1361
|
-
end_time = MAX(end_time, ?)
|
|
1362
|
-
WHERE id = ?
|
|
1410
|
+
this.db.prepare(`
|
|
1411
|
+
UPDATE episodes SET
|
|
1412
|
+
engram_count = engram_count + 1,
|
|
1413
|
+
end_time = MAX(end_time, ?)
|
|
1414
|
+
WHERE id = ?
|
|
1363
1415
|
`).run(new Date().toISOString(), episodeId);
|
|
1364
1416
|
}
|
|
1365
1417
|
getEngramsByEpisode(episodeId) {
|
|
@@ -1410,49 +1462,49 @@ export class EngramStore {
|
|
|
1410
1462
|
// --- Checkpointing ---
|
|
1411
1463
|
updateAutoCheckpointWrite(agentId, engramId) {
|
|
1412
1464
|
const now = new Date().toISOString();
|
|
1413
|
-
this.db.prepare(`
|
|
1414
|
-
INSERT INTO conscious_state (agent_id, last_write_id, last_activity_at, write_count_since_consolidation, updated_at)
|
|
1415
|
-
VALUES (?, ?, ?, 1, ?)
|
|
1416
|
-
ON CONFLICT(agent_id) DO UPDATE SET
|
|
1417
|
-
last_write_id = excluded.last_write_id,
|
|
1418
|
-
last_activity_at = excluded.last_activity_at,
|
|
1419
|
-
write_count_since_consolidation = write_count_since_consolidation + 1,
|
|
1420
|
-
updated_at = excluded.updated_at
|
|
1465
|
+
this.db.prepare(`
|
|
1466
|
+
INSERT INTO conscious_state (agent_id, last_write_id, last_activity_at, write_count_since_consolidation, updated_at)
|
|
1467
|
+
VALUES (?, ?, ?, 1, ?)
|
|
1468
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
1469
|
+
last_write_id = excluded.last_write_id,
|
|
1470
|
+
last_activity_at = excluded.last_activity_at,
|
|
1471
|
+
write_count_since_consolidation = write_count_since_consolidation + 1,
|
|
1472
|
+
updated_at = excluded.updated_at
|
|
1421
1473
|
`).run(agentId, engramId, now, now);
|
|
1422
1474
|
}
|
|
1423
1475
|
updateAutoCheckpointRecall(agentId, context, engramIds) {
|
|
1424
1476
|
const now = new Date().toISOString();
|
|
1425
|
-
this.db.prepare(`
|
|
1426
|
-
INSERT INTO conscious_state (agent_id, last_recall_context, last_recall_ids, last_activity_at, recall_count_since_consolidation, updated_at)
|
|
1427
|
-
VALUES (?, ?, ?, ?, 1, ?)
|
|
1428
|
-
ON CONFLICT(agent_id) DO UPDATE SET
|
|
1429
|
-
last_recall_context = excluded.last_recall_context,
|
|
1430
|
-
last_recall_ids = excluded.last_recall_ids,
|
|
1431
|
-
last_activity_at = excluded.last_activity_at,
|
|
1432
|
-
recall_count_since_consolidation = recall_count_since_consolidation + 1,
|
|
1433
|
-
updated_at = excluded.updated_at
|
|
1477
|
+
this.db.prepare(`
|
|
1478
|
+
INSERT INTO conscious_state (agent_id, last_recall_context, last_recall_ids, last_activity_at, recall_count_since_consolidation, updated_at)
|
|
1479
|
+
VALUES (?, ?, ?, ?, 1, ?)
|
|
1480
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
1481
|
+
last_recall_context = excluded.last_recall_context,
|
|
1482
|
+
last_recall_ids = excluded.last_recall_ids,
|
|
1483
|
+
last_activity_at = excluded.last_activity_at,
|
|
1484
|
+
recall_count_since_consolidation = recall_count_since_consolidation + 1,
|
|
1485
|
+
updated_at = excluded.updated_at
|
|
1434
1486
|
`).run(agentId, context, JSON.stringify(engramIds), now, now);
|
|
1435
1487
|
}
|
|
1436
1488
|
touchActivity(agentId) {
|
|
1437
1489
|
const now = new Date().toISOString();
|
|
1438
|
-
this.db.prepare(`
|
|
1439
|
-
INSERT INTO conscious_state (agent_id, last_activity_at, updated_at)
|
|
1440
|
-
VALUES (?, ?, ?)
|
|
1441
|
-
ON CONFLICT(agent_id) DO UPDATE SET
|
|
1442
|
-
last_activity_at = excluded.last_activity_at,
|
|
1443
|
-
updated_at = excluded.updated_at
|
|
1490
|
+
this.db.prepare(`
|
|
1491
|
+
INSERT INTO conscious_state (agent_id, last_activity_at, updated_at)
|
|
1492
|
+
VALUES (?, ?, ?)
|
|
1493
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
1494
|
+
last_activity_at = excluded.last_activity_at,
|
|
1495
|
+
updated_at = excluded.updated_at
|
|
1444
1496
|
`).run(agentId, now, now);
|
|
1445
1497
|
}
|
|
1446
1498
|
saveCheckpoint(agentId, state) {
|
|
1447
1499
|
const now = new Date().toISOString();
|
|
1448
|
-
this.db.prepare(`
|
|
1449
|
-
INSERT INTO conscious_state (agent_id, execution_state, checkpoint_at, last_activity_at, updated_at)
|
|
1450
|
-
VALUES (?, ?, ?, ?, ?)
|
|
1451
|
-
ON CONFLICT(agent_id) DO UPDATE SET
|
|
1452
|
-
execution_state = excluded.execution_state,
|
|
1453
|
-
checkpoint_at = excluded.checkpoint_at,
|
|
1454
|
-
last_activity_at = excluded.last_activity_at,
|
|
1455
|
-
updated_at = excluded.updated_at
|
|
1500
|
+
this.db.prepare(`
|
|
1501
|
+
INSERT INTO conscious_state (agent_id, execution_state, checkpoint_at, last_activity_at, updated_at)
|
|
1502
|
+
VALUES (?, ?, ?, ?, ?)
|
|
1503
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
1504
|
+
execution_state = excluded.execution_state,
|
|
1505
|
+
checkpoint_at = excluded.checkpoint_at,
|
|
1506
|
+
last_activity_at = excluded.last_activity_at,
|
|
1507
|
+
updated_at = excluded.updated_at
|
|
1456
1508
|
`).run(agentId, JSON.stringify(state), now, now, now);
|
|
1457
1509
|
}
|
|
1458
1510
|
/** Record entity mentions for an engram (D9 inverted index; idempotent). */
|
|
@@ -1469,10 +1521,10 @@ export class EngramStore {
|
|
|
1469
1521
|
const t = term.toLowerCase().replace(/[%_]/g, '');
|
|
1470
1522
|
if (!t)
|
|
1471
1523
|
return [];
|
|
1472
|
-
const rows = this.db.prepare(`
|
|
1473
|
-
SELECT DISTINCT entity FROM (
|
|
1474
|
-
SELECT entity FROM entity_mentions WHERE entity LIKE ?
|
|
1475
|
-
UNION SELECT entity FROM entity_aliases WHERE alias LIKE ?
|
|
1524
|
+
const rows = this.db.prepare(`
|
|
1525
|
+
SELECT DISTINCT entity FROM (
|
|
1526
|
+
SELECT entity FROM entity_mentions WHERE entity LIKE ?
|
|
1527
|
+
UNION SELECT entity FROM entity_aliases WHERE alias LIKE ?
|
|
1476
1528
|
) LIMIT ?`).all(`%${t}%`, `%${t}%`, limit);
|
|
1477
1529
|
return rows.map(r => r.entity);
|
|
1478
1530
|
}
|
|
@@ -1514,20 +1566,20 @@ export class EngramStore {
|
|
|
1514
1566
|
markConsolidation(agentId, mini) {
|
|
1515
1567
|
const now = new Date().toISOString();
|
|
1516
1568
|
if (mini) {
|
|
1517
|
-
this.db.prepare(`
|
|
1518
|
-
UPDATE conscious_state SET last_mini_consolidation_at = ?, updated_at = ? WHERE agent_id = ?
|
|
1569
|
+
this.db.prepare(`
|
|
1570
|
+
UPDATE conscious_state SET last_mini_consolidation_at = ?, updated_at = ? WHERE agent_id = ?
|
|
1519
1571
|
`).run(now, now, agentId);
|
|
1520
1572
|
}
|
|
1521
1573
|
else {
|
|
1522
|
-
this.db.prepare(`
|
|
1523
|
-
UPDATE conscious_state SET
|
|
1524
|
-
last_consolidation_at = ?,
|
|
1525
|
-
last_mini_consolidation_at = ?,
|
|
1526
|
-
write_count_since_consolidation = 0,
|
|
1527
|
-
recall_count_since_consolidation = 0,
|
|
1528
|
-
consolidation_cycle_count = consolidation_cycle_count + 1,
|
|
1529
|
-
updated_at = ?
|
|
1530
|
-
WHERE agent_id = ?
|
|
1574
|
+
this.db.prepare(`
|
|
1575
|
+
UPDATE conscious_state SET
|
|
1576
|
+
last_consolidation_at = ?,
|
|
1577
|
+
last_mini_consolidation_at = ?,
|
|
1578
|
+
write_count_since_consolidation = 0,
|
|
1579
|
+
recall_count_since_consolidation = 0,
|
|
1580
|
+
consolidation_cycle_count = consolidation_cycle_count + 1,
|
|
1581
|
+
updated_at = ?
|
|
1582
|
+
WHERE agent_id = ?
|
|
1531
1583
|
`).run(now, now, now, agentId);
|
|
1532
1584
|
}
|
|
1533
1585
|
}
|
|
@@ -1598,10 +1650,10 @@ export class EngramStore {
|
|
|
1598
1650
|
* sequence are excluded (they have no story-time anchor).
|
|
1599
1651
|
*/
|
|
1600
1652
|
getLatestByTag(opts) {
|
|
1601
|
-
let sql = `SELECT * FROM engrams
|
|
1602
|
-
WHERE agent_id = ?
|
|
1603
|
-
AND retracted = ?
|
|
1604
|
-
AND stage = 'active'
|
|
1653
|
+
let sql = `SELECT * FROM engrams
|
|
1654
|
+
WHERE agent_id = ?
|
|
1655
|
+
AND retracted = ?
|
|
1656
|
+
AND stage = 'active'
|
|
1605
1657
|
AND tags LIKE ?`;
|
|
1606
1658
|
const params = [
|
|
1607
1659
|
opts.agentId,
|
|
@@ -1641,10 +1693,10 @@ export class EngramStore {
|
|
|
1641
1693
|
*/
|
|
1642
1694
|
getTopBy(opts) {
|
|
1643
1695
|
// Build the base query inline (mirrors search() but adds the topBy sort).
|
|
1644
|
-
let sql = `SELECT * FROM engrams
|
|
1645
|
-
WHERE agent_id = ?
|
|
1646
|
-
AND retracted = ?
|
|
1647
|
-
AND stage = 'active'
|
|
1696
|
+
let sql = `SELECT * FROM engrams
|
|
1697
|
+
WHERE agent_id = ?
|
|
1698
|
+
AND retracted = ?
|
|
1699
|
+
AND stage = 'active'
|
|
1648
1700
|
AND tags LIKE ?`;
|
|
1649
1701
|
const params = [
|
|
1650
1702
|
opts.agentId,
|
|
@@ -1712,11 +1764,11 @@ export class EngramStore {
|
|
|
1712
1764
|
// Find candidate referencing engrams. LIKE-filter first to narrow,
|
|
1713
1765
|
// then JSON.parse each candidate's references_json to verify the
|
|
1714
1766
|
// matchEngramId and type. Cheap enough at NovelForge's scale.
|
|
1715
|
-
const candidates = this.db.prepare(`SELECT id, references_json, created_at FROM engrams
|
|
1716
|
-
WHERE agent_id = ?
|
|
1717
|
-
AND retracted = 0
|
|
1718
|
-
AND stage = 'active'
|
|
1719
|
-
AND references_json IS NOT NULL
|
|
1767
|
+
const candidates = this.db.prepare(`SELECT id, references_json, created_at FROM engrams
|
|
1768
|
+
WHERE agent_id = ?
|
|
1769
|
+
AND retracted = 0
|
|
1770
|
+
AND stage = 'active'
|
|
1771
|
+
AND references_json IS NOT NULL
|
|
1720
1772
|
AND references_json LIKE ?`).all(target.agentId, `%"${targetEngramId}"%`);
|
|
1721
1773
|
const terminalTypes = new Set(['resolves', 'subverts', 'abandons']);
|
|
1722
1774
|
const resolvingEvents = [];
|