@framers/agentos 0.1.101 → 0.1.102

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 (176) hide show
  1. package/README.md +16 -0
  2. package/dist/memory/config.d.ts +39 -0
  3. package/dist/memory/config.d.ts.map +1 -1
  4. package/dist/memory/config.js.map +1 -1
  5. package/dist/memory/consolidation/ConsolidationLoop.d.ts +177 -0
  6. package/dist/memory/consolidation/ConsolidationLoop.d.ts.map +1 -0
  7. package/dist/memory/consolidation/ConsolidationLoop.js +517 -0
  8. package/dist/memory/consolidation/ConsolidationLoop.js.map +1 -0
  9. package/dist/memory/consolidation/ConsolidationPipeline.d.ts.map +1 -1
  10. package/dist/memory/consolidation/ConsolidationPipeline.js +7 -0
  11. package/dist/memory/consolidation/ConsolidationPipeline.js.map +1 -1
  12. package/dist/memory/consolidation/index.d.ts +8 -0
  13. package/dist/memory/consolidation/index.d.ts.map +1 -0
  14. package/dist/memory/consolidation/index.js +7 -0
  15. package/dist/memory/consolidation/index.js.map +1 -0
  16. package/dist/memory/decay/DecayModel.d.ts +33 -0
  17. package/dist/memory/decay/DecayModel.d.ts.map +1 -1
  18. package/dist/memory/decay/DecayModel.js +31 -0
  19. package/dist/memory/decay/DecayModel.js.map +1 -1
  20. package/dist/memory/facade/Memory.d.ts +228 -0
  21. package/dist/memory/facade/Memory.d.ts.map +1 -0
  22. package/dist/memory/facade/Memory.js +823 -0
  23. package/dist/memory/facade/Memory.js.map +1 -0
  24. package/dist/memory/facade/index.d.ts +13 -0
  25. package/dist/memory/facade/index.d.ts.map +1 -0
  26. package/dist/memory/facade/index.js +11 -0
  27. package/dist/memory/facade/index.js.map +1 -0
  28. package/dist/memory/facade/types.d.ts +606 -0
  29. package/dist/memory/facade/types.d.ts.map +1 -0
  30. package/dist/memory/facade/types.js +11 -0
  31. package/dist/memory/facade/types.js.map +1 -0
  32. package/dist/memory/feedback/RetrievalFeedbackSignal.d.ts +132 -0
  33. package/dist/memory/feedback/RetrievalFeedbackSignal.d.ts.map +1 -0
  34. package/dist/memory/feedback/RetrievalFeedbackSignal.js +178 -0
  35. package/dist/memory/feedback/RetrievalFeedbackSignal.js.map +1 -0
  36. package/dist/memory/feedback/index.d.ts +13 -0
  37. package/dist/memory/feedback/index.d.ts.map +1 -0
  38. package/dist/memory/feedback/index.js +12 -0
  39. package/dist/memory/feedback/index.js.map +1 -0
  40. package/dist/memory/index.d.ts +22 -0
  41. package/dist/memory/index.d.ts.map +1 -1
  42. package/dist/memory/index.js +24 -0
  43. package/dist/memory/index.js.map +1 -1
  44. package/dist/memory/ingestion/ChunkingEngine.d.ts +143 -0
  45. package/dist/memory/ingestion/ChunkingEngine.d.ts.map +1 -0
  46. package/dist/memory/ingestion/ChunkingEngine.js +508 -0
  47. package/dist/memory/ingestion/ChunkingEngine.js.map +1 -0
  48. package/dist/memory/ingestion/DoclingLoader.d.ts +44 -0
  49. package/dist/memory/ingestion/DoclingLoader.d.ts.map +1 -0
  50. package/dist/memory/ingestion/DoclingLoader.js +228 -0
  51. package/dist/memory/ingestion/DoclingLoader.js.map +1 -0
  52. package/dist/memory/ingestion/DocxLoader.d.ts +37 -0
  53. package/dist/memory/ingestion/DocxLoader.d.ts.map +1 -0
  54. package/dist/memory/ingestion/DocxLoader.js +111 -0
  55. package/dist/memory/ingestion/DocxLoader.js.map +1 -0
  56. package/dist/memory/ingestion/FolderScanner.d.ts +116 -0
  57. package/dist/memory/ingestion/FolderScanner.d.ts.map +1 -0
  58. package/dist/memory/ingestion/FolderScanner.js +127 -0
  59. package/dist/memory/ingestion/FolderScanner.js.map +1 -0
  60. package/dist/memory/ingestion/HtmlLoader.d.ts +49 -0
  61. package/dist/memory/ingestion/HtmlLoader.d.ts.map +1 -0
  62. package/dist/memory/ingestion/HtmlLoader.js +202 -0
  63. package/dist/memory/ingestion/HtmlLoader.js.map +1 -0
  64. package/dist/memory/ingestion/IDocumentLoader.d.ts +63 -0
  65. package/dist/memory/ingestion/IDocumentLoader.d.ts.map +1 -0
  66. package/dist/memory/ingestion/IDocumentLoader.js +11 -0
  67. package/dist/memory/ingestion/IDocumentLoader.js.map +1 -0
  68. package/dist/memory/ingestion/LoaderRegistry.d.ts +140 -0
  69. package/dist/memory/ingestion/LoaderRegistry.d.ts.map +1 -0
  70. package/dist/memory/ingestion/LoaderRegistry.js +229 -0
  71. package/dist/memory/ingestion/LoaderRegistry.js.map +1 -0
  72. package/dist/memory/ingestion/MarkdownLoader.d.ts +50 -0
  73. package/dist/memory/ingestion/MarkdownLoader.d.ts.map +1 -0
  74. package/dist/memory/ingestion/MarkdownLoader.js +169 -0
  75. package/dist/memory/ingestion/MarkdownLoader.js.map +1 -0
  76. package/dist/memory/ingestion/MultimodalAggregator.d.ts +88 -0
  77. package/dist/memory/ingestion/MultimodalAggregator.d.ts.map +1 -0
  78. package/dist/memory/ingestion/MultimodalAggregator.js +96 -0
  79. package/dist/memory/ingestion/MultimodalAggregator.js.map +1 -0
  80. package/dist/memory/ingestion/OcrPdfLoader.d.ts +41 -0
  81. package/dist/memory/ingestion/OcrPdfLoader.d.ts.map +1 -0
  82. package/dist/memory/ingestion/OcrPdfLoader.js +149 -0
  83. package/dist/memory/ingestion/OcrPdfLoader.js.map +1 -0
  84. package/dist/memory/ingestion/PdfLoader.d.ts +78 -0
  85. package/dist/memory/ingestion/PdfLoader.d.ts.map +1 -0
  86. package/dist/memory/ingestion/PdfLoader.js +179 -0
  87. package/dist/memory/ingestion/PdfLoader.js.map +1 -0
  88. package/dist/memory/ingestion/TextLoader.d.ts +66 -0
  89. package/dist/memory/ingestion/TextLoader.d.ts.map +1 -0
  90. package/dist/memory/ingestion/TextLoader.js +207 -0
  91. package/dist/memory/ingestion/TextLoader.js.map +1 -0
  92. package/dist/memory/ingestion/UrlLoader.d.ts +95 -0
  93. package/dist/memory/ingestion/UrlLoader.d.ts.map +1 -0
  94. package/dist/memory/ingestion/UrlLoader.js +174 -0
  95. package/dist/memory/ingestion/UrlLoader.js.map +1 -0
  96. package/dist/memory/io/ChatGptImporter.d.ts +85 -0
  97. package/dist/memory/io/ChatGptImporter.d.ts.map +1 -0
  98. package/dist/memory/io/ChatGptImporter.js +231 -0
  99. package/dist/memory/io/ChatGptImporter.js.map +1 -0
  100. package/dist/memory/io/JsonExporter.d.ts +67 -0
  101. package/dist/memory/io/JsonExporter.d.ts.map +1 -0
  102. package/dist/memory/io/JsonExporter.js +132 -0
  103. package/dist/memory/io/JsonExporter.js.map +1 -0
  104. package/dist/memory/io/JsonImporter.d.ts +84 -0
  105. package/dist/memory/io/JsonImporter.d.ts.map +1 -0
  106. package/dist/memory/io/JsonImporter.js +234 -0
  107. package/dist/memory/io/JsonImporter.js.map +1 -0
  108. package/dist/memory/io/MarkdownExporter.d.ts +95 -0
  109. package/dist/memory/io/MarkdownExporter.d.ts.map +1 -0
  110. package/dist/memory/io/MarkdownExporter.js +130 -0
  111. package/dist/memory/io/MarkdownExporter.js.map +1 -0
  112. package/dist/memory/io/MarkdownImporter.d.ts +84 -0
  113. package/dist/memory/io/MarkdownImporter.d.ts.map +1 -0
  114. package/dist/memory/io/MarkdownImporter.js +166 -0
  115. package/dist/memory/io/MarkdownImporter.js.map +1 -0
  116. package/dist/memory/io/ObsidianExporter.d.ts +80 -0
  117. package/dist/memory/io/ObsidianExporter.d.ts.map +1 -0
  118. package/dist/memory/io/ObsidianExporter.js +127 -0
  119. package/dist/memory/io/ObsidianExporter.js.map +1 -0
  120. package/dist/memory/io/ObsidianImporter.d.ts +93 -0
  121. package/dist/memory/io/ObsidianImporter.d.ts.map +1 -0
  122. package/dist/memory/io/ObsidianImporter.js +221 -0
  123. package/dist/memory/io/ObsidianImporter.js.map +1 -0
  124. package/dist/memory/io/SqliteExporter.d.ts +47 -0
  125. package/dist/memory/io/SqliteExporter.d.ts.map +1 -0
  126. package/dist/memory/io/SqliteExporter.js +56 -0
  127. package/dist/memory/io/SqliteExporter.js.map +1 -0
  128. package/dist/memory/io/SqliteImporter.d.ts +82 -0
  129. package/dist/memory/io/SqliteImporter.d.ts.map +1 -0
  130. package/dist/memory/io/SqliteImporter.js +232 -0
  131. package/dist/memory/io/SqliteImporter.js.map +1 -0
  132. package/dist/memory/io/index.d.ts +31 -0
  133. package/dist/memory/io/index.d.ts.map +1 -0
  134. package/dist/memory/io/index.js +31 -0
  135. package/dist/memory/io/index.js.map +1 -0
  136. package/dist/memory/store/SqliteBrain.d.ts +125 -0
  137. package/dist/memory/store/SqliteBrain.d.ts.map +1 -0
  138. package/dist/memory/store/SqliteBrain.js +407 -0
  139. package/dist/memory/store/SqliteBrain.js.map +1 -0
  140. package/dist/memory/store/SqliteKnowledgeGraph.d.ts +259 -0
  141. package/dist/memory/store/SqliteKnowledgeGraph.d.ts.map +1 -0
  142. package/dist/memory/store/SqliteKnowledgeGraph.js +1062 -0
  143. package/dist/memory/store/SqliteKnowledgeGraph.js.map +1 -0
  144. package/dist/memory/store/SqliteMemoryGraph.d.ts +251 -0
  145. package/dist/memory/store/SqliteMemoryGraph.d.ts.map +1 -0
  146. package/dist/memory/store/SqliteMemoryGraph.js +637 -0
  147. package/dist/memory/store/SqliteMemoryGraph.js.map +1 -0
  148. package/dist/memory/tools/MemoryAddTool.d.ts +98 -0
  149. package/dist/memory/tools/MemoryAddTool.d.ts.map +1 -0
  150. package/dist/memory/tools/MemoryAddTool.js +131 -0
  151. package/dist/memory/tools/MemoryAddTool.js.map +1 -0
  152. package/dist/memory/tools/MemoryDeleteTool.d.ts +83 -0
  153. package/dist/memory/tools/MemoryDeleteTool.d.ts.map +1 -0
  154. package/dist/memory/tools/MemoryDeleteTool.js +96 -0
  155. package/dist/memory/tools/MemoryDeleteTool.js.map +1 -0
  156. package/dist/memory/tools/MemoryMergeTool.d.ts +95 -0
  157. package/dist/memory/tools/MemoryMergeTool.d.ts.map +1 -0
  158. package/dist/memory/tools/MemoryMergeTool.js +164 -0
  159. package/dist/memory/tools/MemoryMergeTool.js.map +1 -0
  160. package/dist/memory/tools/MemoryReflectTool.d.ts +86 -0
  161. package/dist/memory/tools/MemoryReflectTool.d.ts.map +1 -0
  162. package/dist/memory/tools/MemoryReflectTool.js +102 -0
  163. package/dist/memory/tools/MemoryReflectTool.js.map +1 -0
  164. package/dist/memory/tools/MemorySearchTool.d.ts +117 -0
  165. package/dist/memory/tools/MemorySearchTool.d.ts.map +1 -0
  166. package/dist/memory/tools/MemorySearchTool.js +162 -0
  167. package/dist/memory/tools/MemorySearchTool.js.map +1 -0
  168. package/dist/memory/tools/MemoryUpdateTool.d.ts +92 -0
  169. package/dist/memory/tools/MemoryUpdateTool.d.ts.map +1 -0
  170. package/dist/memory/tools/MemoryUpdateTool.js +125 -0
  171. package/dist/memory/tools/MemoryUpdateTool.js.map +1 -0
  172. package/dist/memory/tools/index.d.ts +32 -0
  173. package/dist/memory/tools/index.d.ts.map +1 -0
  174. package/dist/memory/tools/index.js +26 -0
  175. package/dist/memory/tools/index.js.map +1 -0
  176. package/package.json +6 -1
