@framers/agentos 0.1.122 → 0.1.124

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 (126) hide show
  1. package/dist/api/TextToolCallParser.d.ts +61 -0
  2. package/dist/api/TextToolCallParser.d.ts.map +1 -0
  3. package/dist/api/TextToolCallParser.js +137 -0
  4. package/dist/api/TextToolCallParser.js.map +1 -0
  5. package/dist/api/agent.d.ts +7 -0
  6. package/dist/api/agent.d.ts.map +1 -1
  7. package/dist/api/agent.js +1 -0
  8. package/dist/api/agent.js.map +1 -1
  9. package/dist/api/generateText.d.ts +105 -0
  10. package/dist/api/generateText.d.ts.map +1 -1
  11. package/dist/api/generateText.js +191 -2
  12. package/dist/api/generateText.js.map +1 -1
  13. package/dist/cognitive_substrate/GMI.d.ts.map +1 -1
  14. package/dist/cognitive_substrate/GMI.js +27 -1
  15. package/dist/cognitive_substrate/GMI.js.map +1 -1
  16. package/dist/cognitive_substrate/IGMI.d.ts +5 -0
  17. package/dist/cognitive_substrate/IGMI.d.ts.map +1 -1
  18. package/dist/cognitive_substrate/IGMI.js.map +1 -1
  19. package/dist/emergent/AdaptPersonalityTool.d.ts +5 -21
  20. package/dist/emergent/AdaptPersonalityTool.d.ts.map +1 -1
  21. package/dist/emergent/AdaptPersonalityTool.js +18 -10
  22. package/dist/emergent/AdaptPersonalityTool.js.map +1 -1
  23. package/dist/emergent/CreateWorkflowTool.d.ts +1 -0
  24. package/dist/emergent/CreateWorkflowTool.d.ts.map +1 -1
  25. package/dist/emergent/CreateWorkflowTool.js +28 -5
  26. package/dist/emergent/CreateWorkflowTool.js.map +1 -1
  27. package/dist/emergent/EmergentCapabilityEngine.d.ts +68 -0
  28. package/dist/emergent/EmergentCapabilityEngine.d.ts.map +1 -1
  29. package/dist/emergent/EmergentCapabilityEngine.js +99 -0
  30. package/dist/emergent/EmergentCapabilityEngine.js.map +1 -1
  31. package/dist/emergent/SelfEvaluateTool.d.ts +15 -5
  32. package/dist/emergent/SelfEvaluateTool.d.ts.map +1 -1
  33. package/dist/emergent/SelfEvaluateTool.js +111 -54
  34. package/dist/emergent/SelfEvaluateTool.js.map +1 -1
  35. package/dist/emergent/SelfImprovementConfig.d.ts +12 -1
  36. package/dist/emergent/SelfImprovementConfig.d.ts.map +1 -1
  37. package/dist/emergent/SelfImprovementConfig.js.map +1 -1
  38. package/dist/emergent/index.d.ts +7 -0
  39. package/dist/emergent/index.d.ts.map +1 -1
  40. package/dist/emergent/index.js +5 -0
  41. package/dist/emergent/index.js.map +1 -1
  42. package/dist/memory/AgentMemory.d.ts +2 -2
  43. package/dist/memory/AgentMemory.d.ts.map +1 -1
  44. package/dist/memory/AgentMemory.js +4 -3
  45. package/dist/memory/AgentMemory.js.map +1 -1
  46. package/dist/memory/consolidation/ConsolidationLoop.d.ts +31 -4
  47. package/dist/memory/consolidation/ConsolidationLoop.d.ts.map +1 -1
  48. package/dist/memory/consolidation/ConsolidationLoop.js +112 -69
  49. package/dist/memory/consolidation/ConsolidationLoop.js.map +1 -1
  50. package/dist/memory/facade/Memory.d.ts +12 -6
  51. package/dist/memory/facade/Memory.d.ts.map +1 -1
  52. package/dist/memory/facade/Memory.js +216 -212
  53. package/dist/memory/facade/Memory.js.map +1 -1
  54. package/dist/memory/facade/types.d.ts +11 -1
  55. package/dist/memory/facade/types.d.ts.map +1 -1
  56. package/dist/memory/feedback/RetrievalFeedbackSignal.d.ts +7 -10
  57. package/dist/memory/feedback/RetrievalFeedbackSignal.d.ts.map +1 -1
  58. package/dist/memory/feedback/RetrievalFeedbackSignal.js +40 -36
  59. package/dist/memory/feedback/RetrievalFeedbackSignal.js.map +1 -1
  60. package/dist/memory/io/ChatGptImporter.d.ts.map +1 -1
  61. package/dist/memory/io/ChatGptImporter.js +24 -18
  62. package/dist/memory/io/ChatGptImporter.js.map +1 -1
  63. package/dist/memory/io/CsvImporter.d.ts.map +1 -1
  64. package/dist/memory/io/CsvImporter.js +21 -8
  65. package/dist/memory/io/CsvImporter.js.map +1 -1
  66. package/dist/memory/io/JsonExporter.d.ts.map +1 -1
  67. package/dist/memory/io/JsonExporter.js +6 -17
  68. package/dist/memory/io/JsonExporter.js.map +1 -1
  69. package/dist/memory/io/JsonImporter.d.ts +3 -0
  70. package/dist/memory/io/JsonImporter.d.ts.map +1 -1
  71. package/dist/memory/io/JsonImporter.js +58 -29
  72. package/dist/memory/io/JsonImporter.js.map +1 -1
  73. package/dist/memory/io/MarkdownExporter.d.ts.map +1 -1
  74. package/dist/memory/io/MarkdownExporter.js +1 -4
  75. package/dist/memory/io/MarkdownExporter.js.map +1 -1
  76. package/dist/memory/io/MarkdownImporter.d.ts.map +1 -1
  77. package/dist/memory/io/MarkdownImporter.js +12 -7
  78. package/dist/memory/io/MarkdownImporter.js.map +1 -1
  79. package/dist/memory/io/ObsidianExporter.d.ts +14 -9
  80. package/dist/memory/io/ObsidianExporter.d.ts.map +1 -1
  81. package/dist/memory/io/ObsidianExporter.js +35 -23
  82. package/dist/memory/io/ObsidianExporter.js.map +1 -1
  83. package/dist/memory/io/ObsidianImporter.d.ts.map +1 -1
  84. package/dist/memory/io/ObsidianImporter.js +34 -27
  85. package/dist/memory/io/ObsidianImporter.js.map +1 -1
  86. package/dist/memory/io/SqliteExporter.d.ts +1 -2
  87. package/dist/memory/io/SqliteExporter.d.ts.map +1 -1
  88. package/dist/memory/io/SqliteExporter.js +2 -3
  89. package/dist/memory/io/SqliteExporter.js.map +1 -1
  90. package/dist/memory/io/SqliteImporter.d.ts +3 -0
  91. package/dist/memory/io/SqliteImporter.d.ts.map +1 -1
  92. package/dist/memory/io/SqliteImporter.js +62 -27
  93. package/dist/memory/io/SqliteImporter.js.map +1 -1
  94. package/dist/memory/store/SqliteBrain.d.ts +84 -24
  95. package/dist/memory/store/SqliteBrain.d.ts.map +1 -1
  96. package/dist/memory/store/SqliteBrain.js +139 -55
  97. package/dist/memory/store/SqliteBrain.js.map +1 -1
  98. package/dist/memory/store/SqliteKnowledgeGraph.d.ts +6 -2
  99. package/dist/memory/store/SqliteKnowledgeGraph.d.ts.map +1 -1
  100. package/dist/memory/store/SqliteKnowledgeGraph.js +94 -120
  101. package/dist/memory/store/SqliteKnowledgeGraph.js.map +1 -1
  102. package/dist/memory/store/SqliteMemoryGraph.d.ts +4 -5
  103. package/dist/memory/store/SqliteMemoryGraph.d.ts.map +1 -1
  104. package/dist/memory/store/SqliteMemoryGraph.js +31 -41
  105. package/dist/memory/store/SqliteMemoryGraph.js.map +1 -1
  106. package/dist/memory/tools/MemoryAddTool.d.ts.map +1 -1
  107. package/dist/memory/tools/MemoryAddTool.js +12 -16
  108. package/dist/memory/tools/MemoryAddTool.js.map +1 -1
  109. package/dist/memory/tools/MemoryDeleteTool.d.ts.map +1 -1
  110. package/dist/memory/tools/MemoryDeleteTool.js +1 -3
  111. package/dist/memory/tools/MemoryDeleteTool.js.map +1 -1
  112. package/dist/memory/tools/MemoryMergeTool.d.ts.map +1 -1
  113. package/dist/memory/tools/MemoryMergeTool.js +13 -22
  114. package/dist/memory/tools/MemoryMergeTool.js.map +1 -1
  115. package/dist/memory/tools/MemorySearchTool.js +4 -4
  116. package/dist/memory/tools/MemorySearchTool.js.map +1 -1
  117. package/dist/memory/tools/MemoryUpdateTool.d.ts.map +1 -1
  118. package/dist/memory/tools/MemoryUpdateTool.js +10 -18
  119. package/dist/memory/tools/MemoryUpdateTool.js.map +1 -1
  120. package/dist/orchestration/runtime/LoopController.d.ts +7 -0
  121. package/dist/orchestration/runtime/LoopController.d.ts.map +1 -1
  122. package/dist/orchestration/runtime/LoopController.js +7 -0
  123. package/dist/orchestration/runtime/LoopController.js.map +1 -1
  124. package/dist/rag/search/BM25Index.js +1 -1
  125. package/dist/rag/search/BM25Index.js.map +1 -1
  126. package/package.json +1 -1
