agent-working-memory 0.8.6 → 0.8.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -2
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +13 -0
- package/dist/adapters/common.js.map +1 -1
- package/dist/api/routes.js +8 -8
- package/dist/cli/migrate.js +29 -29
- package/dist/cli.js +105 -105
- package/dist/coordination/circuit-breaker.js +23 -23
- package/dist/core/lite-compress.d.ts +26 -0
- package/dist/core/lite-compress.d.ts.map +1 -0
- package/dist/core/lite-compress.js +105 -0
- package/dist/core/lite-compress.js.map +1 -0
- package/dist/mcp.d.ts +5 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +138 -84
- package/dist/mcp.js.map +1 -1
- package/dist/storage/pglite-schema.js +143 -143
- package/dist/storage/pglite.js +138 -138
- package/package.json +4 -3
- package/src/adapters/common.ts +13 -0
- package/src/api/index.ts +3 -3
- package/src/api/routes.ts +1 -1
- package/src/cli/migrate.ts +307 -307
- package/src/cli.ts +1 -1
- package/src/coordination/circuit-breaker.ts +83 -83
- package/src/coordination/failure-modes.ts +50 -50
- package/src/core/decay.ts +63 -63
- package/src/core/embeddings.ts +110 -110
- package/src/core/index.ts +5 -5
- package/src/core/lite-compress.ts +129 -0
- package/src/core/logger.ts +36 -36
- package/src/core/ml-worker-entry.ts +194 -194
- package/src/core/ml-worker.ts +281 -281
- package/src/core/query-expander.ts +122 -122
- package/src/core/reranker.ts +119 -119
- package/src/engine/confidence.ts +120 -120
- package/src/engine/connections.ts +162 -162
- package/src/engine/consolidation-scheduler.ts +242 -242
- package/src/engine/eval.ts +102 -102
- package/src/engine/eviction.ts +101 -101
- package/src/engine/index.ts +8 -8
- package/src/engine/retraction.ts +366 -366
- package/src/engine/staging.ts +74 -74
- package/src/mcp.ts +70 -4
- package/src/storage/factory.ts +147 -147
- package/src/storage/index.ts +3 -3
- package/src/storage/pglite-schema.ts +166 -166
- package/src/storage/pglite.ts +1363 -1363
- package/src/storage/store.ts +80 -80
- package/src/types/agent.ts +67 -67
- package/src/types/checkpoint.ts +46 -46
- package/src/types/eval.ts +100 -100
- package/src/types/index.ts +6 -6
package/src/cli/migrate.ts
CHANGED
|
@@ -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
|
@@ -336,7 +336,7 @@ async function exportMemories() {
|
|
|
336
336
|
const agents = [...new Set(memories.map((m: any) => m.agent_id))];
|
|
337
337
|
|
|
338
338
|
const exportData = {
|
|
339
|
-
version: '0.8.
|
|
339
|
+
version: '0.8.8',
|
|
340
340
|
exported_at: new Date().toISOString(),
|
|
341
341
|
source_db: dbPath,
|
|
342
342
|
agent_filter: agentFilter,
|