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.
Files changed (165) hide show
  1. package/README.md +8 -5
  2. package/dist/adapters/common.d.ts +34 -0
  3. package/dist/adapters/common.d.ts.map +1 -1
  4. package/dist/adapters/common.js +528 -446
  5. package/dist/adapters/common.js.map +1 -1
  6. package/dist/api/routes.d.ts.map +1 -1
  7. package/dist/api/routes.js +19 -0
  8. package/dist/api/routes.js.map +1 -1
  9. package/dist/cli/migrate.d.ts.map +1 -1
  10. package/dist/cli/migrate.js +31 -29
  11. package/dist/cli/migrate.js.map +1 -1
  12. package/dist/core/asked-value.d.ts +37 -0
  13. package/dist/core/asked-value.d.ts.map +1 -0
  14. package/dist/core/asked-value.js +77 -0
  15. package/dist/core/asked-value.js.map +1 -0
  16. package/dist/core/conflicts.d.ts +89 -0
  17. package/dist/core/conflicts.d.ts.map +1 -0
  18. package/dist/core/conflicts.js +267 -0
  19. package/dist/core/conflicts.js.map +1 -0
  20. package/dist/core/embeddings.d.ts +14 -2
  21. package/dist/core/embeddings.d.ts.map +1 -1
  22. package/dist/core/embeddings.js +32 -10
  23. package/dist/core/embeddings.js.map +1 -1
  24. package/dist/core/format-recall.d.ts +21 -1
  25. package/dist/core/format-recall.d.ts.map +1 -1
  26. package/dist/core/format-recall.js +60 -5
  27. package/dist/core/format-recall.js.map +1 -1
  28. package/dist/core/ml-worker.d.ts +24 -1
  29. package/dist/core/ml-worker.d.ts.map +1 -1
  30. package/dist/core/ml-worker.js +165 -4
  31. package/dist/core/ml-worker.js.map +1 -1
  32. package/dist/core/model-daemon-client.d.ts +77 -0
  33. package/dist/core/model-daemon-client.d.ts.map +1 -0
  34. package/dist/core/model-daemon-client.js +383 -0
  35. package/dist/core/model-daemon-client.js.map +1 -0
  36. package/dist/core/model-daemon-server.d.ts +71 -0
  37. package/dist/core/model-daemon-server.d.ts.map +1 -0
  38. package/dist/core/model-daemon-server.js +363 -0
  39. package/dist/core/model-daemon-server.js.map +1 -0
  40. package/dist/core/model-daemon.d.ts +137 -0
  41. package/dist/core/model-daemon.d.ts.map +1 -0
  42. package/dist/core/model-daemon.js +231 -0
  43. package/dist/core/model-daemon.js.map +1 -0
  44. package/dist/core/query-expander.d.ts +5 -0
  45. package/dist/core/query-expander.d.ts.map +1 -1
  46. package/dist/core/query-expander.js +3 -2
  47. package/dist/core/query-expander.js.map +1 -1
  48. package/dist/core/recall-config.d.ts +1 -1
  49. package/dist/core/recall-config.d.ts.map +1 -1
  50. package/dist/core/recall-config.js +5 -0
  51. package/dist/core/recall-config.js.map +1 -1
  52. package/dist/core/rerank-window.d.ts +7 -2
  53. package/dist/core/rerank-window.d.ts.map +1 -1
  54. package/dist/core/rerank-window.js +9 -4
  55. package/dist/core/rerank-window.js.map +1 -1
  56. package/dist/core/rerank2.d.ts +52 -2
  57. package/dist/core/rerank2.d.ts.map +1 -1
  58. package/dist/core/rerank2.js +60 -3
  59. package/dist/core/rerank2.js.map +1 -1
  60. package/dist/core/reranker.d.ts +19 -0
  61. package/dist/core/reranker.d.ts.map +1 -1
  62. package/dist/core/reranker.js +90 -13
  63. package/dist/core/reranker.js.map +1 -1
  64. package/dist/core/trust.d.ts +32 -0
  65. package/dist/core/trust.d.ts.map +1 -0
  66. package/dist/core/trust.js +66 -0
  67. package/dist/core/trust.js.map +1 -0
  68. package/dist/core/validity.d.ts +21 -0
  69. package/dist/core/validity.d.ts.map +1 -0
  70. package/dist/core/validity.js +41 -0
  71. package/dist/core/validity.js.map +1 -0
  72. package/dist/core/whoami.d.ts +3 -0
  73. package/dist/core/whoami.d.ts.map +1 -1
  74. package/dist/core/whoami.js +14 -0
  75. package/dist/core/whoami.js.map +1 -1
  76. package/dist/core/write-pipeline.d.ts +48 -0
  77. package/dist/core/write-pipeline.d.ts.map +1 -1
  78. package/dist/core/write-pipeline.js +80 -3
  79. package/dist/core/write-pipeline.js.map +1 -1
  80. package/dist/engine/activation.d.ts.map +1 -1
  81. package/dist/engine/activation.js +27 -4
  82. package/dist/engine/activation.js.map +1 -1
  83. package/dist/engine/consolidation.d.ts.map +1 -1
  84. package/dist/engine/consolidation.js +53 -4
  85. package/dist/engine/consolidation.js.map +1 -1
  86. package/dist/hooks/prime-query.d.ts +57 -0
  87. package/dist/hooks/prime-query.d.ts.map +1 -0
  88. package/dist/hooks/prime-query.js +388 -0
  89. package/dist/hooks/prime-query.js.map +1 -0
  90. package/dist/hooks/prime.d.ts +8 -0
  91. package/dist/hooks/prime.d.ts.map +1 -1
  92. package/dist/hooks/prime.js +21 -2
  93. package/dist/hooks/prime.js.map +1 -1
  94. package/dist/hooks/sidecar.d.ts +18 -0
  95. package/dist/hooks/sidecar.d.ts.map +1 -1
  96. package/dist/hooks/sidecar.js +58 -2
  97. package/dist/hooks/sidecar.js.map +1 -1
  98. package/dist/index.js +15 -3
  99. package/dist/index.js.map +1 -1
  100. package/dist/mcp.js +157 -104
  101. package/dist/mcp.js.map +1 -1
  102. package/dist/model-daemon.d.ts +8 -0
  103. package/dist/model-daemon.d.ts.map +1 -0
  104. package/dist/model-daemon.js +45 -0
  105. package/dist/model-daemon.js.map +1 -0
  106. package/dist/storage/factory.d.ts.map +1 -1
  107. package/dist/storage/factory.js +5 -0
  108. package/dist/storage/factory.js.map +1 -1
  109. package/dist/storage/pglite-schema.d.ts.map +1 -1
  110. package/dist/storage/pglite-schema.js +176 -170
  111. package/dist/storage/pglite-schema.js.map +1 -1
  112. package/dist/storage/pglite.d.ts +5 -0
  113. package/dist/storage/pglite.d.ts.map +1 -1
  114. package/dist/storage/pglite.js +165 -153
  115. package/dist/storage/pglite.js.map +1 -1
  116. package/dist/storage/postgres.d.ts +5 -0
  117. package/dist/storage/postgres.d.ts.map +1 -1
  118. package/dist/storage/postgres.js +165 -153
  119. package/dist/storage/postgres.js.map +1 -1
  120. package/dist/storage/sqlite.d.ts +22 -0
  121. package/dist/storage/sqlite.d.ts.map +1 -1
  122. package/dist/storage/sqlite.js +415 -363
  123. package/dist/storage/sqlite.js.map +1 -1
  124. package/dist/types/engram.d.ts +33 -0
  125. package/dist/types/engram.d.ts.map +1 -1
  126. package/dist/types/engram.js.map +1 -1
  127. package/package.json +1 -1
  128. package/plugin/.claude-plugin/plugin.json +1 -1
  129. package/plugin/README.md +1 -1
  130. package/plugin/bin/awm-mcp-launcher.cjs +2 -2
  131. package/plugin/skills/awm-memory/SKILL.md +11 -2
  132. package/src/adapters/common.ts +881 -802
  133. package/src/api/routes.ts +20 -0
  134. package/src/cli/migrate.ts +309 -307
  135. package/src/core/asked-value.ts +74 -0
  136. package/src/core/conflicts.ts +280 -0
  137. package/src/core/embeddings.ts +35 -10
  138. package/src/core/format-recall.ts +83 -25
  139. package/src/core/ml-worker.ts +458 -281
  140. package/src/core/model-daemon-client.ts +374 -0
  141. package/src/core/model-daemon-server.ts +344 -0
  142. package/src/core/model-daemon.ts +243 -0
  143. package/src/core/query-expander.ts +127 -126
  144. package/src/core/recall-config.ts +5 -0
  145. package/src/core/rerank-window.ts +164 -159
  146. package/src/core/rerank2.ts +143 -82
  147. package/src/core/reranker.ts +92 -12
  148. package/src/core/trust.ts +65 -0
  149. package/src/core/validity.ts +47 -0
  150. package/src/core/whoami.ts +136 -120
  151. package/src/core/write-pipeline.ts +119 -3
  152. package/src/engine/activation.ts +27 -4
  153. package/src/engine/consolidation.ts +950 -899
  154. package/src/hooks/prime-query.ts +375 -0
  155. package/src/hooks/prime.ts +25 -2
  156. package/src/hooks/sidecar.ts +57 -3
  157. package/src/index.ts +258 -248
  158. package/src/mcp.ts +1529 -1475
  159. package/src/model-daemon.ts +48 -0
  160. package/src/storage/factory.ts +25 -19
  161. package/src/storage/pglite-schema.ts +199 -193
  162. package/src/storage/pglite.ts +1513 -1498
  163. package/src/storage/postgres.ts +1613 -1598
  164. package/src/storage/sqlite.ts +2069 -2019
  165. package/src/types/engram.ts +30 -0
