agent-working-memory 0.8.8 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +165 -46
  2. package/dist/api/routes.js +7 -7
  3. package/dist/cli/migrate.js +29 -29
  4. package/dist/cli.js +104 -104
  5. package/dist/coordination/circuit-breaker.js +23 -23
  6. package/dist/core/write-pipeline.d.ts.map +1 -1
  7. package/dist/core/write-pipeline.js +17 -0
  8. package/dist/core/write-pipeline.js.map +1 -1
  9. package/dist/engine/activation.d.ts +28 -0
  10. package/dist/engine/activation.d.ts.map +1 -1
  11. package/dist/engine/activation.js +341 -11
  12. package/dist/engine/activation.js.map +1 -1
  13. package/dist/engine/connections.d.ts +12 -0
  14. package/dist/engine/connections.d.ts.map +1 -1
  15. package/dist/engine/connections.js +95 -0
  16. package/dist/engine/connections.js.map +1 -1
  17. package/dist/mcp.js +90 -90
  18. package/dist/storage/pglite-schema.js +143 -143
  19. package/dist/storage/pglite.js +138 -138
  20. package/dist/types/engram.d.ts +1 -0
  21. package/dist/types/engram.d.ts.map +1 -1
  22. package/package.json +1 -1
  23. package/src/api/index.ts +3 -3
  24. package/src/cli/migrate.ts +307 -307
  25. package/src/coordination/circuit-breaker.ts +83 -83
  26. package/src/coordination/failure-modes.ts +50 -50
  27. package/src/core/decay.ts +63 -63
  28. package/src/core/embeddings.ts +110 -110
  29. package/src/core/index.ts +5 -5
  30. package/src/core/logger.ts +36 -36
  31. package/src/core/ml-worker-entry.ts +194 -194
  32. package/src/core/ml-worker.ts +281 -281
  33. package/src/core/query-expander.ts +122 -122
  34. package/src/core/reranker.ts +119 -119
  35. package/src/core/write-pipeline.ts +15 -0
  36. package/src/engine/activation.ts +328 -11
  37. package/src/engine/confidence.ts +120 -120
  38. package/src/engine/connections.ts +94 -0
  39. package/src/engine/consolidation-scheduler.ts +242 -242
  40. package/src/engine/eval.ts +102 -102
  41. package/src/engine/eviction.ts +101 -101
  42. package/src/engine/index.ts +8 -8
  43. package/src/engine/retraction.ts +366 -366
  44. package/src/engine/staging.ts +74 -74
  45. package/src/storage/factory.ts +147 -147
  46. package/src/storage/index.ts +3 -3
  47. package/src/storage/pglite-schema.ts +166 -166
  48. package/src/storage/pglite.ts +1363 -1363
  49. package/src/storage/store.ts +80 -80
  50. package/src/types/agent.ts +67 -67
  51. package/src/types/checkpoint.ts +46 -46
  52. package/src/types/engram.ts +1 -0
  53. package/src/types/eval.ts +100 -100
  54. package/src/types/index.ts +6 -6
