agent-working-memory 0.16.1 → 0.18.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.
Files changed (170) hide show
  1. package/README.md +321 -318
  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 -444
  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 +76 -12
  63. package/dist/core/reranker.js.map +1 -1
  64. package/dist/core/salience.d.ts +2 -0
  65. package/dist/core/salience.d.ts.map +1 -1
  66. package/dist/core/salience.js +45 -18
  67. package/dist/core/salience.js.map +1 -1
  68. package/dist/core/trust.d.ts +32 -0
  69. package/dist/core/trust.d.ts.map +1 -0
  70. package/dist/core/trust.js +66 -0
  71. package/dist/core/trust.js.map +1 -0
  72. package/dist/core/validity.d.ts +21 -0
  73. package/dist/core/validity.d.ts.map +1 -0
  74. package/dist/core/validity.js +41 -0
  75. package/dist/core/validity.js.map +1 -0
  76. package/dist/core/whoami.d.ts +3 -0
  77. package/dist/core/whoami.d.ts.map +1 -1
  78. package/dist/core/whoami.js +14 -0
  79. package/dist/core/whoami.js.map +1 -1
  80. package/dist/core/write-pipeline.d.ts +48 -0
  81. package/dist/core/write-pipeline.d.ts.map +1 -1
  82. package/dist/core/write-pipeline.js +104 -4
  83. package/dist/core/write-pipeline.js.map +1 -1
  84. package/dist/engine/activation.d.ts.map +1 -1
  85. package/dist/engine/activation.js +27 -4
  86. package/dist/engine/activation.js.map +1 -1
  87. package/dist/engine/consolidation.d.ts.map +1 -1
  88. package/dist/engine/consolidation.js +67 -5
  89. package/dist/engine/consolidation.js.map +1 -1
  90. package/dist/hooks/prime-query.d.ts +57 -0
  91. package/dist/hooks/prime-query.d.ts.map +1 -0
  92. package/dist/hooks/prime-query.js +388 -0
  93. package/dist/hooks/prime-query.js.map +1 -0
  94. package/dist/hooks/prime.d.ts +8 -0
  95. package/dist/hooks/prime.d.ts.map +1 -1
  96. package/dist/hooks/prime.js +21 -2
  97. package/dist/hooks/prime.js.map +1 -1
  98. package/dist/hooks/sidecar.d.ts +7 -0
  99. package/dist/hooks/sidecar.d.ts.map +1 -1
  100. package/dist/hooks/sidecar.js +10 -2
  101. package/dist/hooks/sidecar.js.map +1 -1
  102. package/dist/index.js +11 -3
  103. package/dist/index.js.map +1 -1
  104. package/dist/mcp.js +148 -103
  105. package/dist/mcp.js.map +1 -1
  106. package/dist/model-daemon.d.ts +8 -0
  107. package/dist/model-daemon.d.ts.map +1 -0
  108. package/dist/model-daemon.js +45 -0
  109. package/dist/model-daemon.js.map +1 -0
  110. package/dist/storage/factory.d.ts.map +1 -1
  111. package/dist/storage/factory.js +5 -0
  112. package/dist/storage/factory.js.map +1 -1
  113. package/dist/storage/pglite-schema.d.ts.map +1 -1
  114. package/dist/storage/pglite-schema.js +176 -170
  115. package/dist/storage/pglite-schema.js.map +1 -1
  116. package/dist/storage/pglite.d.ts +5 -0
  117. package/dist/storage/pglite.d.ts.map +1 -1
  118. package/dist/storage/pglite.js +165 -153
  119. package/dist/storage/pglite.js.map +1 -1
  120. package/dist/storage/postgres.d.ts +5 -0
  121. package/dist/storage/postgres.d.ts.map +1 -1
  122. package/dist/storage/postgres.js +165 -153
  123. package/dist/storage/postgres.js.map +1 -1
  124. package/dist/storage/sqlite.d.ts +22 -0
  125. package/dist/storage/sqlite.d.ts.map +1 -1
  126. package/dist/storage/sqlite.js +400 -363
  127. package/dist/storage/sqlite.js.map +1 -1
  128. package/dist/types/engram.d.ts +33 -0
  129. package/dist/types/engram.d.ts.map +1 -1
  130. package/dist/types/engram.js.map +1 -1
  131. package/package.json +106 -106
  132. package/plugin/.claude-plugin/plugin.json +1 -1
  133. package/plugin/README.md +1 -1
  134. package/plugin/bin/awm-mcp-launcher.cjs +2 -2
  135. package/plugin/skills/awm-memory/SKILL.md +16 -5
  136. package/src/adapters/common.ts +881 -800
  137. package/src/api/routes.ts +20 -0
  138. package/src/cli/migrate.ts +309 -307
  139. package/src/core/asked-value.ts +74 -0
  140. package/src/core/conflicts.ts +280 -0
  141. package/src/core/embeddings.ts +35 -10
  142. package/src/core/format-recall.ts +83 -25
  143. package/src/core/ml-worker.ts +458 -281
  144. package/src/core/model-daemon-client.ts +374 -0
  145. package/src/core/model-daemon-server.ts +344 -0
  146. package/src/core/model-daemon.ts +243 -0
  147. package/src/core/query-expander.ts +127 -126
  148. package/src/core/recall-config.ts +5 -0
  149. package/src/core/rerank-window.ts +164 -159
  150. package/src/core/rerank2.ts +143 -82
  151. package/src/core/reranker.ts +78 -11
  152. package/src/core/salience.ts +45 -18
  153. package/src/core/trust.ts +65 -0
  154. package/src/core/validity.ts +47 -0
  155. package/src/core/whoami.ts +136 -120
  156. package/src/core/write-pipeline.ts +143 -4
  157. package/src/engine/activation.ts +27 -4
  158. package/src/engine/consolidation.ts +950 -887
  159. package/src/hooks/prime-query.ts +375 -0
  160. package/src/hooks/prime.ts +25 -2
  161. package/src/hooks/sidecar.ts +13 -3
  162. package/src/index.ts +255 -248
  163. package/src/mcp.ts +1521 -1475
  164. package/src/model-daemon.ts +48 -0
  165. package/src/storage/factory.ts +25 -19
  166. package/src/storage/pglite-schema.ts +199 -193
  167. package/src/storage/pglite.ts +1513 -1498
  168. package/src/storage/postgres.ts +1613 -1598
  169. package/src/storage/sqlite.ts +2056 -2019
  170. 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
+ }