@@ -1,307 +1,309 @@
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-pgvector';
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-pgvector';
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
+ stage_changed_at
112
+ ) VALUES (
113
+ $1, $2, $3, $4, $5::vector, $6, $7, $8, $9, $10, $11,
114
+ $12, $13, $14, $15, $16, $17, $18, $19, $20,
115
+ $21, $22, $23, $24, $25, $26, $27, $28, $29, $30
116
+ )`,
117
+ [
118
+ r.id, r.agent_id, r.concept, r.content, embedding, r.embedding_model,
119
+ r.confidence, r.salience, r.access_count, r.last_accessed, r.created_at,
120
+ r.salience_features ?? '{}', r.reason_codes ?? '[]',
121
+ r.stage ?? 'active', r.ttl,
122
+ intToBool(r.retracted), r.retracted_by, r.retracted_at,
123
+ r.tags ?? '[]', r.memory_type ?? 'unclassified',
124
+ r.memory_class ?? 'working', r.superseded_by, r.supersedes, r.episode_id,
125
+ r.task_status, r.task_priority, r.blocked_by, r.sequence, r.references_json,
126
+ r.stage_changed_at ?? null,
127
+ ],
128
+ );
129
+ stats.engrams++;
130
+ }
131
+ if (verbose) process.stdout.write(` ${Math.min(i + batchSize, engramRows.length)}/${engramRows.length}\r`);
132
+ }
133
+ if (verbose) console.log(` engrams: done (${stats.engrams})`);
134
+ } else if (dryRun) {
135
+ stats.engrams = engramRows.length;
136
+ }
137
+
138
+ // ── associations ────────────────────────────────────────
139
+ const assocRows = src.prepare(`SELECT * FROM associations`).all() as any[];
140
+ if (verbose) console.log(`associations: ${assocRows.length} rows`);
141
+ if (dst && assocRows.length > 0) {
142
+ for (let i = 0; i < assocRows.length; i += batchSize) {
143
+ const batch = assocRows.slice(i, i + batchSize);
144
+ for (const r of batch) {
145
+ await dst.query(
146
+ `INSERT INTO associations (
147
+ id, from_engram_id, to_engram_id, weight, confidence, type,
148
+ activation_count, created_at, last_activated
149
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)`,
150
+ [
151
+ r.id, r.from_engram_id, r.to_engram_id, r.weight, r.confidence,
152
+ r.type ?? 'hebbian', r.activation_count ?? 0,
153
+ r.created_at, r.last_activated,
154
+ ],
155
+ );
156
+ stats.associations++;
157
+ }
158
+ }
159
+ } else if (dryRun) {
160
+ stats.associations = assocRows.length;
161
+ }
162
+
163
+ // ── agents ──────────────────────────────────────────────
164
+ const agentRows = src.prepare(`SELECT * FROM agents`).all() as any[];
165
+ if (verbose) console.log(`agents: ${agentRows.length} rows`);
166
+ if (dst && agentRows.length > 0) {
167
+ for (const r of agentRows) {
168
+ await dst.query(
169
+ `INSERT INTO agents (id, name, created_at, config) VALUES ($1,$2,$3,$4)
170
+ ON CONFLICT (id) DO NOTHING`,
171
+ [r.id, r.name, r.created_at, r.config ?? '{}'],
172
+ );
173
+ stats.agents++;
174
+ }
175
+ } else if (dryRun) {
176
+ stats.agents = agentRows.length;
177
+ }
178
+
179
+ // ── activation_events ───────────────────────────────────
180
+ const aeRows = src.prepare(`SELECT * FROM activation_events`).all() as any[];
181
+ if (verbose) console.log(`activation_events: ${aeRows.length} rows`);
182
+ if (dst && aeRows.length > 0) {
183
+ for (let i = 0; i < aeRows.length; i += batchSize) {
184
+ for (const r of aeRows.slice(i, i + batchSize)) {
185
+ await dst.query(
186
+ `INSERT INTO activation_events
187
+ (id, agent_id, timestamp, context, results_returned, top_score, latency_ms, engram_ids)
188
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8)`,
189
+ [r.id, r.agent_id, r.timestamp, r.context, r.results_returned,
190
+ r.top_score, r.latency_ms, r.engram_ids ?? '[]'],
191
+ );
192
+ stats.activationEvents++;
193
+ }
194
+ }
195
+ } else if (dryRun) {
196
+ stats.activationEvents = aeRows.length;
197
+ }
198
+
199
+ // ── staging_events ──────────────────────────────────────
200
+ const seRows = src.prepare(`SELECT * FROM staging_events`).all() as any[];
201
+ if (verbose) console.log(`staging_events: ${seRows.length} rows`);
202
+ if (dst && seRows.length > 0) {
203
+ for (const r of seRows) {
204
+ await dst.query(
205
+ `INSERT INTO staging_events (engram_id, agent_id, action, resonance_score, timestamp, age_ms)
206
+ VALUES ($1,$2,$3,$4,$5,$6)`,
207
+ [r.engram_id, r.agent_id, r.action, r.resonance_score, r.timestamp, r.age_ms],
208
+ );
209
+ stats.stagingEvents++;
210
+ }
211
+ } else if (dryRun) {
212
+ stats.stagingEvents = seRows.length;
213
+ }
214
+
215
+ // ── retrieval_feedback ──────────────────────────────────
216
+ const rfRows = src.prepare(`SELECT * FROM retrieval_feedback`).all() as any[];
217
+ if (verbose) console.log(`retrieval_feedback: ${rfRows.length} rows`);
218
+ if (dst && rfRows.length > 0) {
219
+ for (const r of rfRows) {
220
+ await dst.query(
221
+ `INSERT INTO retrieval_feedback
222
+ (id, activation_event_id, engram_id, useful, context, timestamp)
223
+ VALUES ($1,$2,$3,$4,$5,$6)`,
224
+ [r.id, r.activation_event_id, r.engram_id, intToBool(r.useful), r.context, r.timestamp],
225
+ );
226
+ stats.retrievalFeedback++;
227
+ }
228
+ } else if (dryRun) {
229
+ stats.retrievalFeedback = rfRows.length;
230
+ }
231
+
232
+ // ── episodes ────────────────────────────────────────────
233
+ // The episodes table may not exist in older SQLite databases.
234
+ try {
235
+ const epRows = src.prepare(`SELECT * FROM episodes`).all() as any[];
236
+ if (verbose) console.log(`episodes: ${epRows.length} rows`);
237
+ if (dst && epRows.length > 0) {
238
+ for (const r of epRows) {
239
+ const embedding = r.embedding
240
+ ? vectorLiteral(Array.from(bufferToFloat32Array(r.embedding)))
241
+ : null;
242
+ await dst.query(
243
+ `INSERT INTO episodes
244
+ (id, agent_id, label, embedding, engram_count, start_time, end_time, created_at)
245
+ VALUES ($1,$2,$3,$4::vector,$5,$6,$7,$8)`,
246
+ [r.id, r.agent_id, r.label, embedding, r.engram_count ?? 0,
247
+ r.start_time, r.end_time, r.created_at],
248
+ );
249
+ stats.episodes++;
250
+ }
251
+ } else if (dryRun) {
252
+ stats.episodes = epRows.length;
253
+ }
254
+ } catch {
255
+ if (verbose) console.log(`episodes: table not present in source (skipping)`);
256
+ }
257
+
258
+ // ── conscious_state ─────────────────────────────────────
259
+ try {
260
+ const csRows = src.prepare(`SELECT * FROM conscious_state`).all() as any[];
261
+ if (verbose) console.log(`conscious_state: ${csRows.length} rows`);
262
+ if (dst && csRows.length > 0) {
263
+ for (const r of csRows) {
264
+ await dst.query(
265
+ `INSERT INTO conscious_state (
266
+ agent_id, last_write_id, last_recall_context, last_recall_ids,
267
+ last_activity_at, write_count_since_consolidation,
268
+ recall_count_since_consolidation, execution_state, checkpoint_at,
269
+ last_consolidation_at, last_mini_consolidation_at,
270
+ consolidation_cycle_count, updated_at
271
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)
272
+ ON CONFLICT (agent_id) DO NOTHING`,
273
+ [
274
+ r.agent_id, r.last_write_id, r.last_recall_context,
275
+ r.last_recall_ids ?? '[]', r.last_activity_at,
276
+ r.write_count_since_consolidation ?? 0,
277
+ r.recall_count_since_consolidation ?? 0,
278
+ r.execution_state, r.checkpoint_at,
279
+ r.last_consolidation_at, r.last_mini_consolidation_at,
280
+ r.consolidation_cycle_count ?? 0, r.updated_at,
281
+ ],
282
+ );
283
+ stats.consciousState++;
284
+ }
285
+ } else if (dryRun) {
286
+ stats.consciousState = csRows.length;
287
+ }
288
+ } catch {
289
+ if (verbose) console.log(`conscious_state: table not present in source (skipping)`);
290
+ }
291
+
292
+ src.close();
293
+ if (dst) await dst.close();
294
+
295
+ return stats;
296
+ }
297
+
298
+ export function printStats(stats: MigrateStats, dryRun: boolean): void {
299
+ const verb = dryRun ? 'would migrate' : 'migrated';
300
+ console.log(`\nMigration ${dryRun ? 'plan' : 'complete'}:`);
301
+ console.log(` ${verb} ${stats.engrams} engrams`);
302
+ console.log(` ${verb} ${stats.associations} associations`);
303
+ console.log(` ${verb} ${stats.agents} agents`);
304
+ console.log(` ${verb} ${stats.activationEvents} activation events`);
305
+ console.log(` ${verb} ${stats.stagingEvents} staging events`);
306
+ console.log(` ${verb} ${stats.retrievalFeedback} retrieval feedback records`);
307
+ console.log(` ${verb} ${stats.episodes} episodes`);
308
+ console.log(` ${verb} ${stats.consciousState} conscious_state rows`);
309
+ }