@@ -63,7 +63,7 @@ function sha256(content) {
63
63
  *
64
64
  * ## Quick start
65
65
  * ```ts
66
- * const mem = new Memory({ store: 'sqlite', path: './brain.sqlite' });
66
+ * const mem = await Memory.create({ store: 'sqlite', path: './brain.sqlite' });
67
67
  *
68
68
  * await mem.remember('The user prefers dark mode');
69
69
  * const results = await mem.recall('dark mode preference');
@@ -74,67 +74,31 @@ function sha256(content) {
74
74
  */
75
75
  export class Memory {
76
76
  // -------------------------------------------------------------------
77
- // Constructor
77
+ // Constructor (private — use Memory.create() instead)
78
78
  // -------------------------------------------------------------------
79
79
  /**
80
- * Create a new Memory instance and wire together all subsystems.
81
- *
82
- * Initialization sequence:
83
- * 1. Merge `config` with defaults (store='sqlite', path=tmpdir, graph=true,
84
- * selfImprove=true, decay=true).
85
- * 2. Create `SqliteBrain(config.path)`.
86
- * 3. Check embedding dimension compatibility (warn on mismatch).
87
- * 4. Create `SqliteKnowledgeGraph(brain)`.
88
- * 5. Create `SqliteMemoryGraph(brain)` and call `.initialize()`.
89
- * 6. Create `LoaderRegistry()` (pre-registers all built-in loaders).
90
- * 7. Create `FolderScanner(registry)`.
91
- * 8. Create `ChunkingEngine()`.
92
- * 9. If `selfImprove`: create `RetrievalFeedbackSignal(brain)` and
93
- * `ConsolidationLoop(brain, memoryGraph)`.
94
- *
95
- * @param config - Optional configuration; see {@link MemoryConfig}.
80
+ * Private constructor. Receives an already-opened SqliteBrain and
81
+ * pre-computed configuration. Use {@link Memory.create} to instantiate.
96
82
  */
97
- constructor(config) {
83
+ constructor(brain, config) {
98
84
  /** HNSW sidecar index for O(log n) vector search alongside SQLite. */
99
85
  this._hnswSidecar = null;
100
- // Step 1: merge with defaults.
101
- const randomSuffix = Math.random().toString(36).slice(2, 10);
102
- this._config = {
103
- store: 'sqlite',
104
- path: path.join(os.tmpdir(), `brain-${randomSuffix}.sqlite`),
105
- graph: true,
106
- selfImprove: true,
107
- decay: true,
108
- ...config,
109
- };
86
+ this._brain = brain;
87
+ this._config = config;
110
88
  // Store the optional embedding function for vector search.
111
- this._embed = config?.embed ?? null;
112
- if (this._config.store !== 'sqlite') {
113
- throw new Error(`Memory currently supports only the SQLite-backed facade at runtime. ` +
114
- `Received store="${this._config.store}".`);
115
- }
116
- // Step 2: create SqliteBrain.
117
- this._brain = new SqliteBrain(this._config.path);
118
- // Step 3: check embedding dimension compatibility.
119
- const dimensions = this._config.embeddings?.dimensions ?? 1536;
120
- const compatible = this._brain.checkEmbeddingCompat(dimensions);
121
- if (!compatible) {
122
- console.warn(`[Memory] Embedding dimension mismatch: expected ${dimensions} but brain ` +
123
- `was previously configured with a different dimension. Vector similarity ` +
124
- `searches may produce incorrect results.`);
125
- }
126
- // Step 4: create SqliteKnowledgeGraph.
89
+ this._embed = config.embed ?? null;
90
+ // Create SqliteKnowledgeGraph.
127
91
  this._knowledgeGraph = new SqliteKnowledgeGraph(this._brain);
128
- // Step 5: create SqliteMemoryGraph and initialize.
92
+ // Create SqliteMemoryGraph and initialize.
129
93
  this._memoryGraph = new SqliteMemoryGraph(this._brain);
130
94
  this._initPromise = this._memoryGraph.initialize();
131
- // Step 6: create LoaderRegistry.
95
+ // Create LoaderRegistry.
132
96
  this._loaderRegistry = new LoaderRegistry();
133
- // Step 7: create FolderScanner.
97
+ // Create FolderScanner.
134
98
  this._folderScanner = new FolderScanner(this._loaderRegistry);
135
- // Step 8: create ChunkingEngine.
99
+ // Create ChunkingEngine.
136
100
  this._chunkingEngine = new ChunkingEngine();
137
- // Step 9: self-improvement subsystems.
101
+ // Self-improvement subsystems.
138
102
  if (this._config.selfImprove) {
139
103
  this._feedbackSignal = new RetrievalFeedbackSignal(this._brain);
140
104
  this._consolidationLoop = new ConsolidationLoop(this._brain, this._memoryGraph);
@@ -143,7 +107,7 @@ export class Memory {
143
107
  this._feedbackSignal = null;
144
108
  this._consolidationLoop = null;
145
109
  }
146
- // Step 10: HNSW sidecar index (O(log n) ANN alongside SQLite).
110
+ // HNSW sidecar index (O(log n) ANN alongside SQLite).
147
111
  // Loads existing index from disk if present; auto-builds when trace count
148
112
  // exceeds 1000 and embeddings are available. Falls back to FTS5-only
149
113
  // recall if hnswlib-node is not installed or no embeddings exist.
@@ -164,6 +128,55 @@ export class Memory {
164
128
  }
165
129
  });
166
130
  }
131
+ // -------------------------------------------------------------------
132
+ // Async factory
133
+ // -------------------------------------------------------------------
134
+ /**
135
+ * Create a new Memory instance and wire together all subsystems.
136
+ *
137
+ * Initialization sequence:
138
+ * 1. Merge `config` with defaults (store='sqlite', path=tmpdir, graph=true,
139
+ * selfImprove=true, decay=true).
140
+ * 2. Await `SqliteBrain.open(config.path)`.
141
+ * 3. Check embedding dimension compatibility (warn on mismatch).
142
+ * 4. Create `SqliteKnowledgeGraph(brain)`.
143
+ * 5. Create `SqliteMemoryGraph(brain)` and call `.initialize()`.
144
+ * 6. Create `LoaderRegistry()` (pre-registers all built-in loaders).
145
+ * 7. Create `FolderScanner(registry)`.
146
+ * 8. Create `ChunkingEngine()`.
147
+ * 9. If `selfImprove`: create `RetrievalFeedbackSignal(brain)` and
148
+ * `ConsolidationLoop(brain, memoryGraph)`.
149
+ *
150
+ * @param config - Optional configuration; see {@link MemoryConfig}.
151
+ * @returns A fully initialised Memory instance.
152
+ */
153
+ static async create(config) {
154
+ // Step 1: merge with defaults.
155
+ const randomSuffix = Math.random().toString(36).slice(2, 10);
156
+ const merged = {
157
+ store: 'sqlite',
158
+ path: path.join(os.tmpdir(), `brain-${randomSuffix}.sqlite`),
159
+ graph: true,
160
+ selfImprove: true,
161
+ decay: true,
162
+ ...config,
163
+ };
164
+ if (merged.store !== 'sqlite') {
165
+ throw new Error(`Memory currently supports only the SQLite-backed facade at runtime. ` +
166
+ `Received store="${merged.store}".`);
167
+ }
168
+ // Step 2: create SqliteBrain (async).
169
+ const brain = await SqliteBrain.open(merged.path);
170
+ // Step 3: check embedding dimension compatibility.
171
+ const dimensions = merged.embeddings?.dimensions ?? 1536;
172
+ const compatible = await brain.checkEmbeddingCompat(dimensions);
173
+ if (!compatible) {
174
+ console.warn(`[Memory] Embedding dimension mismatch: expected ${dimensions} but brain ` +
175
+ `was previously configured with a different dimension. Vector similarity ` +
176
+ `searches may produce incorrect results.`);
177
+ }
178
+ return new Memory(brain, merged);
179
+ }
167
180
  // =========================================================================
168
181
  // Core memory operations
169
182
  // =========================================================================
@@ -184,7 +197,7 @@ export class Memory {
184
197
  const type = options?.type ?? 'episodic';
185
198
  const scope = options?.scope ?? 'user';
186
199
  const scopeId = options?.scopeId ?? '';
187
- const existing = this._findExistingTraceByHash(contentHash, type, scope, scopeId);
200
+ const existing = await this._findExistingTraceByHash(contentHash, type, scope, scopeId);
188
201
  if (existing) {
189
202
  return this._buildTrace(existing);
190
203
  }
@@ -207,22 +220,28 @@ export class Memory {
207
220
  }
208
221
  }
209
222
  // Insert into memory_traces.
210
- this._brain.db
211
- .prepare(`INSERT INTO memory_traces
212
- (id, type, scope, content, embedding, strength, created_at,
213
- last_accessed, retrieval_count, tags, emotions, metadata, deleted)
214
- VALUES (?, ?, ?, ?, ?, ?, ?, NULL, 0, ?, ?, ?, 0)`)
215
- .run(id, type, scope, content, embeddingBlob, // Binary blob or null
216
- importance, now, JSON.stringify(tags), JSON.stringify({}), JSON.stringify(buildInitialTraceMetadata({}, { contentHash, entities, scopeId })));
223
+ await this._brain.run(`INSERT INTO memory_traces
224
+ (id, type, scope, content, embedding, strength, created_at,
225
+ last_accessed, retrieval_count, tags, emotions, metadata, deleted)
226
+ VALUES (?, ?, ?, ?, ?, ?, ?, NULL, 0, ?, ?, ?, 0)`, [
227
+ id,
228
+ type,
229
+ scope,
230
+ content,
231
+ embeddingBlob, // Binary blob or null
232
+ importance,
233
+ now,
234
+ JSON.stringify(tags),
235
+ JSON.stringify({}),
236
+ JSON.stringify(buildInitialTraceMetadata({}, { contentHash, entities, scopeId })),
237
+ ]);
217
238
  // Sync FTS5 index. The external-content FTS5 table needs explicit insert.
218
- this._brain.db
219
- .prepare(`INSERT INTO memory_traces_fts (rowid, content, tags)
220
- VALUES (
221
- (SELECT rowid FROM memory_traces WHERE id = ?),
222
- ?,
223
- ?
224
- )`)
225
- .run(id, content, JSON.stringify(tags));
239
+ await this._brain.run(`INSERT INTO memory_traces_fts (rowid, content, tags)
240
+ VALUES (
241
+ (SELECT rowid FROM memory_traces WHERE id = ?),
242
+ ?,
243
+ ?
244
+ )`, [id, content, JSON.stringify(tags)]);
226
245
  // Add to memory graph if available.
227
246
  if (this._config.graph) {
228
247
  await this._memoryGraph.addNode(id, {
@@ -237,20 +256,17 @@ export class Memory {
237
256
  // The embedding is stored in the memory_traces table; if it's non-null,
238
257
  // also index it in the HNSW sidecar for fast ANN recall.
239
258
  if (this._hnswSidecar) {
240
- const row = this._brain.db
241
- .prepare('SELECT embedding FROM memory_traces WHERE id = ?')
242
- .get(id);
259
+ const row = await this._brain.get('SELECT embedding FROM memory_traces WHERE id = ?', [id]);
243
260
  if (row?.embedding && row.embedding.length > 0) {
244
261
  const { blobToEmbedding, isLegacyJsonBlob } = await import('../../rag/utils/vectorMath.js');
245
262
  const vec = isLegacyJsonBlob(row.embedding)
246
263
  ? JSON.parse(row.embedding)
247
264
  : blobToEmbedding(row.embedding);
248
- const count = this._brain.db.prepare('SELECT COUNT(*) as c FROM memory_traces WHERE deleted = 0').get().c;
265
+ const countRow = await this._brain.get('SELECT COUNT(*) as c FROM memory_traces WHERE deleted = 0');
266
+ const count = countRow?.c ?? 0;
249
267
  if (!this._hnswSidecar.isActive && count >= 1000) {
250
268
  // Threshold crossed — rebuild full index from all embeddings.
251
- const allRows = this._brain.db
252
- .prepare('SELECT id, embedding FROM memory_traces WHERE deleted = 0 AND embedding IS NOT NULL')
253
- .all();
269
+ const allRows = await this._brain.all('SELECT id, embedding FROM memory_traces WHERE deleted = 0 AND embedding IS NOT NULL');
254
270
  const data = allRows
255
271
  .filter(r => r.embedding && r.embedding.length > 0)
256
272
  .map(r => ({
@@ -363,9 +379,7 @@ export class Memory {
363
379
  ORDER BY abs(fts.rank) DESC
364
380
  LIMIT ?
365
381
  `;
366
- const ftsRows = this._brain.db
367
- .prepare(ftsSql)
368
- .all(...params, ftsQuery, limit * 3);
382
+ const ftsRows = await this._brain.all(ftsSql, [...params, ftsQuery, limit * 3]);
369
383
  const ftsRank = new Map(ftsRows.map((r, i) => [r.id, i + 1]));
370
384
  // Merge all candidate IDs.
371
385
  const allIds = new Set([...hnswIds, ...ftsRows.map(r => r.id)]);
@@ -384,10 +398,8 @@ export class Memory {
384
398
  // Fetch full rows for the top candidates.
385
399
  if (topIds.length > 0) {
386
400
  const placeholders = topIds.map(() => '?').join(',');
387
- const fullRows = this._brain.db
388
- .prepare(`SELECT t.*, 0.0 as rank FROM memory_traces t WHERE t.id IN (${placeholders}) AND t.deleted = 0`)
389
- .all(...topIds);
390
- const updatedRows = this._applyRecallAccessUpdates(fullRows);
401
+ const fullRows = await this._brain.all(`SELECT t.*, 0.0 as rank FROM memory_traces t WHERE t.id IN (${placeholders}) AND t.deleted = 0`, topIds);
402
+ const updatedRows = await this._applyRecallAccessUpdates(fullRows);
391
403
  const rrfMap = new Map(scored.map(s => [s.id, s.rrfScore]));
392
404
  return updatedRows.map((row) => ({
393
405
  trace: this._buildTrace(row),
@@ -409,10 +421,8 @@ export class Memory {
409
421
  LIMIT ?
410
422
  `;
411
423
  params.push(ftsQuery, limit);
412
- const rows = this._brain.db
413
- .prepare(sql)
414
- .all(...params);
415
- const updatedRows = this._applyRecallAccessUpdates(rows);
424
+ const rows = await this._brain.all(sql, params);
425
+ const updatedRows = await this._applyRecallAccessUpdates(rows);
416
426
  return updatedRows.map((row) => ({
417
427
  trace: this._buildTrace(row),
418
428
  score: row.strength * Math.abs(row.rank),
@@ -428,9 +438,7 @@ export class Memory {
428
438
  */
429
439
  async forget(traceId) {
430
440
  await this._initPromise;
431
- this._brain.db
432
- .prepare('UPDATE memory_traces SET deleted = 1 WHERE id = ?')
433
- .run(traceId);
441
+ await this._brain.run('UPDATE memory_traces SET deleted = 1 WHERE id = ?', [traceId]);
434
442
  }
435
443
  // =========================================================================
436
444
  // Document ingestion
@@ -618,28 +626,24 @@ export class Memory {
618
626
  /**
619
627
  * Record retrieval feedback for a memory trace.
620
628
  *
621
- * Fire-and-forget: the feedback is persisted asynchronously and this method
622
- * returns immediately without waiting for the write to complete.
629
+ * The feedback is persisted asynchronously. This method returns a Promise
630
+ * that resolves once the feedback has been written.
623
631
  *
624
632
  * @param traceId - The ID of the trace being evaluated.
625
633
  * @param signal - Whether the trace was `'used'` or `'ignored'` by the LLM.
626
634
  */
627
- feedback(traceId, signal) {
635
+ async feedback(traceId, signal) {
628
636
  if (!this._feedbackSignal)
629
637
  return;
630
638
  try {
631
639
  const now = Date.now();
632
- const row = this._brain.db
633
- .prepare(`SELECT id, type, scope, content, embedding, strength, created_at,
634
- last_accessed, retrieval_count, tags, emotions, metadata, deleted
635
- FROM memory_traces
636
- WHERE id = ?
637
- LIMIT 1`)
638
- .get(traceId);
639
- this._brain.db
640
- .prepare(`INSERT INTO retrieval_feedback (trace_id, signal, query, created_at)
641
- VALUES (?, ?, NULL, ?)`)
642
- .run(traceId, signal, now);
640
+ const row = await this._brain.get(`SELECT id, type, scope, content, embedding, strength, created_at,
641
+ last_accessed, retrieval_count, tags, emotions, metadata, deleted
642
+ FROM memory_traces
643
+ WHERE id = ?
644
+ LIMIT 1`, [traceId]);
645
+ await this._brain.run(`INSERT INTO retrieval_feedback (trace_id, signal, query, created_at)
646
+ VALUES (?, ?, NULL, ?)`, [traceId, signal, now]);
643
647
  if (!row)
644
648
  return;
645
649
  if (signal === 'used') {
@@ -650,11 +654,15 @@ export class Memory {
650
654
  reinforcementInterval: update.reinforcementInterval,
651
655
  nextReinforcementAt: update.nextReinforcementAt,
652
656
  }));
653
- this._brain.db
654
- .prepare(`UPDATE memory_traces
655
- SET strength = ?, last_accessed = ?, retrieval_count = ?, metadata = ?
656
- WHERE id = ?`)
657
- .run(update.encodingStrength, update.lastAccessedAt, update.retrievalCount, metadata, traceId);
657
+ await this._brain.run(`UPDATE memory_traces
658
+ SET strength = ?, last_accessed = ?, retrieval_count = ?, metadata = ?
659
+ WHERE id = ?`, [
660
+ update.encodingStrength,
661
+ update.lastAccessedAt,
662
+ update.retrievalCount,
663
+ metadata,
664
+ traceId,
665
+ ]);
658
666
  return;
659
667
  }
660
668
  const penalty = penalizeUnused(this._buildTrace(row), now);
@@ -667,11 +675,9 @@ export class Memory {
667
675
  ? { nextReinforcementAt: existingDecay.nextReinforcementAt }
668
676
  : {}),
669
677
  }));
670
- this._brain.db
671
- .prepare(`UPDATE memory_traces
672
- SET strength = ?, last_accessed = ?, metadata = ?
673
- WHERE id = ?`)
674
- .run(penalty.encodingStrength, penalty.lastAccessedAt, metadata, traceId);
678
+ await this._brain.run(`UPDATE memory_traces
679
+ SET strength = ?, last_accessed = ?, metadata = ?
680
+ WHERE id = ?`, [penalty.encodingStrength, penalty.lastAccessedAt, metadata, traceId]);
675
681
  }
676
682
  catch {
677
683
  // Explicit feedback is best-effort; the caller should not fail on analytics updates.
@@ -758,7 +764,7 @@ export class Memory {
758
764
  break;
759
765
  }
760
766
  if (result.imported > 0) {
761
- this._rebuildFtsIndex();
767
+ await this._rebuildFtsIndex();
762
768
  }
763
769
  return result;
764
770
  }
@@ -807,63 +813,42 @@ export class Memory {
807
813
  */
808
814
  async health() {
809
815
  await this._initPromise;
810
- const db = this._brain.db;
811
816
  // Total traces (active + deleted).
812
- const totalRow = db
813
- .prepare('SELECT COUNT(*) AS cnt FROM memory_traces')
814
- .get();
817
+ const totalRow = await this._brain.get('SELECT COUNT(*) AS cnt FROM memory_traces');
815
818
  const totalTraces = totalRow?.cnt ?? 0;
816
819
  // Active traces (not deleted).
817
- const activeRow = db
818
- .prepare('SELECT COUNT(*) AS cnt FROM memory_traces WHERE deleted = 0')
819
- .get();
820
+ const activeRow = await this._brain.get('SELECT COUNT(*) AS cnt FROM memory_traces WHERE deleted = 0');
820
821
  const activeTraces = activeRow?.cnt ?? 0;
821
822
  // Average strength of active traces.
822
- const avgRow = db
823
- .prepare('SELECT AVG(strength) AS avg_s FROM memory_traces WHERE deleted = 0')
824
- .get();
823
+ const avgRow = await this._brain.get('SELECT AVG(strength) AS avg_s FROM memory_traces WHERE deleted = 0');
825
824
  const avgStrength = avgRow?.avg_s ?? 0;
826
825
  // Weakest active trace.
827
- const weakRow = db
828
- .prepare('SELECT MIN(strength) AS min_s FROM memory_traces WHERE deleted = 0')
829
- .get();
826
+ const weakRow = await this._brain.get('SELECT MIN(strength) AS min_s FROM memory_traces WHERE deleted = 0');
830
827
  const weakestTraceStrength = weakRow?.min_s ?? 0;
831
828
  // Knowledge graph counts.
832
- const nodesRow = db
833
- .prepare('SELECT COUNT(*) AS cnt FROM knowledge_nodes')
834
- .get();
829
+ const nodesRow = await this._brain.get('SELECT COUNT(*) AS cnt FROM knowledge_nodes');
835
830
  const graphNodes = nodesRow?.cnt ?? 0;
836
- const edgesRow = db
837
- .prepare('SELECT COUNT(*) AS cnt FROM knowledge_edges')
838
- .get();
831
+ const edgesRow = await this._brain.get('SELECT COUNT(*) AS cnt FROM knowledge_edges');
839
832
  const graphEdges = edgesRow?.cnt ?? 0;
840
833
  // Last consolidation timestamp.
841
- const lastConsolRow = db
842
- .prepare('SELECT ran_at FROM consolidation_log ORDER BY ran_at DESC LIMIT 1')
843
- .get();
834
+ const lastConsolRow = await this._brain.get('SELECT ran_at FROM consolidation_log ORDER BY ran_at DESC LIMIT 1');
844
835
  const lastConsolidation = lastConsolRow
845
836
  ? new Date(lastConsolRow.ran_at).toISOString()
846
837
  : null;
847
838
  // Traces per type.
848
- const typeRows = db
849
- .prepare('SELECT type, COUNT(*) AS cnt FROM memory_traces WHERE deleted = 0 GROUP BY type')
850
- .all();
839
+ const typeRows = await this._brain.all('SELECT type, COUNT(*) AS cnt FROM memory_traces WHERE deleted = 0 GROUP BY type');
851
840
  const tracesPerType = {};
852
841
  for (const row of typeRows) {
853
842
  tracesPerType[row.type] = row.cnt;
854
843
  }
855
844
  // Traces per scope.
856
- const scopeRows = db
857
- .prepare('SELECT scope, COUNT(*) AS cnt FROM memory_traces WHERE deleted = 0 GROUP BY scope')
858
- .all();
845
+ const scopeRows = await this._brain.all('SELECT scope, COUNT(*) AS cnt FROM memory_traces WHERE deleted = 0 GROUP BY scope');
859
846
  const tracesPerScope = {};
860
847
  for (const row of scopeRows) {
861
848
  tracesPerScope[row.scope] = row.cnt;
862
849
  }
863
850
  // Total document chunks.
864
- const docsRow = db
865
- .prepare('SELECT COUNT(*) AS cnt FROM documents')
866
- .get();
851
+ const docsRow = await this._brain.get('SELECT COUNT(*) AS cnt FROM documents');
867
852
  const documentsIngested = docsRow?.cnt ?? 0;
868
853
  return {
869
854
  totalTraces,
@@ -889,7 +874,7 @@ export class Memory {
889
874
  */
890
875
  async close() {
891
876
  await this._initPromise;
892
- this._brain.close();
877
+ await this._brain.close();
893
878
  }
894
879
  // =========================================================================
895
880
  // Private helpers
@@ -956,50 +941,58 @@ export class Memory {
956
941
  * importer-used `import_hash` key so dedup works across facade and import
957
942
  * workflows.
958
943
  */
959
- _findExistingTraceByHash(contentHash, type, scope, scopeId) {
960
- return this._brain.db
961
- .prepare(`SELECT id, type, scope, content, embedding, strength, created_at,
962
- last_accessed, retrieval_count, tags, emotions, metadata, deleted
963
- FROM memory_traces
964
- WHERE deleted = 0
965
- AND type = ?
966
- AND scope = ?
967
- AND ifnull(json_extract(metadata, '$.scopeId'), '') = ?
968
- AND (
969
- json_extract(metadata, '$.content_hash') = ?
970
- OR json_extract(metadata, '$.import_hash') = ?
971
- )
972
- LIMIT 1`)
973
- .get(type, scope, scopeId, contentHash, contentHash);
944
+ async _findExistingTraceByHash(contentHash, type, scope, scopeId) {
945
+ const row = await this._brain.get(`SELECT id, type, scope, content, embedding, strength, created_at,
946
+ last_accessed, retrieval_count, tags, emotions, metadata, deleted
947
+ FROM memory_traces
948
+ WHERE deleted = 0
949
+ AND type = ?
950
+ AND scope = ?
951
+ AND ifnull(json_extract(metadata, '$.scopeId'), '') = ?
952
+ AND (
953
+ json_extract(metadata, '$.content_hash') = ?
954
+ OR json_extract(metadata, '$.import_hash') = ?
955
+ )
956
+ LIMIT 1`, [type, scope, scopeId, contentHash, contentHash]);
957
+ return row ?? undefined;
974
958
  }
975
959
  /**
976
960
  * Apply spaced-repetition access updates to recalled rows and persist the
977
961
  * updated retrieval metadata back to SQLite.
978
962
  */
979
- _applyRecallAccessUpdates(rows) {
963
+ async _applyRecallAccessUpdates(rows) {
980
964
  if (rows.length === 0)
981
965
  return rows;
982
966
  const now = Date.now();
983
- const updateStmt = this._brain.db.prepare(`UPDATE memory_traces
984
- SET strength = ?, last_accessed = ?, retrieval_count = ?, metadata = ?
985
- WHERE id = ?`);
986
- return this._brain.db.transaction(() => rows.map((row) => {
987
- const update = updateOnRetrieval(this._buildTrace(row), now);
988
- const metadata = JSON.stringify(withPersistedDecayState(parseTraceMetadata(row.metadata), {
989
- stability: update.stability,
990
- accessCount: update.accessCount,
991
- reinforcementInterval: update.reinforcementInterval,
992
- nextReinforcementAt: update.nextReinforcementAt,
993
- }));
994
- updateStmt.run(update.encodingStrength, update.lastAccessedAt, update.retrievalCount, metadata, row.id);
995
- return {
996
- ...row,
997
- strength: update.encodingStrength,
998
- last_accessed: update.lastAccessedAt,
999
- retrieval_count: update.retrievalCount,
1000
- metadata,
1001
- };
1002
- }))();
967
+ return this._brain.transaction(async (trx) => {
968
+ const results = [];
969
+ for (const row of rows) {
970
+ const update = updateOnRetrieval(this._buildTrace(row), now);
971
+ const metadata = JSON.stringify(withPersistedDecayState(parseTraceMetadata(row.metadata), {
972
+ stability: update.stability,
973
+ accessCount: update.accessCount,
974
+ reinforcementInterval: update.reinforcementInterval,
975
+ nextReinforcementAt: update.nextReinforcementAt,
976
+ }));
977
+ await trx.run(`UPDATE memory_traces
978
+ SET strength = ?, last_accessed = ?, retrieval_count = ?, metadata = ?
979
+ WHERE id = ?`, [
980
+ update.encodingStrength,
981
+ update.lastAccessedAt,
982
+ update.retrievalCount,
983
+ metadata,
984
+ row.id,
985
+ ]);
986
+ results.push({
987
+ ...row,
988
+ strength: update.encodingStrength,
989
+ last_accessed: update.lastAccessedAt,
990
+ retrieval_count: update.retrievalCount,
991
+ metadata,
992
+ });
993
+ }
994
+ return results;
995
+ });
1003
996
  }
1004
997
  /**
1005
998
  * Persist one loaded document into the documents/chunks/traces tables.
@@ -1009,40 +1002,46 @@ export class Memory {
1009
1002
  */
1010
1003
  async _ingestLoadedDocument(source, doc, chunking, result) {
1011
1004
  const contentHash = sha256(doc.content);
1012
- const existingDoc = this._brain.db
1013
- .prepare(`SELECT id FROM documents WHERE content_hash = ? LIMIT 1`)
1014
- .get(contentHash);
1005
+ const existingDoc = await this._brain.get(`SELECT id FROM documents WHERE content_hash = ? LIMIT 1`, [contentHash]);
1015
1006
  if (existingDoc) {
1016
1007
  return;
1017
1008
  }
1018
1009
  const chunks = await this._chunkingEngine.chunk(doc.content, chunking);
1019
1010
  const docId = `doc_${crypto.randomUUID()}`;
1020
- this._brain.db
1021
- .prepare(`INSERT INTO documents
1022
- (id, path, format, title, content_hash, chunk_count, metadata, ingested_at)
1023
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
1024
- .run(docId, doc.metadata.source ?? source, doc.format, doc.metadata.title ?? null, contentHash, chunks.length, JSON.stringify(doc.metadata), Date.now());
1011
+ await this._brain.run(`INSERT INTO documents
1012
+ (id, path, format, title, content_hash, chunk_count, metadata, ingested_at)
1013
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
1014
+ docId,
1015
+ doc.metadata.source ?? source,
1016
+ doc.format,
1017
+ doc.metadata.title ?? null,
1018
+ contentHash,
1019
+ chunks.length,
1020
+ JSON.stringify(doc.metadata),
1021
+ Date.now(),
1022
+ ]);
1025
1023
  for (const chunk of chunks) {
1026
1024
  const chunkId = `chunk_${crypto.randomUUID()}`;
1027
1025
  const traceId = nextTraceId();
1028
1026
  const createdAt = Date.now();
1029
- this._brain.db
1030
- .prepare(`INSERT INTO memory_traces
1031
- (id, type, scope, content, embedding, strength, created_at,
1032
- last_accessed, retrieval_count, tags, emotions, metadata, deleted)
1033
- VALUES (?, 'semantic', 'user', ?, NULL, 1.0, ?, NULL, 0, '[]', '{}', ?, 0)`)
1034
- .run(traceId, chunk.content, createdAt, JSON.stringify(buildInitialTraceMetadata({
1035
- document_id: docId,
1036
- chunk_index: chunk.index,
1037
- }, { contentHash: sha256(chunk.content) })));
1038
- this._brain.db
1039
- .prepare(`INSERT INTO memory_traces_fts (rowid, content, tags)
1040
- VALUES (
1041
- (SELECT rowid FROM memory_traces WHERE id = ?),
1042
- ?,
1043
- '[]'
1044
- )`)
1045
- .run(traceId, chunk.content);
1027
+ await this._brain.run(`INSERT INTO memory_traces
1028
+ (id, type, scope, content, embedding, strength, created_at,
1029
+ last_accessed, retrieval_count, tags, emotions, metadata, deleted)
1030
+ VALUES (?, 'semantic', 'user', ?, NULL, 1.0, ?, NULL, 0, '[]', '{}', ?, 0)`, [
1031
+ traceId,
1032
+ chunk.content,
1033
+ createdAt,
1034
+ JSON.stringify(buildInitialTraceMetadata({
1035
+ document_id: docId,
1036
+ chunk_index: chunk.index,
1037
+ }, { contentHash: sha256(chunk.content) })),
1038
+ ]);
1039
+ await this._brain.run(`INSERT INTO memory_traces_fts (rowid, content, tags)
1040
+ VALUES (
1041
+ (SELECT rowid FROM memory_traces WHERE id = ?),
1042
+ ?,
1043
+ '[]'
1044
+ )`, [traceId, chunk.content]);
1046
1045
  if (this._config.graph && !this._memoryGraph.hasNode(traceId)) {
1047
1046
  await this._memoryGraph.addNode(traceId, {
1048
1047
  type: 'semantic',
@@ -1052,10 +1051,15 @@ export class Memory {
1052
1051
  createdAt,
1053
1052
  });
1054
1053
  }
1055
- this._brain.db
1056
- .prepare(`INSERT INTO document_chunks (id, document_id, trace_id, content, chunk_index, page_number, embedding)
1057
- VALUES (?, ?, ?, ?, ?, ?, NULL)`)
1058
- .run(chunkId, docId, traceId, chunk.content, chunk.index, chunk.pageNumber ?? null);
1054
+ await this._brain.run(`INSERT INTO document_chunks (id, document_id, trace_id, content, chunk_index, page_number, embedding)
1055
+ VALUES (?, ?, ?, ?, ?, ?, NULL)`, [
1056
+ chunkId,
1057
+ docId,
1058
+ traceId,
1059
+ chunk.content,
1060
+ chunk.index,
1061
+ chunk.pageNumber ?? null,
1062
+ ]);
1059
1063
  result.chunksCreated++;
1060
1064
  result.tracesCreated++;
1061
1065
  }
@@ -1063,9 +1067,9 @@ export class Memory {
1063
1067
  /**
1064
1068
  * Rebuild the external-content FTS index after bulk import operations.
1065
1069
  */
1066
- _rebuildFtsIndex() {
1070
+ async _rebuildFtsIndex() {
1067
1071
  try {
1068
- this._brain.db.exec(`INSERT INTO memory_traces_fts(memory_traces_fts) VALUES('rebuild')`);
1072
+ await this._brain.exec(`INSERT INTO memory_traces_fts(memory_traces_fts) VALUES('rebuild')`);
1069
1073
  }
1070
1074
  catch {
1071
1075
  // Best-effort; imports still succeed even if the FTS rebuild is unavailable.