@@ -0,0 +1,823 @@
1
+ /**
2
+ * @fileoverview Memory facade -- unified public API for the complete memory system.
3
+ *
4
+ * The `Memory` class wires together every subsystem built in Tasks 1-12:
5
+ * - SqliteBrain (unified SQLite connection with WAL, full schema)
6
+ * - SqliteKnowledgeGraph (IKnowledgeGraph backed by SQLite)
7
+ * - SqliteMemoryGraph (IMemoryGraph with spreading activation)
8
+ * - LoaderRegistry (document loaders: text, md, html, pdf, docx)
9
+ * - FolderScanner (recursive directory scanning)
10
+ * - ChunkingEngine (4 chunking strategies)
11
+ * - RetrievalFeedbackSignal (used/ignored detection)
12
+ * - ConsolidationLoop (6-step self-improvement)
13
+ * - I/O exporters and importers (JSON, Markdown, Obsidian, SQLite, ChatGPT)
14
+ *
15
+ * Consumers only need to import this single class to interact with the entire
16
+ * memory subsystem.
17
+ *
18
+ * @module memory/facade/Memory
19
+ */
20
+ import crypto from 'node:crypto';
21
+ import fs from 'node:fs/promises';
22
+ import os from 'node:os';
23
+ import path from 'node:path';
24
+ import { SqliteBrain } from '../store/SqliteBrain.js';
25
+ import { SqliteKnowledgeGraph } from '../store/SqliteKnowledgeGraph.js';
26
+ import { SqliteMemoryGraph } from '../store/SqliteMemoryGraph.js';
27
+ import { LoaderRegistry } from '../ingestion/LoaderRegistry.js';
28
+ import { FolderScanner } from '../ingestion/FolderScanner.js';
29
+ import { ChunkingEngine } from '../ingestion/ChunkingEngine.js';
30
+ import { RetrievalFeedbackSignal } from '../feedback/RetrievalFeedbackSignal.js';
31
+ import { ConsolidationLoop } from '../consolidation/ConsolidationLoop.js';
32
+ import { JsonExporter, JsonImporter, MarkdownExporter, MarkdownImporter, ObsidianExporter, ObsidianImporter, SqliteExporter, SqliteImporter, ChatGptImporter, } from '../io/index.js';
33
+ // ---------------------------------------------------------------------------
34
+ // Constants & defaults
35
+ // ---------------------------------------------------------------------------
36
+ /** Monotonically increasing counter for trace IDs. */
37
+ let _traceCounter = 0;
38
+ /**
39
+ * Generate a unique, collision-free trace ID.
40
+ *
41
+ * Format: `mt_{timestamp}_{counter}` where the counter resets at process
42
+ * start but is never reused within a single process lifetime.
43
+ */
44
+ function nextTraceId() {
45
+ return `mt_${Date.now()}_${_traceCounter++}`;
46
+ }
47
+ /**
48
+ * Compute SHA-256 hex digest of a string.
49
+ */
50
+ function sha256(content) {
51
+ return crypto.createHash('sha256').update(content).digest('hex');
52
+ }
53
+ // ---------------------------------------------------------------------------
54
+ // Memory facade
55
+ // ---------------------------------------------------------------------------
56
+ /**
57
+ * Unified public API for the AgentOS memory system.
58
+ *
59
+ * One `Memory` instance manages the full lifecycle of an agent's memories:
60
+ * storing, retrieving, ingesting documents, building a knowledge graph,
61
+ * self-improving through consolidation, and importing/exporting data.
62
+ *
63
+ * ## Quick start
64
+ * ```ts
65
+ * const mem = new Memory({ store: 'sqlite', path: './brain.sqlite' });
66
+ *
67
+ * await mem.remember('The user prefers dark mode');
68
+ * const results = await mem.recall('dark mode preference');
69
+ * console.log(results[0].trace.content);
70
+ *
71
+ * await mem.close();
72
+ * ```
73
+ */
74
+ export class Memory {
75
+ // -------------------------------------------------------------------
76
+ // Constructor
77
+ // -------------------------------------------------------------------
78
+ /**
79
+ * Create a new Memory instance and wire together all subsystems.
80
+ *
81
+ * Initialization sequence:
82
+ * 1. Merge `config` with defaults (store='sqlite', path=tmpdir, graph=true,
83
+ * selfImprove=true, decay=true).
84
+ * 2. Create `SqliteBrain(config.path)`.
85
+ * 3. Check embedding dimension compatibility (warn on mismatch).
86
+ * 4. Create `SqliteKnowledgeGraph(brain)`.
87
+ * 5. Create `SqliteMemoryGraph(brain)` and call `.initialize()`.
88
+ * 6. Create `LoaderRegistry()` (pre-registers all built-in loaders).
89
+ * 7. Create `FolderScanner(registry)`.
90
+ * 8. Create `ChunkingEngine()`.
91
+ * 9. If `selfImprove`: create `RetrievalFeedbackSignal(brain)` and
92
+ * `ConsolidationLoop(brain, memoryGraph)`.
93
+ *
94
+ * @param config - Optional configuration; see {@link MemoryConfig}.
95
+ */
96
+ constructor(config) {
97
+ // Step 1: merge with defaults.
98
+ const randomSuffix = Math.random().toString(36).slice(2, 10);
99
+ this._config = {
100
+ store: 'sqlite',
101
+ path: path.join(os.tmpdir(), `brain-${randomSuffix}.sqlite`),
102
+ graph: true,
103
+ selfImprove: true,
104
+ decay: true,
105
+ ...config,
106
+ };
107
+ // Step 2: create SqliteBrain.
108
+ this._brain = new SqliteBrain(this._config.path);
109
+ // Step 3: check embedding dimension compatibility.
110
+ const dimensions = this._config.embeddings?.dimensions ?? 1536;
111
+ const compatible = this._brain.checkEmbeddingCompat(dimensions);
112
+ if (!compatible) {
113
+ console.warn(`[Memory] Embedding dimension mismatch: expected ${dimensions} but brain ` +
114
+ `was previously configured with a different dimension. Vector similarity ` +
115
+ `searches may produce incorrect results.`);
116
+ }
117
+ // Step 4: create SqliteKnowledgeGraph.
118
+ this._knowledgeGraph = new SqliteKnowledgeGraph(this._brain);
119
+ // Step 5: create SqliteMemoryGraph and initialize.
120
+ this._memoryGraph = new SqliteMemoryGraph(this._brain);
121
+ this._initPromise = this._memoryGraph.initialize();
122
+ // Step 6: create LoaderRegistry.
123
+ this._loaderRegistry = new LoaderRegistry();
124
+ // Step 7: create FolderScanner.
125
+ this._folderScanner = new FolderScanner(this._loaderRegistry);
126
+ // Step 8: create ChunkingEngine.
127
+ this._chunkingEngine = new ChunkingEngine();
128
+ // Step 9: self-improvement subsystems.
129
+ if (this._config.selfImprove) {
130
+ this._feedbackSignal = new RetrievalFeedbackSignal(this._brain);
131
+ this._consolidationLoop = new ConsolidationLoop(this._brain, this._memoryGraph);
132
+ }
133
+ else {
134
+ this._feedbackSignal = null;
135
+ this._consolidationLoop = null;
136
+ }
137
+ }
138
+ // =========================================================================
139
+ // Core memory operations
140
+ // =========================================================================
141
+ /**
142
+ * Store a new memory trace.
143
+ *
144
+ * Creates a trace in the `memory_traces` table with a unique ID, content
145
+ * hash for deduplication, and optional type/scope/tags metadata. If the
146
+ * memory graph is available the trace is also added as a graph node.
147
+ *
148
+ * @param content - The text content to remember.
149
+ * @param options - Optional metadata (type, scope, tags, importance, etc.).
150
+ * @returns The created MemoryTrace-like object.
151
+ */
152
+ async remember(content, options) {
153
+ await this._initPromise;
154
+ const id = nextTraceId();
155
+ const now = Date.now();
156
+ const type = options?.type ?? 'episodic';
157
+ const scope = options?.scope ?? 'user';
158
+ const scopeId = options?.scopeId ?? '';
159
+ const tags = options?.tags ?? [];
160
+ const entities = options?.entities ?? [];
161
+ const importance = options?.importance ?? 1.0;
162
+ const contentHash = sha256(content);
163
+ // Insert into memory_traces.
164
+ this._brain.db
165
+ .prepare(`INSERT INTO memory_traces
166
+ (id, type, scope, content, embedding, strength, created_at,
167
+ last_accessed, retrieval_count, tags, emotions, metadata, deleted)
168
+ VALUES (?, ?, ?, ?, NULL, ?, ?, NULL, 0, ?, ?, ?, 0)`)
169
+ .run(id, type, scope, content, importance, now, JSON.stringify(tags), JSON.stringify({}), JSON.stringify({ content_hash: contentHash, entities, scopeId }));
170
+ // Sync FTS5 index. The external-content FTS5 table needs explicit insert.
171
+ this._brain.db
172
+ .prepare(`INSERT INTO memory_traces_fts (rowid, content, tags)
173
+ VALUES (
174
+ (SELECT rowid FROM memory_traces WHERE id = ?),
175
+ ?,
176
+ ?
177
+ )`)
178
+ .run(id, content, JSON.stringify(tags));
179
+ // Add to memory graph if available.
180
+ if (this._config.graph) {
181
+ await this._memoryGraph.addNode(id, {
182
+ type: type,
183
+ scope: scope,
184
+ scopeId,
185
+ strength: importance,
186
+ createdAt: now,
187
+ });
188
+ }
189
+ // Build a MemoryTrace-shaped return value.
190
+ return this._buildTrace({
191
+ id,
192
+ type,
193
+ scope,
194
+ content,
195
+ embedding: null,
196
+ strength: importance,
197
+ created_at: now,
198
+ last_accessed: null,
199
+ retrieval_count: 0,
200
+ tags: JSON.stringify(tags),
201
+ emotions: JSON.stringify({}),
202
+ metadata: JSON.stringify({ content_hash: contentHash, entities, scopeId }),
203
+ deleted: 0,
204
+ });
205
+ }
206
+ /**
207
+ * Search for memory traces matching a natural-language query.
208
+ *
209
+ * Uses FTS5 full-text search with the Porter tokenizer. Results are ranked
210
+ * by `strength * abs(fts_rank)` and filtered by optional type/scope/strength
211
+ * criteria.
212
+ *
213
+ * @param query - Natural-language search query.
214
+ * @param options - Optional filters (limit, type, scope, minStrength).
215
+ * @returns Ranked array of {@link ScoredTrace} results.
216
+ */
217
+ async recall(query, options) {
218
+ await this._initPromise;
219
+ const limit = options?.limit ?? 10;
220
+ const minStrength = options?.minStrength ?? 0;
221
+ // Build WHERE clause fragments for optional filters.
222
+ const conditions = ['t.deleted = 0'];
223
+ const params = [];
224
+ if (options?.type) {
225
+ conditions.push('t.type = ?');
226
+ params.push(options.type);
227
+ }
228
+ if (options?.scope) {
229
+ conditions.push('t.scope = ?');
230
+ params.push(options.scope);
231
+ }
232
+ if (minStrength > 0) {
233
+ conditions.push('t.strength >= ?');
234
+ params.push(minStrength);
235
+ }
236
+ const whereClause = conditions.length > 0
237
+ ? `WHERE ${conditions.join(' AND ')}`
238
+ : '';
239
+ // FTS5 MATCH query joined with the main table.
240
+ // rank is negative (closer to 0 = better match), so we use abs().
241
+ const sql = `
242
+ SELECT t.*, fts.rank
243
+ FROM memory_traces_fts fts
244
+ JOIN memory_traces t ON t.rowid = fts.rowid
245
+ ${whereClause}
246
+ AND memory_traces_fts MATCH ?
247
+ ORDER BY (t.strength * abs(fts.rank)) DESC
248
+ LIMIT ?
249
+ `;
250
+ params.push(query, limit);
251
+ const rows = this._brain.db
252
+ .prepare(sql)
253
+ .all(...params);
254
+ return rows.map((row) => ({
255
+ trace: this._buildTrace(row),
256
+ score: row.strength * Math.abs(row.rank),
257
+ }));
258
+ }
259
+ /**
260
+ * Soft-delete a memory trace by setting `deleted = 1`.
261
+ *
262
+ * The trace remains in the database for audit/recovery purposes but is
263
+ * excluded from all recall queries and health reports.
264
+ *
265
+ * @param traceId - The ID of the trace to forget.
266
+ */
267
+ async forget(traceId) {
268
+ await this._initPromise;
269
+ this._brain.db
270
+ .prepare('UPDATE memory_traces SET deleted = 1 WHERE id = ?')
271
+ .run(traceId);
272
+ }
273
+ // =========================================================================
274
+ // Document ingestion
275
+ // =========================================================================
276
+ /**
277
+ * Ingest documents from a file, directory, or URL.
278
+ *
279
+ * Workflow:
280
+ * 1. Detect source type (file, directory, or URL).
281
+ * 2. Load document(s) using the appropriate loader.
282
+ * 3. Chunk each document using the configured strategy.
283
+ * 4. For each chunk: insert into `document_chunks`, create a memory trace.
284
+ * 5. Record the document in the `documents` table.
285
+ *
286
+ * @param source - File path, directory path, or URL.
287
+ * @param options - Optional ingestion settings (recursive, include/exclude globs).
288
+ * @returns Summary of the ingestion run.
289
+ */
290
+ async ingest(source, options) {
291
+ await this._initPromise;
292
+ const result = {
293
+ succeeded: [],
294
+ failed: [],
295
+ chunksCreated: 0,
296
+ tracesCreated: 0,
297
+ };
298
+ const chunkStrategy = this._config.ingestion?.chunkStrategy ?? 'semantic';
299
+ const chunkSize = this._config.ingestion?.chunkSize ?? 512;
300
+ const chunkOverlap = this._config.ingestion?.chunkOverlap ?? 64;
301
+ try {
302
+ // Detect source type.
303
+ const stat = await fs.stat(source).catch(() => null);
304
+ if (stat?.isDirectory()) {
305
+ // Directory scan.
306
+ const scanResult = await this._folderScanner.scan(source, {
307
+ recursive: options?.recursive ?? true,
308
+ include: options?.include,
309
+ exclude: options?.exclude,
310
+ onProgress: options?.onProgress
311
+ ? (file, index, total) => options.onProgress(index, total, file)
312
+ : undefined,
313
+ });
314
+ result.succeeded.push(...scanResult.succeeded);
315
+ result.failed.push(...scanResult.failed);
316
+ // Chunk and store each loaded document.
317
+ for (const doc of scanResult.documents) {
318
+ const chunks = await this._chunkingEngine.chunk(doc.content, {
319
+ strategy: chunkStrategy,
320
+ chunkSize,
321
+ chunkOverlap,
322
+ });
323
+ const docId = `doc_${Date.now()}_${_traceCounter++}`;
324
+ const contentHash = sha256(doc.content);
325
+ // Insert document record.
326
+ this._brain.db
327
+ .prepare(`INSERT OR IGNORE INTO documents
328
+ (id, path, format, title, content_hash, chunk_count, metadata, ingested_at)
329
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
330
+ .run(docId, doc.metadata.source ?? source, doc.format, doc.metadata.title ?? null, contentHash, chunks.length, JSON.stringify(doc.metadata), Date.now());
331
+ // Insert chunks and create traces.
332
+ // Insert memory_traces FIRST (document_chunks.trace_id is an FK).
333
+ for (const chunk of chunks) {
334
+ const chunkId = `chunk_${Date.now()}_${_traceCounter++}`;
335
+ const traceId = nextTraceId();
336
+ // 1. Create the memory trace for this chunk.
337
+ this._brain.db
338
+ .prepare(`INSERT INTO memory_traces
339
+ (id, type, scope, content, embedding, strength, created_at,
340
+ last_accessed, retrieval_count, tags, emotions, metadata, deleted)
341
+ VALUES (?, 'semantic', 'user', ?, NULL, 1.0, ?, NULL, 0, '[]', '{}', ?, 0)`)
342
+ .run(traceId, chunk.content, Date.now(), JSON.stringify({
343
+ content_hash: sha256(chunk.content),
344
+ document_id: docId,
345
+ chunk_index: chunk.index,
346
+ }));
347
+ // 2. Sync FTS index.
348
+ this._brain.db
349
+ .prepare(`INSERT INTO memory_traces_fts (rowid, content, tags)
350
+ VALUES (
351
+ (SELECT rowid FROM memory_traces WHERE id = ?),
352
+ ?,
353
+ '[]'
354
+ )`)
355
+ .run(traceId, chunk.content);
356
+ // 3. Insert the document chunk (FK to memory_traces now satisfied).
357
+ this._brain.db
358
+ .prepare(`INSERT INTO document_chunks (id, document_id, trace_id, content, chunk_index, page_number, embedding)
359
+ VALUES (?, ?, ?, ?, ?, ?, NULL)`)
360
+ .run(chunkId, docId, traceId, chunk.content, chunk.index, chunk.pageNumber ?? null);
361
+ result.chunksCreated++;
362
+ result.tracesCreated++;
363
+ }
364
+ }
365
+ }
366
+ else if (stat?.isFile()) {
367
+ // Single file.
368
+ try {
369
+ const doc = await this._loaderRegistry.loadFile(source);
370
+ result.succeeded.push(source);
371
+ const chunks = await this._chunkingEngine.chunk(doc.content, {
372
+ strategy: chunkStrategy,
373
+ chunkSize,
374
+ chunkOverlap,
375
+ });
376
+ const docId = `doc_${Date.now()}_${_traceCounter++}`;
377
+ const contentHash = sha256(doc.content);
378
+ this._brain.db
379
+ .prepare(`INSERT OR IGNORE INTO documents
380
+ (id, path, format, title, content_hash, chunk_count, metadata, ingested_at)
381
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
382
+ .run(docId, source, doc.format, doc.metadata.title ?? null, contentHash, chunks.length, JSON.stringify(doc.metadata), Date.now());
383
+ for (const chunk of chunks) {
384
+ const chunkId = `chunk_${Date.now()}_${_traceCounter++}`;
385
+ const traceId = nextTraceId();
386
+ // 1. Create the memory trace first (FK target for document_chunks).
387
+ this._brain.db
388
+ .prepare(`INSERT INTO memory_traces
389
+ (id, type, scope, content, embedding, strength, created_at,
390
+ last_accessed, retrieval_count, tags, emotions, metadata, deleted)
391
+ VALUES (?, 'semantic', 'user', ?, NULL, 1.0, ?, NULL, 0, '[]', '{}', ?, 0)`)
392
+ .run(traceId, chunk.content, Date.now(), JSON.stringify({
393
+ content_hash: sha256(chunk.content),
394
+ document_id: docId,
395
+ chunk_index: chunk.index,
396
+ }));
397
+ // 2. Sync FTS index.
398
+ this._brain.db
399
+ .prepare(`INSERT INTO memory_traces_fts (rowid, content, tags)
400
+ VALUES (
401
+ (SELECT rowid FROM memory_traces WHERE id = ?),
402
+ ?,
403
+ '[]'
404
+ )`)
405
+ .run(traceId, chunk.content);
406
+ // 3. Insert document chunk (FK to memory_traces now satisfied).
407
+ this._brain.db
408
+ .prepare(`INSERT INTO document_chunks (id, document_id, trace_id, content, chunk_index, page_number, embedding)
409
+ VALUES (?, ?, ?, ?, ?, ?, NULL)`)
410
+ .run(chunkId, docId, traceId, chunk.content, chunk.index, chunk.pageNumber ?? null);
411
+ result.chunksCreated++;
412
+ result.tracesCreated++;
413
+ }
414
+ }
415
+ catch (err) {
416
+ const message = err instanceof Error ? err.message : String(err);
417
+ result.failed.push({ path: source, error: message });
418
+ }
419
+ }
420
+ else {
421
+ // URL or unknown source -- treat as unsupported for now.
422
+ result.failed.push({
423
+ path: source,
424
+ error: `Source "${source}" is not a file or directory.`,
425
+ });
426
+ }
427
+ }
428
+ catch (err) {
429
+ const message = err instanceof Error ? err.message : String(err);
430
+ result.failed.push({ path: source, error: message });
431
+ }
432
+ return result;
433
+ }
434
+ // =========================================================================
435
+ // Knowledge graph
436
+ // =========================================================================
437
+ /**
438
+ * Add or update an entity in the knowledge graph.
439
+ *
440
+ * Delegates to `SqliteKnowledgeGraph.upsertEntity()`. Accepts a partial
441
+ * entity; `id`, `createdAt`, and `updatedAt` are auto-generated when omitted.
442
+ *
443
+ * @param entity - Partial entity descriptor.
444
+ * @returns The complete, persisted entity.
445
+ */
446
+ async addEntity(entity) {
447
+ await this._initPromise;
448
+ return this._knowledgeGraph.upsertEntity({
449
+ type: entity.type ?? 'concept',
450
+ label: entity.label ?? '',
451
+ properties: entity.properties ?? {},
452
+ confidence: entity.confidence ?? 1.0,
453
+ source: entity.source ?? {
454
+ type: 'system',
455
+ timestamp: new Date().toISOString(),
456
+ },
457
+ embedding: entity.embedding,
458
+ ownerId: entity.ownerId,
459
+ tags: entity.tags,
460
+ metadata: entity.metadata,
461
+ ...(entity.id ? { id: entity.id } : {}),
462
+ });
463
+ }
464
+ /**
465
+ * Add or update a relation (edge) in the knowledge graph.
466
+ *
467
+ * Delegates to `SqliteKnowledgeGraph.upsertRelation()`. Accepts a partial
468
+ * relation; `id` and `createdAt` are auto-generated when omitted.
469
+ *
470
+ * @param relation - Partial relation descriptor.
471
+ * @returns The complete, persisted relation.
472
+ */
473
+ async addRelation(relation) {
474
+ await this._initPromise;
475
+ return this._knowledgeGraph.upsertRelation({
476
+ sourceId: relation.sourceId ?? '',
477
+ targetId: relation.targetId ?? '',
478
+ type: relation.type ?? 'related_to',
479
+ label: relation.label ?? '',
480
+ weight: relation.weight ?? 1.0,
481
+ bidirectional: relation.bidirectional ?? false,
482
+ confidence: relation.confidence ?? 1.0,
483
+ source: relation.source ?? {
484
+ type: 'system',
485
+ timestamp: new Date().toISOString(),
486
+ },
487
+ properties: relation.properties,
488
+ validFrom: relation.validFrom,
489
+ validTo: relation.validTo,
490
+ ...(relation.id ? { id: relation.id } : {}),
491
+ });
492
+ }
493
+ /**
494
+ * Access the underlying IKnowledgeGraph implementation.
495
+ *
496
+ * Useful for advanced queries (traversal, semantic search, neighbourhood
497
+ * lookups) that are not exposed on the facade directly.
498
+ */
499
+ get graph() {
500
+ return this._knowledgeGraph;
501
+ }
502
+ // =========================================================================
503
+ // Self-improvement
504
+ // =========================================================================
505
+ /**
506
+ * Run one consolidation cycle (prune, merge, strengthen, derive, compact,
507
+ * re-index).
508
+ *
509
+ * @param options - Optional topic filter (reserved for future use).
510
+ * @returns Statistics from the consolidation run.
511
+ * @throws {Error} When `selfImprove` was set to `false` in the config.
512
+ */
513
+ async consolidate(options) {
514
+ await this._initPromise;
515
+ if (!this._consolidationLoop) {
516
+ throw new Error('Memory.consolidate(): self-improvement is disabled. ' +
517
+ 'Set selfImprove: true in the MemoryConfig to enable consolidation.');
518
+ }
519
+ return this._consolidationLoop.run(this._config.consolidation);
520
+ }
521
+ /**
522
+ * Record retrieval feedback for a memory trace.
523
+ *
524
+ * Fire-and-forget: the feedback is persisted asynchronously and this method
525
+ * returns immediately without waiting for the write to complete.
526
+ *
527
+ * @param traceId - The ID of the trace being evaluated.
528
+ * @param signal - Whether the trace was `'used'` or `'ignored'` by the LLM.
529
+ */
530
+ feedback(traceId, signal) {
531
+ if (!this._feedbackSignal)
532
+ return;
533
+ // Fire-and-forget: insert feedback row without awaiting.
534
+ this._brain.db
535
+ .prepare(`INSERT INTO retrieval_feedback (trace_id, signal, query, created_at)
536
+ VALUES (?, ?, NULL, ?)`)
537
+ .run(traceId, signal, Date.now());
538
+ }
539
+ // =========================================================================
540
+ // Import / Export
541
+ // =========================================================================
542
+ /**
543
+ * Export the memory store to a file or directory.
544
+ *
545
+ * Format is detected from `options.format` or the file extension:
546
+ * - `.json` -> JSON
547
+ * - `.sqlite` / `.db` -> SQLite file copy
548
+ * - directory path -> Markdown or Obsidian (based on `options.format`)
549
+ *
550
+ * @param outputPath - Path to write the export to.
551
+ * @param options - Optional format and content controls.
552
+ */
553
+ async export(outputPath, options) {
554
+ await this._initPromise;
555
+ const format = this._detectExportFormat(outputPath, options);
556
+ switch (format) {
557
+ case 'json': {
558
+ const exporter = new JsonExporter(this._brain);
559
+ await exporter.export(outputPath, options);
560
+ break;
561
+ }
562
+ case 'markdown': {
563
+ const exporter = new MarkdownExporter(this._brain);
564
+ await exporter.export(outputPath, options);
565
+ break;
566
+ }
567
+ case 'obsidian': {
568
+ const exporter = new ObsidianExporter(this._brain);
569
+ await exporter.export(outputPath, options);
570
+ break;
571
+ }
572
+ case 'sqlite': {
573
+ const exporter = new SqliteExporter(this._brain);
574
+ await exporter.export(outputPath, options);
575
+ break;
576
+ }
577
+ default: {
578
+ throw new Error(`Memory.export(): unsupported format "${format}".`);
579
+ }
580
+ }
581
+ }
582
+ /**
583
+ * Import memory data from a file or directory.
584
+ *
585
+ * Format is detected from `options.format`, the file extension, or by
586
+ * inspecting the content.
587
+ *
588
+ * @param source - Path to the import source (file or directory).
589
+ * @param options - Optional format hint and dedup settings.
590
+ * @returns Summary of the import operation.
591
+ */
592
+ async importFrom(source, options) {
593
+ await this._initPromise;
594
+ const format = await this._detectImportFormat(source, options);
595
+ switch (format) {
596
+ case 'json': {
597
+ const importer = new JsonImporter(this._brain);
598
+ return importer.import(source);
599
+ }
600
+ case 'markdown': {
601
+ const importer = new MarkdownImporter(this._brain);
602
+ return importer.import(source);
603
+ }
604
+ case 'obsidian': {
605
+ const importer = new ObsidianImporter(this._brain);
606
+ return importer.import(source);
607
+ }
608
+ case 'sqlite': {
609
+ const importer = new SqliteImporter(this._brain);
610
+ return importer.import(source);
611
+ }
612
+ case 'chatgpt': {
613
+ const importer = new ChatGptImporter(this._brain);
614
+ return importer.import(source);
615
+ }
616
+ default: {
617
+ return { imported: 0, skipped: 0, errors: [`Unsupported import format: "${format}"`] };
618
+ }
619
+ }
620
+ }
621
+ // =========================================================================
622
+ // Health
623
+ // =========================================================================
624
+ /**
625
+ * Return a health snapshot of the memory store.
626
+ *
627
+ * Queries aggregate statistics from all tables and returns a
628
+ * {@link MemoryHealth} report.
629
+ */
630
+ async health() {
631
+ await this._initPromise;
632
+ const db = this._brain.db;
633
+ // Total traces (active + deleted).
634
+ const totalRow = db
635
+ .prepare('SELECT COUNT(*) AS cnt FROM memory_traces')
636
+ .get();
637
+ const totalTraces = totalRow?.cnt ?? 0;
638
+ // Active traces (not deleted).
639
+ const activeRow = db
640
+ .prepare('SELECT COUNT(*) AS cnt FROM memory_traces WHERE deleted = 0')
641
+ .get();
642
+ const activeTraces = activeRow?.cnt ?? 0;
643
+ // Average strength of active traces.
644
+ const avgRow = db
645
+ .prepare('SELECT AVG(strength) AS avg_s FROM memory_traces WHERE deleted = 0')
646
+ .get();
647
+ const avgStrength = avgRow?.avg_s ?? 0;
648
+ // Weakest active trace.
649
+ const weakRow = db
650
+ .prepare('SELECT MIN(strength) AS min_s FROM memory_traces WHERE deleted = 0')
651
+ .get();
652
+ const weakestTraceStrength = weakRow?.min_s ?? 0;
653
+ // Knowledge graph counts.
654
+ const nodesRow = db
655
+ .prepare('SELECT COUNT(*) AS cnt FROM knowledge_nodes')
656
+ .get();
657
+ const graphNodes = nodesRow?.cnt ?? 0;
658
+ const edgesRow = db
659
+ .prepare('SELECT COUNT(*) AS cnt FROM knowledge_edges')
660
+ .get();
661
+ const graphEdges = edgesRow?.cnt ?? 0;
662
+ // Last consolidation timestamp.
663
+ const lastConsolRow = db
664
+ .prepare('SELECT ran_at FROM consolidation_log ORDER BY ran_at DESC LIMIT 1')
665
+ .get();
666
+ const lastConsolidation = lastConsolRow
667
+ ? new Date(lastConsolRow.ran_at).toISOString()
668
+ : null;
669
+ // Traces per type.
670
+ const typeRows = db
671
+ .prepare('SELECT type, COUNT(*) AS cnt FROM memory_traces WHERE deleted = 0 GROUP BY type')
672
+ .all();
673
+ const tracesPerType = {};
674
+ for (const row of typeRows) {
675
+ tracesPerType[row.type] = row.cnt;
676
+ }
677
+ // Traces per scope.
678
+ const scopeRows = db
679
+ .prepare('SELECT scope, COUNT(*) AS cnt FROM memory_traces WHERE deleted = 0 GROUP BY scope')
680
+ .all();
681
+ const tracesPerScope = {};
682
+ for (const row of scopeRows) {
683
+ tracesPerScope[row.scope] = row.cnt;
684
+ }
685
+ // Total document chunks.
686
+ const docsRow = db
687
+ .prepare('SELECT COUNT(*) AS cnt FROM documents')
688
+ .get();
689
+ const documentsIngested = docsRow?.cnt ?? 0;
690
+ return {
691
+ totalTraces,
692
+ activeTraces,
693
+ avgStrength,
694
+ weakestTraceStrength,
695
+ graphNodes,
696
+ graphEdges,
697
+ lastConsolidation,
698
+ tracesPerType,
699
+ tracesPerScope,
700
+ documentsIngested,
701
+ };
702
+ }
703
+ // =========================================================================
704
+ // Lifecycle
705
+ // =========================================================================
706
+ /**
707
+ * Close the Memory instance and release all resources.
708
+ *
709
+ * Flushes the SQLite WAL and releases the file lock. Must be called when
710
+ * the agent shuts down.
711
+ */
712
+ async close() {
713
+ await this._initPromise;
714
+ this._brain.close();
715
+ }
716
+ // =========================================================================
717
+ // Private helpers
718
+ // =========================================================================
719
+ /**
720
+ * Convert a raw `memory_traces` row into a `MemoryTrace` object.
721
+ */
722
+ _buildTrace(row) {
723
+ let tags = [];
724
+ try {
725
+ tags = JSON.parse(row.tags);
726
+ }
727
+ catch { /* empty */ }
728
+ let emotions = {};
729
+ try {
730
+ emotions = JSON.parse(row.emotions);
731
+ }
732
+ catch { /* empty */ }
733
+ let metadata = {};
734
+ try {
735
+ metadata = JSON.parse(row.metadata);
736
+ }
737
+ catch { /* empty */ }
738
+ const entities = Array.isArray(metadata.entities) ? metadata.entities : [];
739
+ const scopeId = typeof metadata.scopeId === 'string' ? metadata.scopeId : '';
740
+ return {
741
+ id: row.id,
742
+ type: row.type,
743
+ scope: row.scope,
744
+ scopeId,
745
+ content: row.content,
746
+ entities,
747
+ tags,
748
+ provenance: {
749
+ sourceType: 'user_statement',
750
+ sourceTimestamp: row.created_at,
751
+ confidence: 1.0,
752
+ verificationCount: 0,
753
+ },
754
+ emotionalContext: {
755
+ valence: 0,
756
+ arousal: 0,
757
+ dominance: 0,
758
+ intensity: 0,
759
+ gmiMood: 'neutral',
760
+ ...emotions,
761
+ },
762
+ encodingStrength: row.strength,
763
+ stability: 86400000, // 1 day default
764
+ retrievalCount: row.retrieval_count,
765
+ lastAccessedAt: row.last_accessed ?? row.created_at,
766
+ accessCount: row.retrieval_count,
767
+ reinforcementInterval: 86400000,
768
+ associatedTraceIds: [],
769
+ createdAt: row.created_at,
770
+ updatedAt: row.created_at,
771
+ isActive: row.deleted === 0,
772
+ };
773
+ }
774
+ /**
775
+ * Detect the export format from options or file extension.
776
+ */
777
+ _detectExportFormat(outputPath, options) {
778
+ if (options?.format)
779
+ return options.format;
780
+ const ext = path.extname(outputPath).toLowerCase();
781
+ switch (ext) {
782
+ case '.json': return 'json';
783
+ case '.sqlite':
784
+ case '.db': return 'sqlite';
785
+ default: return 'markdown';
786
+ }
787
+ }
788
+ /**
789
+ * Detect the import format from options, file extension, or content inspection.
790
+ */
791
+ async _detectImportFormat(source, options) {
792
+ if (options?.format && options.format !== 'auto')
793
+ return options.format;
794
+ const ext = path.extname(source).toLowerCase();
795
+ switch (ext) {
796
+ case '.json': {
797
+ // Check if it looks like a ChatGPT export.
798
+ try {
799
+ const head = await fs.readFile(source, { encoding: 'utf8', flag: 'r' });
800
+ if (head.includes('"mapping"') && head.includes('"conversation_id"')) {
801
+ return 'chatgpt';
802
+ }
803
+ }
804
+ catch { /* fall through */ }
805
+ return 'json';
806
+ }
807
+ case '.sqlite':
808
+ case '.db': return 'sqlite';
809
+ case '.csv': return 'csv';
810
+ default: {
811
+ // Check if source is a directory.
812
+ try {
813
+ const stat = await fs.stat(source);
814
+ if (stat.isDirectory())
815
+ return 'markdown';
816
+ }
817
+ catch { /* fall through */ }
818
+ return 'json';
819
+ }
820
+ }
821
+ }
822
+ }
823
+ //# sourceMappingURL=Memory.js.map