agent-working-memory 0.9.0 → 0.9.1

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.
@@ -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
+ }
package/src/cli.ts CHANGED
@@ -444,9 +444,13 @@ async function importMemories() {
444
444
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
445
445
  `);
446
446
 
447
+ // NOTE: associations.last_activated is NOT NULL on the engrams DB (storage/sqlite.ts),
448
+ // so importing into an existing store fails if it's omitted — and because import wraps
449
+ // memories+associations in ONE transaction, that rolls back the memories too (silent
450
+ // "empty store"). Set it alongside created_at. (migrate/merge paths already do this.)
447
451
  const insertAssoc = db.prepare(`
448
- INSERT INTO associations (id, from_engram_id, to_engram_id, weight, type, activation_count, created_at)
449
- VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
452
+ INSERT INTO associations (id, from_engram_id, to_engram_id, weight, type, activation_count, created_at, last_activated)
453
+ VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))
450
454
  `);
451
455
 
452
456
  const importTx = db.transaction(() => {