@@ -1,307 +1,307 @@
1
- // Copyright 2026 Robert Winter / Complete Ideas
2
- // SPDX-License-Identifier: Apache-2.0
3
- /**
4
- * `awm migrate` — Convert a SQLite AWM database to a PGlite store.
5
- *
6
- * Usage:
7
- * awm migrate --from <sqlite.db> --to <pglite-dir> [--dry-run]
8
- *
9
- * Reads each table from the source SQLite database and writes it to a fresh
10
- * PGlite directory. Embedding BLOBs are converted from Float32Array bytes to
11
- * pgvector text format (`[v1,v2,...]`). Integer booleans (0/1) become
12
- * Postgres BOOLEAN. Timestamps are passed through as ISO strings.
13
- *
14
- * Tables migrated:
15
- * - engrams + their derived FTS index (regenerated by PGlite trigger)
16
- * - associations
17
- * - agents
18
- * - activation_events
19
- * - staging_events
20
- * - retrieval_feedback
21
- * - episodes
22
- * - conscious_state
23
- */
24
-
25
- import Database from 'better-sqlite3';
26
- import { PGlite } from '@electric-sql/pglite';
27
- import { vector } from '@electric-sql/pglite/vector';
28
- import { PGLITE_SCHEMA_DDL } from '../storage/pglite-schema.js';
29
- import { existsSync, mkdirSync } from 'node:fs';
30
-
31
- function bufferToFloat32Array(buf: Buffer): Float32Array {
32
- const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
33
- return new Float32Array(ab);
34
- }
35
-
36
- function vectorLiteral(values: number[] | null): string | null {
37
- if (!values || values.length === 0) return null;
38
- return '[' + values.join(',') + ']';
39
- }
40
-
41
- function intToBool(v: unknown): boolean {
42
- return v === 1 || v === true || v === '1' || v === 'true';
43
- }
44
-
45
- interface MigrateOptions {
46
- from: string;
47
- to: string;
48
- dryRun?: boolean;
49
- batchSize?: number;
50
- verbose?: boolean;
51
- }
52
-
53
- interface MigrateStats {
54
- engrams: number;
55
- associations: number;
56
- agents: number;
57
- activationEvents: number;
58
- stagingEvents: number;
59
- retrievalFeedback: number;
60
- episodes: number;
61
- consciousState: number;
62
- }
63
-
64
- export async function migrate(opts: MigrateOptions): Promise<MigrateStats> {
65
- const { from, to, dryRun = false, batchSize = 500, verbose = false } = opts;
66
-
67
- if (!existsSync(from)) {
68
- throw new Error(`Source SQLite database not found: ${from}`);
69
- }
70
-
71
- if (!dryRun && !existsSync(to)) {
72
- mkdirSync(to, { recursive: true });
73
- }
74
-
75
- const src = new Database(from, { readonly: true });
76
- const dst = dryRun ? null : await PGlite.create(to, { extensions: { vector } });
77
-
78
- if (dst) {
79
- await dst.exec(PGLITE_SCHEMA_DDL);
80
- }
81
-
82
- const stats: MigrateStats = {
83
- engrams: 0,
84
- associations: 0,
85
- agents: 0,
86
- activationEvents: 0,
87
- stagingEvents: 0,
88
- retrievalFeedback: 0,
89
- episodes: 0,
90
- consciousState: 0,
91
- };
92
-
93
- // ── engrams ─────────────────────────────────────────────
94
- const engramRows = src.prepare(`SELECT * FROM engrams`).all() as any[];
95
- if (verbose) console.log(`engrams: ${engramRows.length} rows to migrate`);
96
- if (dst && engramRows.length > 0) {
97
- for (let i = 0; i < engramRows.length; i += batchSize) {
98
- const batch = engramRows.slice(i, i + batchSize);
99
- for (const r of batch) {
100
- const embedding = r.embedding
101
- ? vectorLiteral(Array.from(bufferToFloat32Array(r.embedding)))
102
- : null;
103
- await dst.query(
104
- `INSERT INTO engrams (
105
- id, agent_id, concept, content, embedding, embedding_model,
106
- confidence, salience, access_count, last_accessed, created_at,
107
- salience_features, reason_codes, stage, ttl,
108
- retracted, retracted_by, retracted_at, tags, memory_type,
109
- memory_class, superseded_by, supersedes, episode_id,
110
- task_status, task_priority, blocked_by, sequence, references_json
111
- ) VALUES (
112
- $1, $2, $3, $4, $5::vector, $6, $7, $8, $9, $10, $11,
113
- $12, $13, $14, $15, $16, $17, $18, $19, $20,
114
- $21, $22, $23, $24, $25, $26, $27, $28, $29
115
- )`,
116
- [
117
- r.id, r.agent_id, r.concept, r.content, embedding, r.embedding_model,
118
- r.confidence, r.salience, r.access_count, r.last_accessed, r.created_at,
119
- r.salience_features ?? '{}', r.reason_codes ?? '[]',
120
- r.stage ?? 'active', r.ttl,
121
- intToBool(r.retracted), r.retracted_by, r.retracted_at,
122
- r.tags ?? '[]', r.memory_type ?? 'unclassified',
123
- r.memory_class ?? 'working', r.superseded_by, r.supersedes, r.episode_id,
124
- r.task_status, r.task_priority, r.blocked_by, r.sequence, r.references_json,
125
- ],
126
- );
127
- stats.engrams++;
128
- }
129
- if (verbose) process.stdout.write(` ${Math.min(i + batchSize, engramRows.length)}/${engramRows.length}\r`);
130
- }
131
- if (verbose) console.log(` engrams: done (${stats.engrams})`);
132
- } else if (dryRun) {
133
- stats.engrams = engramRows.length;
134
- }
135
-
136
- // ── associations ────────────────────────────────────────
137
- const assocRows = src.prepare(`SELECT * FROM associations`).all() as any[];
138
- if (verbose) console.log(`associations: ${assocRows.length} rows`);
139
- if (dst && assocRows.length > 0) {
140
- for (let i = 0; i < assocRows.length; i += batchSize) {
141
- const batch = assocRows.slice(i, i + batchSize);
142
- for (const r of batch) {
143
- await dst.query(
144
- `INSERT INTO associations (
145
- id, from_engram_id, to_engram_id, weight, confidence, type,
146
- activation_count, created_at, last_activated
147
- ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)`,
148
- [
149
- r.id, r.from_engram_id, r.to_engram_id, r.weight, r.confidence,
150
- r.type ?? 'hebbian', r.activation_count ?? 0,
151
- r.created_at, r.last_activated,
152
- ],
153
- );
154
- stats.associations++;
155
- }
156
- }
157
- } else if (dryRun) {
158
- stats.associations = assocRows.length;
159
- }
160
-
161
- // ── agents ──────────────────────────────────────────────
162
- const agentRows = src.prepare(`SELECT * FROM agents`).all() as any[];
163
- if (verbose) console.log(`agents: ${agentRows.length} rows`);
164
- if (dst && agentRows.length > 0) {
165
- for (const r of agentRows) {
166
- await dst.query(
167
- `INSERT INTO agents (id, name, created_at, config) VALUES ($1,$2,$3,$4)
168
- ON CONFLICT (id) DO NOTHING`,
169
- [r.id, r.name, r.created_at, r.config ?? '{}'],
170
- );
171
- stats.agents++;
172
- }
173
- } else if (dryRun) {
174
- stats.agents = agentRows.length;
175
- }
176
-
177
- // ── activation_events ───────────────────────────────────
178
- const aeRows = src.prepare(`SELECT * FROM activation_events`).all() as any[];
179
- if (verbose) console.log(`activation_events: ${aeRows.length} rows`);
180
- if (dst && aeRows.length > 0) {
181
- for (let i = 0; i < aeRows.length; i += batchSize) {
182
- for (const r of aeRows.slice(i, i + batchSize)) {
183
- await dst.query(
184
- `INSERT INTO activation_events
185
- (id, agent_id, timestamp, context, results_returned, top_score, latency_ms, engram_ids)
186
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8)`,
187
- [r.id, r.agent_id, r.timestamp, r.context, r.results_returned,
188
- r.top_score, r.latency_ms, r.engram_ids ?? '[]'],
189
- );
190
- stats.activationEvents++;
191
- }
192
- }
193
- } else if (dryRun) {
194
- stats.activationEvents = aeRows.length;
195
- }
196
-
197
- // ── staging_events ──────────────────────────────────────
198
- const seRows = src.prepare(`SELECT * FROM staging_events`).all() as any[];
199
- if (verbose) console.log(`staging_events: ${seRows.length} rows`);
200
- if (dst && seRows.length > 0) {
201
- for (const r of seRows) {
202
- await dst.query(
203
- `INSERT INTO staging_events (engram_id, agent_id, action, resonance_score, timestamp, age_ms)
204
- VALUES ($1,$2,$3,$4,$5,$6)`,
205
- [r.engram_id, r.agent_id, r.action, r.resonance_score, r.timestamp, r.age_ms],
206
- );
207
- stats.stagingEvents++;
208
- }
209
- } else if (dryRun) {
210
- stats.stagingEvents = seRows.length;
211
- }
212
-
213
- // ── retrieval_feedback ──────────────────────────────────
214
- const rfRows = src.prepare(`SELECT * FROM retrieval_feedback`).all() as any[];
215
- if (verbose) console.log(`retrieval_feedback: ${rfRows.length} rows`);
216
- if (dst && rfRows.length > 0) {
217
- for (const r of rfRows) {
218
- await dst.query(
219
- `INSERT INTO retrieval_feedback
220
- (id, activation_event_id, engram_id, useful, context, timestamp)
221
- VALUES ($1,$2,$3,$4,$5,$6)`,
222
- [r.id, r.activation_event_id, r.engram_id, intToBool(r.useful), r.context, r.timestamp],
223
- );
224
- stats.retrievalFeedback++;
225
- }
226
- } else if (dryRun) {
227
- stats.retrievalFeedback = rfRows.length;
228
- }
229
-
230
- // ── episodes ────────────────────────────────────────────
231
- // The episodes table may not exist in older SQLite databases.
232
- try {
233
- const epRows = src.prepare(`SELECT * FROM episodes`).all() as any[];
234
- if (verbose) console.log(`episodes: ${epRows.length} rows`);
235
- if (dst && epRows.length > 0) {
236
- for (const r of epRows) {
237
- const embedding = r.embedding
238
- ? vectorLiteral(Array.from(bufferToFloat32Array(r.embedding)))
239
- : null;
240
- await dst.query(
241
- `INSERT INTO episodes
242
- (id, agent_id, label, embedding, engram_count, start_time, end_time, created_at)
243
- VALUES ($1,$2,$3,$4::vector,$5,$6,$7,$8)`,
244
- [r.id, r.agent_id, r.label, embedding, r.engram_count ?? 0,
245
- r.start_time, r.end_time, r.created_at],
246
- );
247
- stats.episodes++;
248
- }
249
- } else if (dryRun) {
250
- stats.episodes = epRows.length;
251
- }
252
- } catch {
253
- if (verbose) console.log(`episodes: table not present in source (skipping)`);
254
- }
255
-
256
- // ── conscious_state ─────────────────────────────────────
257
- try {
258
- const csRows = src.prepare(`SELECT * FROM conscious_state`).all() as any[];
259
- if (verbose) console.log(`conscious_state: ${csRows.length} rows`);
260
- if (dst && csRows.length > 0) {
261
- for (const r of csRows) {
262
- await dst.query(
263
- `INSERT INTO conscious_state (
264
- agent_id, last_write_id, last_recall_context, last_recall_ids,
265
- last_activity_at, write_count_since_consolidation,
266
- recall_count_since_consolidation, execution_state, checkpoint_at,
267
- last_consolidation_at, last_mini_consolidation_at,
268
- consolidation_cycle_count, updated_at
269
- ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)
270
- ON CONFLICT (agent_id) DO NOTHING`,
271
- [
272
- r.agent_id, r.last_write_id, r.last_recall_context,
273
- r.last_recall_ids ?? '[]', r.last_activity_at,
274
- r.write_count_since_consolidation ?? 0,
275
- r.recall_count_since_consolidation ?? 0,
276
- r.execution_state, r.checkpoint_at,
277
- r.last_consolidation_at, r.last_mini_consolidation_at,
278
- r.consolidation_cycle_count ?? 0, r.updated_at,
279
- ],
280
- );
281
- stats.consciousState++;
282
- }
283
- } else if (dryRun) {
284
- stats.consciousState = csRows.length;
285
- }
286
- } catch {
287
- if (verbose) console.log(`conscious_state: table not present in source (skipping)`);
288
- }
289
-
290
- src.close();
291
- if (dst) await dst.close();
292
-
293
- return stats;
294
- }
295
-
296
- export function printStats(stats: MigrateStats, dryRun: boolean): void {
297
- const verb = dryRun ? 'would migrate' : 'migrated';
298
- console.log(`\nMigration ${dryRun ? 'plan' : 'complete'}:`);
299
- console.log(` ${verb} ${stats.engrams} engrams`);
300
- console.log(` ${verb} ${stats.associations} associations`);
301
- console.log(` ${verb} ${stats.agents} agents`);
302
- console.log(` ${verb} ${stats.activationEvents} activation events`);
303
- console.log(` ${verb} ${stats.stagingEvents} staging events`);
304
- console.log(` ${verb} ${stats.retrievalFeedback} retrieval feedback records`);
305
- console.log(` ${verb} ${stats.episodes} episodes`);
306
- console.log(` ${verb} ${stats.consciousState} conscious_state rows`);
307
- }
1
+ // Copyright 2026 Robert Winter / Complete Ideas
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * `awm migrate` — Convert a SQLite AWM database to a PGlite store.
5
+ *
6
+ * Usage:
7
+ * awm migrate --from <sqlite.db> --to <pglite-dir> [--dry-run]
8
+ *
9
+ * Reads each table from the source SQLite database and writes it to a fresh
10
+ * PGlite directory. Embedding BLOBs are converted from Float32Array bytes to
11
+ * pgvector text format (`[v1,v2,...]`). Integer booleans (0/1) become
12
+ * Postgres BOOLEAN. Timestamps are passed through as ISO strings.
13
+ *
14
+ * Tables migrated:
15
+ * - engrams + their derived FTS index (regenerated by PGlite trigger)
16
+ * - associations
17
+ * - agents
18
+ * - activation_events
19
+ * - staging_events
20
+ * - retrieval_feedback
21
+ * - episodes
22
+ * - conscious_state
23
+ */
24
+
25
+ import Database from 'better-sqlite3';
26
+ import { PGlite } from '@electric-sql/pglite';
27
+ import { vector } from '@electric-sql/pglite/vector';
28
+ import { PGLITE_SCHEMA_DDL } from '../storage/pglite-schema.js';
29
+ import { existsSync, mkdirSync } from 'node:fs';
30
+
31
+ function bufferToFloat32Array(buf: Buffer): Float32Array {
32
+ const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
33
+ return new Float32Array(ab);
34
+ }
35
+
36
+ function vectorLiteral(values: number[] | null): string | null {
37
+ if (!values || values.length === 0) return null;
38
+ return '[' + values.join(',') + ']';
39
+ }
40
+
41
+ function intToBool(v: unknown): boolean {
42
+ return v === 1 || v === true || v === '1' || v === 'true';
43
+ }
44
+
45
+ interface MigrateOptions {
46
+ from: string;
47
+ to: string;
48
+ dryRun?: boolean;
49
+ batchSize?: number;
50
+ verbose?: boolean;
51
+ }
52
+
53
+ interface MigrateStats {
54
+ engrams: number;
55
+ associations: number;
56
+ agents: number;
57
+ activationEvents: number;
58
+ stagingEvents: number;
59
+ retrievalFeedback: number;
60
+ episodes: number;
61
+ consciousState: number;
62
+ }
63
+
64
+ export async function migrate(opts: MigrateOptions): Promise<MigrateStats> {
65
+ const { from, to, dryRun = false, batchSize = 500, verbose = false } = opts;
66
+
67
+ if (!existsSync(from)) {
68
+ throw new Error(`Source SQLite database not found: ${from}`);
69
+ }
70
+
71
+ if (!dryRun && !existsSync(to)) {
72
+ mkdirSync(to, { recursive: true });
73
+ }
74
+
75
+ const src = new Database(from, { readonly: true });
76
+ const dst = dryRun ? null : await PGlite.create(to, { extensions: { vector } });
77
+
78
+ if (dst) {
79
+ await dst.exec(PGLITE_SCHEMA_DDL);
80
+ }
81
+
82
+ const stats: MigrateStats = {
83
+ engrams: 0,
84
+ associations: 0,
85
+ agents: 0,
86
+ activationEvents: 0,
87
+ stagingEvents: 0,
88
+ retrievalFeedback: 0,
89
+ episodes: 0,
90
+ consciousState: 0,
91
+ };
92
+
93
+ // ── engrams ─────────────────────────────────────────────
94
+ const engramRows = src.prepare(`SELECT * FROM engrams`).all() as any[];
95
+ if (verbose) console.log(`engrams: ${engramRows.length} rows to migrate`);
96
+ if (dst && engramRows.length > 0) {
97
+ for (let i = 0; i < engramRows.length; i += batchSize) {
98
+ const batch = engramRows.slice(i, i + batchSize);
99
+ for (const r of batch) {
100
+ const embedding = r.embedding
101
+ ? vectorLiteral(Array.from(bufferToFloat32Array(r.embedding)))
102
+ : null;
103
+ await dst.query(
104
+ `INSERT INTO engrams (
105
+ id, agent_id, concept, content, embedding, embedding_model,
106
+ confidence, salience, access_count, last_accessed, created_at,
107
+ salience_features, reason_codes, stage, ttl,
108
+ retracted, retracted_by, retracted_at, tags, memory_type,
109
+ memory_class, superseded_by, supersedes, episode_id,
110
+ task_status, task_priority, blocked_by, sequence, references_json
111
+ ) VALUES (
112
+ $1, $2, $3, $4, $5::vector, $6, $7, $8, $9, $10, $11,
113
+ $12, $13, $14, $15, $16, $17, $18, $19, $20,
114
+ $21, $22, $23, $24, $25, $26, $27, $28, $29
115
+ )`,
116
+ [
117
+ r.id, r.agent_id, r.concept, r.content, embedding, r.embedding_model,
118
+ r.confidence, r.salience, r.access_count, r.last_accessed, r.created_at,
119
+ r.salience_features ?? '{}', r.reason_codes ?? '[]',
120
+ r.stage ?? 'active', r.ttl,
121
+ intToBool(r.retracted), r.retracted_by, r.retracted_at,
122
+ r.tags ?? '[]', r.memory_type ?? 'unclassified',
123
+ r.memory_class ?? 'working', r.superseded_by, r.supersedes, r.episode_id,
124
+ r.task_status, r.task_priority, r.blocked_by, r.sequence, r.references_json,
125
+ ],
126
+ );
127
+ stats.engrams++;
128
+ }
129
+ if (verbose) process.stdout.write(` ${Math.min(i + batchSize, engramRows.length)}/${engramRows.length}\r`);
130
+ }
131
+ if (verbose) console.log(` engrams: done (${stats.engrams})`);
132
+ } else if (dryRun) {
133
+ stats.engrams = engramRows.length;
134
+ }
135
+
136
+ // ── associations ────────────────────────────────────────
137
+ const assocRows = src.prepare(`SELECT * FROM associations`).all() as any[];
138
+ if (verbose) console.log(`associations: ${assocRows.length} rows`);
139
+ if (dst && assocRows.length > 0) {
140
+ for (let i = 0; i < assocRows.length; i += batchSize) {
141
+ const batch = assocRows.slice(i, i + batchSize);
142
+ for (const r of batch) {
143
+ await dst.query(
144
+ `INSERT INTO associations (
145
+ id, from_engram_id, to_engram_id, weight, confidence, type,
146
+ activation_count, created_at, last_activated
147
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)`,
148
+ [
149
+ r.id, r.from_engram_id, r.to_engram_id, r.weight, r.confidence,
150
+ r.type ?? 'hebbian', r.activation_count ?? 0,
151
+ r.created_at, r.last_activated,
152
+ ],
153
+ );
154
+ stats.associations++;
155
+ }
156
+ }
157
+ } else if (dryRun) {
158
+ stats.associations = assocRows.length;
159
+ }
160
+
161
+ // ── agents ──────────────────────────────────────────────
162
+ const agentRows = src.prepare(`SELECT * FROM agents`).all() as any[];
163
+ if (verbose) console.log(`agents: ${agentRows.length} rows`);
164
+ if (dst && agentRows.length > 0) {
165
+ for (const r of agentRows) {
166
+ await dst.query(
167
+ `INSERT INTO agents (id, name, created_at, config) VALUES ($1,$2,$3,$4)
168
+ ON CONFLICT (id) DO NOTHING`,
169
+ [r.id, r.name, r.created_at, r.config ?? '{}'],
170
+ );
171
+ stats.agents++;
172
+ }
173
+ } else if (dryRun) {
174
+ stats.agents = agentRows.length;
175
+ }
176
+
177
+ // ── activation_events ───────────────────────────────────
178
+ const aeRows = src.prepare(`SELECT * FROM activation_events`).all() as any[];
179
+ if (verbose) console.log(`activation_events: ${aeRows.length} rows`);
180
+ if (dst && aeRows.length > 0) {
181
+ for (let i = 0; i < aeRows.length; i += batchSize) {
182
+ for (const r of aeRows.slice(i, i + batchSize)) {
183
+ await dst.query(
184
+ `INSERT INTO activation_events
185
+ (id, agent_id, timestamp, context, results_returned, top_score, latency_ms, engram_ids)
186
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8)`,
187
+ [r.id, r.agent_id, r.timestamp, r.context, r.results_returned,
188
+ r.top_score, r.latency_ms, r.engram_ids ?? '[]'],
189
+ );
190
+ stats.activationEvents++;
191
+ }
192
+ }
193
+ } else if (dryRun) {
194
+ stats.activationEvents = aeRows.length;
195
+ }
196
+
197
+ // ── staging_events ──────────────────────────────────────
198
+ const seRows = src.prepare(`SELECT * FROM staging_events`).all() as any[];
199
+ if (verbose) console.log(`staging_events: ${seRows.length} rows`);
200
+ if (dst && seRows.length > 0) {
201
+ for (const r of seRows) {
202
+ await dst.query(
203
+ `INSERT INTO staging_events (engram_id, agent_id, action, resonance_score, timestamp, age_ms)
204
+ VALUES ($1,$2,$3,$4,$5,$6)`,
205
+ [r.engram_id, r.agent_id, r.action, r.resonance_score, r.timestamp, r.age_ms],
206
+ );
207
+ stats.stagingEvents++;
208
+ }
209
+ } else if (dryRun) {
210
+ stats.stagingEvents = seRows.length;
211
+ }
212
+
213
+ // ── retrieval_feedback ──────────────────────────────────
214
+ const rfRows = src.prepare(`SELECT * FROM retrieval_feedback`).all() as any[];
215
+ if (verbose) console.log(`retrieval_feedback: ${rfRows.length} rows`);
216
+ if (dst && rfRows.length > 0) {
217
+ for (const r of rfRows) {
218
+ await dst.query(
219
+ `INSERT INTO retrieval_feedback
220
+ (id, activation_event_id, engram_id, useful, context, timestamp)
221
+ VALUES ($1,$2,$3,$4,$5,$6)`,
222
+ [r.id, r.activation_event_id, r.engram_id, intToBool(r.useful), r.context, r.timestamp],
223
+ );
224
+ stats.retrievalFeedback++;
225
+ }
226
+ } else if (dryRun) {
227
+ stats.retrievalFeedback = rfRows.length;
228
+ }
229
+
230
+ // ── episodes ────────────────────────────────────────────
231
+ // The episodes table may not exist in older SQLite databases.
232
+ try {
233
+ const epRows = src.prepare(`SELECT * FROM episodes`).all() as any[];
234
+ if (verbose) console.log(`episodes: ${epRows.length} rows`);
235
+ if (dst && epRows.length > 0) {
236
+ for (const r of epRows) {
237
+ const embedding = r.embedding
238
+ ? vectorLiteral(Array.from(bufferToFloat32Array(r.embedding)))
239
+ : null;
240
+ await dst.query(
241
+ `INSERT INTO episodes
242
+ (id, agent_id, label, embedding, engram_count, start_time, end_time, created_at)
243
+ VALUES ($1,$2,$3,$4::vector,$5,$6,$7,$8)`,
244
+ [r.id, r.agent_id, r.label, embedding, r.engram_count ?? 0,
245
+ r.start_time, r.end_time, r.created_at],
246
+ );
247
+ stats.episodes++;
248
+ }
249
+ } else if (dryRun) {
250
+ stats.episodes = epRows.length;
251
+ }
252
+ } catch {
253
+ if (verbose) console.log(`episodes: table not present in source (skipping)`);
254
+ }
255
+
256
+ // ── conscious_state ─────────────────────────────────────
257
+ try {
258
+ const csRows = src.prepare(`SELECT * FROM conscious_state`).all() as any[];
259
+ if (verbose) console.log(`conscious_state: ${csRows.length} rows`);
260
+ if (dst && csRows.length > 0) {
261
+ for (const r of csRows) {
262
+ await dst.query(
263
+ `INSERT INTO conscious_state (
264
+ agent_id, last_write_id, last_recall_context, last_recall_ids,
265
+ last_activity_at, write_count_since_consolidation,
266
+ recall_count_since_consolidation, execution_state, checkpoint_at,
267
+ last_consolidation_at, last_mini_consolidation_at,
268
+ consolidation_cycle_count, updated_at
269
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)
270
+ ON CONFLICT (agent_id) DO NOTHING`,
271
+ [
272
+ r.agent_id, r.last_write_id, r.last_recall_context,
273
+ r.last_recall_ids ?? '[]', r.last_activity_at,
274
+ r.write_count_since_consolidation ?? 0,
275
+ r.recall_count_since_consolidation ?? 0,
276
+ r.execution_state, r.checkpoint_at,
277
+ r.last_consolidation_at, r.last_mini_consolidation_at,
278
+ r.consolidation_cycle_count ?? 0, r.updated_at,
279
+ ],
280
+ );
281
+ stats.consciousState++;
282
+ }
283
+ } else if (dryRun) {
284
+ stats.consciousState = csRows.length;
285
+ }
286
+ } catch {
287
+ if (verbose) console.log(`conscious_state: table not present in source (skipping)`);
288
+ }
289
+
290
+ src.close();
291
+ if (dst) await dst.close();
292
+
293
+ return stats;
294
+ }
295
+
296
+ export function printStats(stats: MigrateStats, dryRun: boolean): void {
297
+ const verb = dryRun ? 'would migrate' : 'migrated';
298
+ console.log(`\nMigration ${dryRun ? 'plan' : 'complete'}:`);
299
+ console.log(` ${verb} ${stats.engrams} engrams`);
300
+ console.log(` ${verb} ${stats.associations} associations`);
301
+ console.log(` ${verb} ${stats.agents} agents`);
302
+ console.log(` ${verb} ${stats.activationEvents} activation events`);
303
+ console.log(` ${verb} ${stats.stagingEvents} staging events`);
304
+ console.log(` ${verb} ${stats.retrievalFeedback} retrieval feedback records`);
305
+ console.log(` ${verb} ${stats.episodes} episodes`);
306
+ console.log(` ${verb} ${stats.consciousState} conscious_state rows`);
307
+ }