@framers/agentos 0.2.12 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/dist/ingest-router/executors/EntityExtractor.d.ts +23 -0
  2. package/dist/ingest-router/executors/EntityExtractor.d.ts.map +1 -0
  3. package/dist/ingest-router/executors/EntityExtractor.js +69 -0
  4. package/dist/ingest-router/executors/EntityExtractor.js.map +1 -0
  5. package/dist/ingest-router/executors/EntityLinkingIngestExecutor.d.ts +46 -0
  6. package/dist/ingest-router/executors/EntityLinkingIngestExecutor.d.ts.map +1 -0
  7. package/dist/ingest-router/executors/EntityLinkingIngestExecutor.js +45 -0
  8. package/dist/ingest-router/executors/EntityLinkingIngestExecutor.js.map +1 -0
  9. package/dist/ingest-router/executors/entity-types.d.ts +55 -0
  10. package/dist/ingest-router/executors/entity-types.d.ts.map +1 -0
  11. package/dist/ingest-router/executors/entity-types.js +17 -0
  12. package/dist/ingest-router/executors/entity-types.js.map +1 -0
  13. package/dist/ingest-router/executors/index.d.ts +7 -0
  14. package/dist/ingest-router/executors/index.d.ts.map +1 -1
  15. package/dist/ingest-router/executors/index.js +6 -0
  16. package/dist/ingest-router/executors/index.js.map +1 -1
  17. package/dist/ingest-router/index.d.ts +2 -2
  18. package/dist/ingest-router/index.d.ts.map +1 -1
  19. package/dist/ingest-router/index.js +1 -1
  20. package/dist/ingest-router/index.js.map +1 -1
  21. package/dist/memory/AgentMemory.js +1 -1
  22. package/dist/memory/AgentMemory.js.map +1 -1
  23. package/dist/memory/CognitiveMemoryManager.js +4 -4
  24. package/dist/memory/CognitiveMemoryManager.js.map +1 -1
  25. package/dist/memory/archive/IMemoryArchive.d.ts +2 -2
  26. package/dist/memory/archive/SqlStorageMemoryArchive.d.ts +17 -13
  27. package/dist/memory/archive/SqlStorageMemoryArchive.d.ts.map +1 -1
  28. package/dist/memory/archive/SqlStorageMemoryArchive.js +36 -28
  29. package/dist/memory/archive/SqlStorageMemoryArchive.js.map +1 -1
  30. package/dist/memory/core/config.d.ts +4 -4
  31. package/dist/memory/core/config.d.ts.map +1 -1
  32. package/dist/memory/index.d.ts +3 -3
  33. package/dist/memory/index.d.ts.map +1 -1
  34. package/dist/memory/index.js +3 -3
  35. package/dist/memory/index.js.map +1 -1
  36. package/dist/memory/io/ChatGptImporter.d.ts +5 -5
  37. package/dist/memory/io/ChatGptImporter.d.ts.map +1 -1
  38. package/dist/memory/io/ChatGptImporter.js +9 -7
  39. package/dist/memory/io/ChatGptImporter.js.map +1 -1
  40. package/dist/memory/io/CsvImporter.d.ts +4 -4
  41. package/dist/memory/io/CsvImporter.d.ts.map +1 -1
  42. package/dist/memory/io/CsvImporter.js +11 -8
  43. package/dist/memory/io/CsvImporter.js.map +1 -1
  44. package/dist/memory/io/JsonExporter.d.ts +5 -5
  45. package/dist/memory/io/JsonExporter.d.ts.map +1 -1
  46. package/dist/memory/io/JsonExporter.js +13 -12
  47. package/dist/memory/io/JsonExporter.js.map +1 -1
  48. package/dist/memory/io/JsonImporter.d.ts +5 -5
  49. package/dist/memory/io/JsonImporter.d.ts.map +1 -1
  50. package/dist/memory/io/JsonImporter.js +50 -34
  51. package/dist/memory/io/JsonImporter.js.map +1 -1
  52. package/dist/memory/io/MarkdownExporter.d.ts +4 -4
  53. package/dist/memory/io/MarkdownExporter.d.ts.map +1 -1
  54. package/dist/memory/io/MarkdownExporter.js +1 -1
  55. package/dist/memory/io/MarkdownExporter.js.map +1 -1
  56. package/dist/memory/io/MarkdownImporter.d.ts +6 -6
  57. package/dist/memory/io/MarkdownImporter.d.ts.map +1 -1
  58. package/dist/memory/io/MarkdownImporter.js +8 -7
  59. package/dist/memory/io/MarkdownImporter.js.map +1 -1
  60. package/dist/memory/io/ObsidianImporter.d.ts +4 -4
  61. package/dist/memory/io/ObsidianImporter.d.ts.map +1 -1
  62. package/dist/memory/io/ObsidianImporter.js +15 -10
  63. package/dist/memory/io/ObsidianImporter.js.map +1 -1
  64. package/dist/memory/io/SqliteExporter.d.ts +5 -5
  65. package/dist/memory/io/SqliteExporter.d.ts.map +1 -1
  66. package/dist/memory/io/SqliteExporter.js +3 -3
  67. package/dist/memory/io/SqliteExporter.js.map +1 -1
  68. package/dist/memory/io/SqliteImporter.d.ts +4 -4
  69. package/dist/memory/io/SqliteImporter.d.ts.map +1 -1
  70. package/dist/memory/io/SqliteImporter.js +23 -16
  71. package/dist/memory/io/SqliteImporter.js.map +1 -1
  72. package/dist/memory/io/facade/Memory.d.ts +58 -10
  73. package/dist/memory/io/facade/Memory.d.ts.map +1 -1
  74. package/dist/memory/io/facade/Memory.js +124 -50
  75. package/dist/memory/io/facade/Memory.js.map +1 -1
  76. package/dist/memory/io/facade/types.d.ts +1 -1
  77. package/dist/memory/io/index.d.ts +2 -2
  78. package/dist/memory/io/index.js +2 -2
  79. package/dist/memory/io/tools/MemoryAddTool.d.ts +2 -2
  80. package/dist/memory/io/tools/MemoryAddTool.d.ts.map +1 -1
  81. package/dist/memory/io/tools/MemoryAddTool.js +2 -2
  82. package/dist/memory/io/tools/MemoryAddTool.js.map +1 -1
  83. package/dist/memory/io/tools/MemoryDeleteTool.d.ts +2 -2
  84. package/dist/memory/io/tools/MemoryDeleteTool.d.ts.map +1 -1
  85. package/dist/memory/io/tools/MemoryDeleteTool.js +1 -1
  86. package/dist/memory/io/tools/MemoryDeleteTool.js.map +1 -1
  87. package/dist/memory/io/tools/MemoryMergeTool.d.ts +2 -2
  88. package/dist/memory/io/tools/MemoryMergeTool.d.ts.map +1 -1
  89. package/dist/memory/io/tools/MemoryMergeTool.js +4 -3
  90. package/dist/memory/io/tools/MemoryMergeTool.js.map +1 -1
  91. package/dist/memory/io/tools/MemoryReflectTool.d.ts +2 -2
  92. package/dist/memory/io/tools/MemoryReflectTool.d.ts.map +1 -1
  93. package/dist/memory/io/tools/MemoryReflectTool.js.map +1 -1
  94. package/dist/memory/io/tools/MemorySearchTool.d.ts +2 -2
  95. package/dist/memory/io/tools/MemorySearchTool.d.ts.map +1 -1
  96. package/dist/memory/io/tools/MemorySearchTool.js.map +1 -1
  97. package/dist/memory/io/tools/MemoryUpdateTool.d.ts +2 -2
  98. package/dist/memory/io/tools/MemoryUpdateTool.d.ts.map +1 -1
  99. package/dist/memory/io/tools/MemoryUpdateTool.js +5 -4
  100. package/dist/memory/io/tools/MemoryUpdateTool.js.map +1 -1
  101. package/dist/memory/pipeline/consolidation/ConsolidationLoop.d.ts +3 -3
  102. package/dist/memory/pipeline/consolidation/ConsolidationLoop.d.ts.map +1 -1
  103. package/dist/memory/pipeline/consolidation/ConsolidationLoop.js +22 -17
  104. package/dist/memory/pipeline/consolidation/ConsolidationLoop.js.map +1 -1
  105. package/dist/memory/retrieval/feedback/RetrievalFeedbackSignal.d.ts +3 -3
  106. package/dist/memory/retrieval/feedback/RetrievalFeedbackSignal.d.ts.map +1 -1
  107. package/dist/memory/retrieval/feedback/RetrievalFeedbackSignal.js +15 -12
  108. package/dist/memory/retrieval/feedback/RetrievalFeedbackSignal.js.map +1 -1
  109. package/dist/memory/retrieval/graph/index.d.ts +0 -1
  110. package/dist/memory/retrieval/graph/index.d.ts.map +1 -1
  111. package/dist/memory/retrieval/graph/index.js +4 -1
  112. package/dist/memory/retrieval/graph/index.js.map +1 -1
  113. package/dist/memory/retrieval/store/{SqliteBrain.d.ts → Brain.d.ts} +111 -23
  114. package/dist/memory/retrieval/store/Brain.d.ts.map +1 -0
  115. package/dist/memory/retrieval/store/{SqliteBrain.js → Brain.js} +367 -76
  116. package/dist/memory/retrieval/store/Brain.js.map +1 -0
  117. package/dist/memory/retrieval/store/HnswSidecar.d.ts +1 -1
  118. package/dist/memory/retrieval/store/HnswSidecar.js +1 -1
  119. package/dist/memory/retrieval/store/MemoryStore.d.ts +6 -6
  120. package/dist/memory/retrieval/store/MemoryStore.d.ts.map +1 -1
  121. package/dist/memory/retrieval/store/MemoryStore.js +10 -9
  122. package/dist/memory/retrieval/store/MemoryStore.js.map +1 -1
  123. package/dist/memory/retrieval/store/{SqliteKnowledgeGraph.d.ts → SqlKnowledgeGraph.d.ts} +12 -12
  124. package/dist/memory/retrieval/store/SqlKnowledgeGraph.d.ts.map +1 -0
  125. package/dist/memory/retrieval/store/{SqliteKnowledgeGraph.js → SqlKnowledgeGraph.js} +83 -64
  126. package/dist/memory/retrieval/store/SqlKnowledgeGraph.js.map +1 -0
  127. package/dist/memory/retrieval/store/{SqliteMemoryGraph.d.ts → SqlMemoryGraph.d.ts} +11 -11
  128. package/dist/memory/retrieval/store/SqlMemoryGraph.d.ts.map +1 -0
  129. package/dist/memory/retrieval/store/{SqliteMemoryGraph.js → SqlMemoryGraph.js} +26 -24
  130. package/dist/memory/retrieval/store/SqlMemoryGraph.js.map +1 -0
  131. package/dist/memory/retrieval/store/migrations/v1-to-v2.d.ts +31 -0
  132. package/dist/memory/retrieval/store/migrations/v1-to-v2.d.ts.map +1 -0
  133. package/dist/memory/retrieval/store/migrations/v1-to-v2.js +423 -0
  134. package/dist/memory/retrieval/store/migrations/v1-to-v2.js.map +1 -0
  135. package/dist/memory-router/backends/EntityRetrievalRanker.d.ts +54 -0
  136. package/dist/memory-router/backends/EntityRetrievalRanker.d.ts.map +1 -0
  137. package/dist/memory-router/backends/EntityRetrievalRanker.js +39 -0
  138. package/dist/memory-router/backends/EntityRetrievalRanker.js.map +1 -0
  139. package/dist/memory-router/backends/index.d.ts +16 -0
  140. package/dist/memory-router/backends/index.d.ts.map +1 -0
  141. package/dist/memory-router/backends/index.js +16 -0
  142. package/dist/memory-router/backends/index.js.map +1 -0
  143. package/dist/memory-router/index.d.ts +2 -0
  144. package/dist/memory-router/index.d.ts.map +1 -1
  145. package/dist/memory-router/index.js +4 -0
  146. package/dist/memory-router/index.js.map +1 -1
  147. package/dist/rag/utils/vectorMath.d.ts +1 -1
  148. package/dist/rag/utils/vectorMath.js +1 -1
  149. package/dist/rag/vector-search/HnswIndexSidecar.d.ts +1 -1
  150. package/dist/rag/vector-search/HnswIndexSidecar.js +1 -1
  151. package/package.json +2 -2
  152. package/dist/memory/retrieval/graph/knowledge/SqliteKnowledgeGraph.d.ts +0 -10
  153. package/dist/memory/retrieval/graph/knowledge/SqliteKnowledgeGraph.d.ts.map +0 -1
  154. package/dist/memory/retrieval/graph/knowledge/SqliteKnowledgeGraph.js +0 -10
  155. package/dist/memory/retrieval/graph/knowledge/SqliteKnowledgeGraph.js.map +0 -1
  156. package/dist/memory/retrieval/store/SqliteBrain.d.ts.map +0 -1
  157. package/dist/memory/retrieval/store/SqliteBrain.js.map +0 -1
  158. package/dist/memory/retrieval/store/SqliteKnowledgeGraph.d.ts.map +0 -1
  159. package/dist/memory/retrieval/store/SqliteKnowledgeGraph.js.map +0 -1
  160. package/dist/memory/retrieval/store/SqliteMemoryGraph.d.ts.map +0 -1
  161. package/dist/memory/retrieval/store/SqliteMemoryGraph.js.map +0 -1
@@ -26,15 +26,35 @@
26
26
  * - **JSON columns**: tags, emotions, metadata stored as JSON TEXT for schema flexibility
27
27
  * without sacrificing query-ability via SQLite's json_extract().
28
28
  *
29
- * @module memory/store/SqliteBrain
29
+ * @module memory/store/Brain
30
30
  */
31
- import { resolveStorageAdapter, createStorageFeatures } from '@framers/sql-storage-adapter';
31
+ import { promises as fs } from 'node:fs';
32
+ import path from 'node:path';
33
+ import { resolveStorageAdapter, createStorageFeatures, createPostgresAdapter } from '@framers/sql-storage-adapter';
32
34
  import { DDL_ARCHIVED_TRACES, DDL_ARCHIVED_TRACES_IDX_AGENT_TIME, DDL_ARCHIVED_TRACES_IDX_REASON, DDL_ARCHIVE_ACCESS_LOG, DDL_ARCHIVE_ACCESS_LOG_IDX, } from '../../archive/SqlStorageMemoryArchive.js';
35
+ import { migrateV1ToV2 } from './migrations/v1-to-v2.js';
36
+ /**
37
+ * Derive a stable brain identifier from the database file path.
38
+ *
39
+ * `:memory:` becomes `'default'`. For real paths, the file basename is used
40
+ * with extensions stripped (e.g. `companion-alice.sqlite` becomes
41
+ * `companion-alice`; `foo.brain.sqlite` becomes `foo.brain`).
42
+ *
43
+ * Used by {@link Brain.open} when the caller does not supply an
44
+ * explicit `brainId`.
45
+ */
46
+ function deriveBrainIdFromPath(dbPath) {
47
+ if (dbPath === ':memory:')
48
+ return 'default';
49
+ const basename = path.basename(dbPath);
50
+ const lastDot = basename.lastIndexOf('.');
51
+ return lastDot > 0 ? basename.slice(0, lastDot) : basename;
52
+ }
33
53
  // ---------------------------------------------------------------------------
34
54
  // Constants
35
55
  // ---------------------------------------------------------------------------
36
56
  /** Current schema version. Increment when breaking schema changes are made. */
37
- const SCHEMA_VERSION = '1';
57
+ const SCHEMA_VERSION = '2';
38
58
  // ---------------------------------------------------------------------------
39
59
  // DDL — full schema
40
60
  // ---------------------------------------------------------------------------
@@ -44,8 +64,10 @@ const SCHEMA_VERSION = '1';
44
64
  */
45
65
  const DDL_BRAIN_META = `
46
66
  CREATE TABLE IF NOT EXISTS brain_meta (
47
- key TEXT PRIMARY KEY,
48
- value TEXT NOT NULL
67
+ brain_id TEXT NOT NULL,
68
+ key TEXT NOT NULL,
69
+ value TEXT NOT NULL,
70
+ PRIMARY KEY (brain_id, key)
49
71
  );
50
72
  `;
51
73
  /**
@@ -59,7 +81,8 @@ CREATE TABLE IF NOT EXISTS brain_meta (
59
81
  */
60
82
  const DDL_MEMORY_TRACES = `
61
83
  CREATE TABLE IF NOT EXISTS memory_traces (
62
- id TEXT PRIMARY KEY,
84
+ brain_id TEXT NOT NULL,
85
+ id TEXT NOT NULL,
63
86
  type TEXT NOT NULL,
64
87
  scope TEXT NOT NULL,
65
88
  content TEXT NOT NULL,
@@ -71,8 +94,14 @@ CREATE TABLE IF NOT EXISTS memory_traces (
71
94
  tags TEXT NOT NULL DEFAULT '[]',
72
95
  emotions TEXT NOT NULL DEFAULT '{}',
73
96
  metadata TEXT NOT NULL DEFAULT '{}',
74
- deleted INTEGER NOT NULL DEFAULT 0
97
+ deleted INTEGER NOT NULL DEFAULT 0,
98
+ PRIMARY KEY (brain_id, id)
75
99
  );
100
+
101
+ CREATE INDEX IF NOT EXISTS idx_memory_traces_brain_type
102
+ ON memory_traces (brain_id, type, created_at DESC);
103
+ CREATE INDEX IF NOT EXISTS idx_memory_traces_brain_scope
104
+ ON memory_traces (brain_id, scope);
76
105
  `;
77
106
  // FTS index DDL is now generated dynamically by features.fts.createIndex()
78
107
  // to support both SQLite FTS5 and Postgres tsvector/GIN.
@@ -86,15 +115,20 @@ CREATE TABLE IF NOT EXISTS memory_traces (
86
115
  */
87
116
  const DDL_KNOWLEDGE_NODES = `
88
117
  CREATE TABLE IF NOT EXISTS knowledge_nodes (
89
- id TEXT PRIMARY KEY,
118
+ brain_id TEXT NOT NULL,
119
+ id TEXT NOT NULL,
90
120
  type TEXT NOT NULL,
91
121
  label TEXT NOT NULL,
92
122
  properties TEXT NOT NULL DEFAULT '{}',
93
123
  embedding BLOB,
94
124
  confidence REAL NOT NULL DEFAULT 1.0,
95
125
  source TEXT NOT NULL DEFAULT '{}',
96
- created_at INTEGER NOT NULL
126
+ created_at INTEGER NOT NULL,
127
+ PRIMARY KEY (brain_id, id)
97
128
  );
129
+
130
+ CREATE INDEX IF NOT EXISTS idx_knowledge_nodes_brain_type
131
+ ON knowledge_nodes (brain_id, type);
98
132
  `;
99
133
  /**
100
134
  * Knowledge graph edges (typed relationships).
@@ -105,15 +139,24 @@ CREATE TABLE IF NOT EXISTS knowledge_nodes (
105
139
  */
106
140
  const DDL_KNOWLEDGE_EDGES = `
107
141
  CREATE TABLE IF NOT EXISTS knowledge_edges (
108
- id TEXT PRIMARY KEY,
109
- source_id TEXT NOT NULL REFERENCES knowledge_nodes(id),
110
- target_id TEXT NOT NULL REFERENCES knowledge_nodes(id),
142
+ brain_id TEXT NOT NULL,
143
+ id TEXT NOT NULL,
144
+ source_id TEXT NOT NULL,
145
+ target_id TEXT NOT NULL,
111
146
  type TEXT NOT NULL,
112
147
  weight REAL NOT NULL DEFAULT 1.0,
113
148
  bidirectional INTEGER NOT NULL DEFAULT 0,
114
149
  metadata TEXT NOT NULL DEFAULT '{}',
115
- created_at INTEGER NOT NULL
150
+ created_at INTEGER NOT NULL,
151
+ PRIMARY KEY (brain_id, id),
152
+ FOREIGN KEY (brain_id, source_id) REFERENCES knowledge_nodes(brain_id, id),
153
+ FOREIGN KEY (brain_id, target_id) REFERENCES knowledge_nodes(brain_id, id)
116
154
  );
155
+
156
+ CREATE INDEX IF NOT EXISTS idx_knowledge_edges_brain_source
157
+ ON knowledge_edges (brain_id, source_id);
158
+ CREATE INDEX IF NOT EXISTS idx_knowledge_edges_brain_target
159
+ ON knowledge_edges (brain_id, target_id);
117
160
  `;
118
161
  /**
119
162
  * Ingested document registry.
@@ -125,14 +168,16 @@ CREATE TABLE IF NOT EXISTS knowledge_edges (
125
168
  */
126
169
  const DDL_DOCUMENTS = `
127
170
  CREATE TABLE IF NOT EXISTS documents (
128
- id TEXT PRIMARY KEY,
171
+ brain_id TEXT NOT NULL,
172
+ id TEXT NOT NULL,
129
173
  path TEXT NOT NULL,
130
174
  format TEXT NOT NULL,
131
175
  title TEXT,
132
176
  content_hash TEXT NOT NULL,
133
177
  chunk_count INTEGER NOT NULL DEFAULT 0,
134
178
  metadata TEXT NOT NULL DEFAULT '{}',
135
- ingested_at INTEGER NOT NULL
179
+ ingested_at INTEGER NOT NULL,
180
+ PRIMARY KEY (brain_id, id)
136
181
  );
137
182
  `;
138
183
  /**
@@ -144,14 +189,21 @@ CREATE TABLE IF NOT EXISTS documents (
144
189
  */
145
190
  const DDL_DOCUMENT_CHUNKS = `
146
191
  CREATE TABLE IF NOT EXISTS document_chunks (
147
- id TEXT PRIMARY KEY,
148
- document_id TEXT NOT NULL REFERENCES documents(id),
149
- trace_id TEXT REFERENCES memory_traces(id),
192
+ brain_id TEXT NOT NULL,
193
+ id TEXT NOT NULL,
194
+ document_id TEXT NOT NULL,
195
+ trace_id TEXT,
150
196
  content TEXT NOT NULL,
151
197
  chunk_index INTEGER NOT NULL,
152
198
  page_number INTEGER,
153
- embedding BLOB
199
+ embedding BLOB,
200
+ PRIMARY KEY (brain_id, id),
201
+ FOREIGN KEY (brain_id, document_id) REFERENCES documents(brain_id, id),
202
+ FOREIGN KEY (brain_id, trace_id) REFERENCES memory_traces(brain_id, id)
154
203
  );
204
+
205
+ CREATE INDEX IF NOT EXISTS idx_document_chunks_brain_document
206
+ ON document_chunks (brain_id, document_id, chunk_index);
155
207
  `;
156
208
  /**
157
209
  * Document image table.
@@ -161,14 +213,18 @@ CREATE TABLE IF NOT EXISTS document_chunks (
161
213
  */
162
214
  const DDL_DOCUMENT_IMAGES = `
163
215
  CREATE TABLE IF NOT EXISTS document_images (
164
- id TEXT PRIMARY KEY,
165
- document_id TEXT NOT NULL REFERENCES documents(id),
166
- chunk_id TEXT REFERENCES document_chunks(id),
216
+ brain_id TEXT NOT NULL,
217
+ id TEXT NOT NULL,
218
+ document_id TEXT NOT NULL,
219
+ chunk_id TEXT,
167
220
  data BLOB NOT NULL,
168
221
  mime_type TEXT NOT NULL,
169
222
  caption TEXT,
170
223
  page_number INTEGER,
171
- embedding BLOB
224
+ embedding BLOB,
225
+ PRIMARY KEY (brain_id, id),
226
+ FOREIGN KEY (brain_id, document_id) REFERENCES documents(brain_id, id),
227
+ FOREIGN KEY (brain_id, chunk_id) REFERENCES document_chunks(brain_id, id)
172
228
  );
173
229
  `;
174
230
  /**
@@ -181,6 +237,7 @@ CREATE TABLE IF NOT EXISTS document_images (
181
237
  const DDL_CONSOLIDATION_LOG = `
182
238
  CREATE TABLE IF NOT EXISTS consolidation_log (
183
239
  id INTEGER PRIMARY KEY AUTOINCREMENT,
240
+ brain_id TEXT NOT NULL,
184
241
  ran_at INTEGER NOT NULL,
185
242
  pruned INTEGER NOT NULL DEFAULT 0,
186
243
  merged INTEGER NOT NULL DEFAULT 0,
@@ -188,6 +245,9 @@ CREATE TABLE IF NOT EXISTS consolidation_log (
188
245
  compacted INTEGER NOT NULL DEFAULT 0,
189
246
  duration_ms INTEGER NOT NULL DEFAULT 0
190
247
  );
248
+
249
+ CREATE INDEX IF NOT EXISTS idx_consolidation_log_brain_time
250
+ ON consolidation_log (brain_id, ran_at DESC);
191
251
  `;
192
252
  /**
193
253
  * Retrieval feedback signals.
@@ -201,11 +261,16 @@ CREATE TABLE IF NOT EXISTS consolidation_log (
201
261
  const DDL_RETRIEVAL_FEEDBACK = `
202
262
  CREATE TABLE IF NOT EXISTS retrieval_feedback (
203
263
  id INTEGER PRIMARY KEY AUTOINCREMENT,
204
- trace_id TEXT NOT NULL REFERENCES memory_traces(id),
264
+ brain_id TEXT NOT NULL,
265
+ trace_id TEXT NOT NULL,
205
266
  signal TEXT NOT NULL,
206
267
  query TEXT,
207
- created_at INTEGER NOT NULL
268
+ created_at INTEGER NOT NULL,
269
+ FOREIGN KEY (brain_id, trace_id) REFERENCES memory_traces(brain_id, id)
208
270
  );
271
+
272
+ CREATE INDEX IF NOT EXISTS idx_retrieval_feedback_brain_trace
273
+ ON retrieval_feedback (brain_id, trace_id, created_at DESC);
209
274
  `;
210
275
  /**
211
276
  * Conversation sessions.
@@ -215,11 +280,13 @@ CREATE TABLE IF NOT EXISTS retrieval_feedback (
215
280
  */
216
281
  const DDL_CONVERSATIONS = `
217
282
  CREATE TABLE IF NOT EXISTS conversations (
218
- id TEXT PRIMARY KEY,
283
+ brain_id TEXT NOT NULL,
284
+ id TEXT NOT NULL,
219
285
  title TEXT,
220
286
  created_at INTEGER NOT NULL,
221
287
  updated_at INTEGER NOT NULL,
222
- metadata TEXT NOT NULL DEFAULT '{}'
288
+ metadata TEXT NOT NULL DEFAULT '{}',
289
+ PRIMARY KEY (brain_id, id)
223
290
  );
224
291
  `;
225
292
  /**
@@ -230,13 +297,19 @@ CREATE TABLE IF NOT EXISTS conversations (
230
297
  */
231
298
  const DDL_MESSAGES = `
232
299
  CREATE TABLE IF NOT EXISTS messages (
233
- id TEXT PRIMARY KEY,
234
- conversation_id TEXT NOT NULL REFERENCES conversations(id),
300
+ brain_id TEXT NOT NULL,
301
+ id TEXT NOT NULL,
302
+ conversation_id TEXT NOT NULL,
235
303
  role TEXT NOT NULL,
236
304
  content TEXT NOT NULL,
237
305
  created_at INTEGER NOT NULL,
238
- metadata TEXT NOT NULL DEFAULT '{}'
306
+ metadata TEXT NOT NULL DEFAULT '{}',
307
+ PRIMARY KEY (brain_id, id),
308
+ FOREIGN KEY (brain_id, conversation_id) REFERENCES conversations(brain_id, id)
239
309
  );
310
+
311
+ CREATE INDEX IF NOT EXISTS idx_messages_brain_conversation
312
+ ON messages (brain_id, conversation_id, created_at);
240
313
  `;
241
314
  /**
242
315
  * Prospective memory items table.
@@ -252,7 +325,8 @@ CREATE TABLE IF NOT EXISTS messages (
252
325
  */
253
326
  const DDL_PROSPECTIVE_ITEMS = `
254
327
  CREATE TABLE IF NOT EXISTS prospective_items (
255
- id TEXT PRIMARY KEY,
328
+ brain_id TEXT NOT NULL,
329
+ id TEXT NOT NULL,
256
330
  content TEXT NOT NULL,
257
331
  trigger_type TEXT NOT NULL,
258
332
  trigger_at INTEGER,
@@ -264,11 +338,12 @@ CREATE TABLE IF NOT EXISTS prospective_items (
264
338
  triggered INTEGER NOT NULL DEFAULT 0,
265
339
  recurring INTEGER NOT NULL DEFAULT 0,
266
340
  source_trace_id TEXT,
267
- created_at INTEGER NOT NULL
341
+ created_at INTEGER NOT NULL,
342
+ PRIMARY KEY (brain_id, id)
268
343
  );
269
344
  `;
270
345
  // ---------------------------------------------------------------------------
271
- // SqliteBrain
346
+ // Brain
272
347
  // ---------------------------------------------------------------------------
273
348
  /**
274
349
  * Unified cross-platform connection manager for a single agent's persistent brain.
@@ -279,7 +354,7 @@ CREATE TABLE IF NOT EXISTS prospective_items (
279
354
  *
280
355
  * **Usage:**
281
356
  * ```ts
282
- * const brain = await SqliteBrain.open('/path/to/agent/brain.sqlite');
357
+ * const brain = await Brain.open('/path/to/agent/brain.sqlite');
283
358
  *
284
359
  * // Async query API for subsystems
285
360
  * const row = await brain.get<{ value: string }>('SELECT value FROM brain_meta WHERE key = ?', ['schema_version']);
@@ -292,74 +367,119 @@ CREATE TABLE IF NOT EXISTS prospective_items (
292
367
  * ```
293
368
  *
294
369
  * Subsystems (KnowledgeGraph, MemoryGraph, ConsolidationLoop, etc.)
295
- * receive the `SqliteBrain` instance and call its async proxy methods
370
+ * receive the `Brain` instance and call its async proxy methods
296
371
  * (`run`, `get`, `all`, `exec`, `transaction`) for all database operations.
297
372
  */
298
- export class SqliteBrain {
373
+ export class Brain {
299
374
  // ---------------------------------------------------------------------------
300
- // Constructor (private — use SqliteBrain.open())
375
+ // Constructor (private — use Brain.open())
301
376
  // ---------------------------------------------------------------------------
302
377
  /**
303
- * Private constructor — use `SqliteBrain.open(dbPath)` instead.
378
+ * Private constructor — use `Brain.open(dbPath)` instead.
304
379
  *
305
380
  * @param adapter - A fully initialised StorageAdapter instance.
306
381
  * @param features - Platform-aware feature bundle.
382
+ * @param brainId - Brain identifier used to scope multi-tenant queries.
307
383
  */
308
- constructor(adapter, features) {
384
+ constructor(adapter, features, brainId) {
309
385
  this._adapter = adapter;
310
386
  this._features = features;
387
+ this._brainId = brainId;
388
+ }
389
+ /**
390
+ * Brain identifier scoping every query through this Brain instance.
391
+ * Subsystems (KnowledgeGraph, MemoryGraph, ConsolidationLoop) read this
392
+ * to inject `brain_id` into their own SQL.
393
+ */
394
+ get brainId() {
395
+ return this._brainId;
311
396
  }
312
397
  // ---------------------------------------------------------------------------
313
- // Async factory
398
+ // Async factories (three named entry points)
314
399
  // ---------------------------------------------------------------------------
315
400
  /**
316
- * Create or open the agent's brain database at `dbPath`.
317
- *
318
- * Async factory that replaces the previous synchronous constructor.
319
- *
320
- * Initialization sequence:
321
- * 1. Resolve the best available storage adapter for the current runtime.
322
- * 2. Enable WAL journal mode for concurrent read access (when supported).
323
- * 3. Enable foreign key enforcement (OFF by default in SQLite).
324
- * 4. Execute the full DDL schema (all `CREATE TABLE IF NOT EXISTS`).
325
- * 5. Create the FTS5 virtual table for full-text memory search.
326
- * 6. Seed `brain_meta` with `schema_version` and `created_at` if absent.
401
+ * Open a Brain backed by SQLite. Tries adapters in order:
402
+ * better-sqlite3 (Node native) -> sql.js (WASM) -> indexeddb (browser).
327
403
  *
328
- * @param dbPath - Absolute path to the `.sqlite` file. The file is created
329
- * if it does not exist; parent directories must already exist.
330
- * @returns A fully initialised `SqliteBrain` instance.
404
+ * @param path - File path. Use `:memory:` for in-process testing.
405
+ * @param opts.brainId - Optional explicit brainId; defaults to file basename
406
+ * (or `'default'` for `:memory:`).
407
+ * @param opts.priority - Override the default adapter priority.
408
+ * @returns A fully initialised `Brain` instance with the v2 schema.
331
409
  */
332
- static async open(dbPath) {
410
+ static async openSqlite(path, opts = {}) {
333
411
  const adapter = await resolveStorageAdapter({
334
- filePath: dbPath,
335
- priority: ['better-sqlite3', 'sqljs', 'indexeddb'],
412
+ filePath: path,
413
+ priority: opts.priority ?? ['better-sqlite3', 'sqljs', 'indexeddb'],
336
414
  quiet: true,
337
415
  });
416
+ const brainId = opts.brainId ?? deriveBrainIdFromPath(path);
417
+ return Brain._initialize(adapter, brainId);
418
+ }
419
+ /**
420
+ * Open a Brain backed by PostgreSQL. Requires the `pg` npm package and
421
+ * a reachable Postgres instance.
422
+ *
423
+ * @param connectionString - Standard Postgres connection URL.
424
+ * @param opts.brainId - REQUIRED. Used to scope every query so multiple
425
+ * brains can share one Postgres database without leaking rows.
426
+ * @param opts.poolSize - pg connection pool size. Defaults to 10.
427
+ */
428
+ static async openPostgres(connectionString, opts) {
429
+ if (!opts.brainId) {
430
+ throw new Error('Brain.openPostgres: opts.brainId is required (Postgres mode is multi-tenant)');
431
+ }
432
+ // Use createPostgresAdapter directly so we can pass pool size; the
433
+ // resolveStorageAdapter facade only forwards `connectionString`.
434
+ const adapter = await createPostgresAdapter({
435
+ connectionString,
436
+ max: opts.poolSize ?? 10,
437
+ });
438
+ await adapter.open();
439
+ return Brain._initialize(adapter, opts.brainId);
440
+ }
441
+ /**
442
+ * Open a Brain with a pre-resolved StorageAdapter. Use when sharing an
443
+ * adapter across subsystems (e.g., wilds-ai foundation pool + brain) or
444
+ * when the consumer needs full control over adapter resolution.
445
+ *
446
+ * @param adapter - Pre-built StorageAdapter instance.
447
+ * @param opts.brainId - Required for postgres-kind adapters; optional for
448
+ * sqlite-kind adapters (defaults to `'default'`).
449
+ */
450
+ static async openWithAdapter(adapter, opts = {}) {
451
+ const isPostgres = adapter.kind.includes('postgres');
452
+ if (isPostgres && !opts.brainId) {
453
+ throw new Error('Brain.openWithAdapter: opts.brainId is required for postgres-kind adapters');
454
+ }
455
+ const brainId = opts.brainId ?? 'default';
456
+ return Brain._initialize(adapter, brainId);
457
+ }
458
+ /**
459
+ * Internal common initialization path used by all three factories.
460
+ *
461
+ * Sequence:
462
+ * 1. Build platform-aware feature bundle.
463
+ * 2. Set WAL mode (dialect.pragma returns null on Postgres).
464
+ * 3. Enable foreign key enforcement (dialect.pragma returns null on Postgres).
465
+ * 4. Auto-migrate v1 schemas to v2 (idempotent; no-op for fresh DBs and v2).
466
+ * 5. Apply full DDL via _initSchema().
467
+ * 6. Seed brain_meta defaults.
468
+ */
469
+ static async _initialize(adapter, brainId) {
338
470
  const features = createStorageFeatures(adapter);
339
- const brain = new SqliteBrain(adapter, features);
340
- // Step 1: WAL mode — dialect returns null for non-SQLite adapters.
471
+ const brain = new Brain(adapter, features, brainId);
341
472
  const walPragma = features.dialect.pragma('journal_mode', 'WAL');
342
473
  if (walPragma)
343
474
  await adapter.exec(walPragma);
344
- // Step 2: Foreign key enforcement — dialect returns null for Postgres (enforced by default).
345
475
  const fkPragma = features.dialect.pragma('foreign_keys', 'ON');
346
476
  if (fkPragma)
347
477
  await adapter.exec(fkPragma);
348
- // Step 3: Apply full schema in a single transaction for atomicity.
478
+ await migrateV1ToV2(adapter, features, brainId);
349
479
  await brain._initSchema();
350
- // Step 4: Seed brain_meta defaults if this is a fresh database.
351
480
  await brain._seedMeta();
352
481
  return brain;
353
482
  }
354
- /**
355
- * Alias for `open()` — matches the naming convention used by WildsMemoryFacade.
356
- *
357
- * @param dbPath - Absolute path to the `.sqlite` file.
358
- * @returns A fully initialised `SqliteBrain` instance.
359
- */
360
- static async create(dbPath) {
361
- return SqliteBrain.open(dbPath);
362
- }
363
483
  // ---------------------------------------------------------------------------
364
484
  // Async proxy methods (for consumer subsystems)
365
485
  // ---------------------------------------------------------------------------
@@ -489,8 +609,8 @@ export class SqliteBrain {
489
609
  // INSERT OR IGNORE is idempotent — no transaction needed.
490
610
  // Avoids sql.js "cannot rollback" errors when DDL from _initSchema()
491
611
  // leaves the connection in an implicit-commit state.
492
- await this._adapter.run(dialect.insertOrIgnore('brain_meta', ['key', 'value'], ['?', '?']), ['schema_version', SCHEMA_VERSION]);
493
- await this._adapter.run(dialect.insertOrIgnore('brain_meta', ['key', 'value'], ['?', '?']), ['created_at', Date.now().toString()]);
612
+ await this._adapter.run(dialect.insertOrIgnore('brain_meta', ['brain_id', 'key', 'value'], ['?', '?', '?']), [this._brainId, 'schema_version', SCHEMA_VERSION]);
613
+ await this._adapter.run(dialect.insertOrIgnore('brain_meta', ['brain_id', 'key', 'value'], ['?', '?', '?']), [this._brainId, 'created_at', Date.now().toString()]);
494
614
  }
495
615
  // ---------------------------------------------------------------------------
496
616
  // Public API
@@ -502,7 +622,7 @@ export class SqliteBrain {
502
622
  * @returns The stored string value, or `undefined` if the key does not exist.
503
623
  */
504
624
  async getMeta(key) {
505
- const row = await this._adapter.get('SELECT value FROM brain_meta WHERE key = ?', [key]);
625
+ const row = await this._adapter.get('SELECT value FROM brain_meta WHERE brain_id = ? AND key = ?', [this._brainId, key]);
506
626
  return row?.value;
507
627
  }
508
628
  /**
@@ -515,7 +635,7 @@ export class SqliteBrain {
515
635
  * @param value - The string value to store.
516
636
  */
517
637
  async setMeta(key, value) {
518
- await this._adapter.run(this._features.dialect.insertOrReplace('brain_meta', ['key', 'value'], ['?', '?'], 'key'), [key, value]);
638
+ await this._adapter.run(this._features.dialect.insertOrReplace('brain_meta', ['brain_id', 'key', 'value'], ['?', '?', '?'], 'brain_id, key'), [this._brainId, key, value]);
519
639
  }
520
640
  /**
521
641
  * Check whether a given embedding dimension is compatible with this brain.
@@ -539,6 +659,131 @@ export class SqliteBrain {
539
659
  }
540
660
  return parseInt(stored, 10) === dimensions;
541
661
  }
662
+ // ---------------------------------------------------------------------------
663
+ // Portable artifact: export to / import from a SQLite snapshot
664
+ // ---------------------------------------------------------------------------
665
+ /**
666
+ * Materialize this brain to a portable SQLite file at `targetPath`.
667
+ *
668
+ * Source can be any backend (SQLite, Postgres, Capacitor, etc.); output
669
+ * is always a fresh SQLite file. Used by `.wildsoul`-style export and
670
+ * other portability flows.
671
+ *
672
+ * Refuses to overwrite an existing file at `targetPath` so callers do
673
+ * not silently lose data.
674
+ *
675
+ * Forking semantics: rows are emitted with the source brainId. Importing
676
+ * the resulting file under a different brainId produces a fork.
677
+ *
678
+ * @param targetPath - Destination file path. File must not exist.
679
+ * @returns Bytes written to the destination file.
680
+ */
681
+ async exportToSqlite(targetPath) {
682
+ // Refuse to overwrite an existing file.
683
+ try {
684
+ await fs.access(targetPath);
685
+ throw new Error(`Brain.exportToSqlite: target already exists: ${targetPath}`);
686
+ }
687
+ catch (err) {
688
+ const code = err.code;
689
+ if (code !== 'ENOENT') {
690
+ // Re-throw the "already exists" error and any other access error
691
+ // that isn't a missing-file response.
692
+ throw err;
693
+ }
694
+ }
695
+ // Open a fresh SQLite Brain at the target path. We import under the
696
+ // source brainId so the export file is identifiable as belonging to
697
+ // this brain even if the receiving Brain has a different id.
698
+ const target = await Brain.openSqlite(targetPath, { brainId: this._brainId });
699
+ try {
700
+ for (const table of PORTABLE_TABLES) {
701
+ const rows = await this.all(`SELECT * FROM ${table} WHERE brain_id = ?`, [this._brainId]);
702
+ if (rows.length === 0)
703
+ continue;
704
+ // Upsert so source rows override the brain_meta defaults
705
+ // (schema_version, created_at) seeded during target initialisation.
706
+ await this._bulkCopy(target, table, rows, this._brainId, { upsert: true });
707
+ }
708
+ }
709
+ finally {
710
+ await target.close();
711
+ }
712
+ const stat = await fs.stat(targetPath);
713
+ return { bytesWritten: stat.size };
714
+ }
715
+ /**
716
+ * Load a portable SQLite file into this Brain's adapter.
717
+ *
718
+ * Forking semantics: rows from the source file are written under the
719
+ * RECEIVING brain's `brainId`, not the brainId stored in the source
720
+ * file. This means importing an `alice` snapshot into a Brain opened
721
+ * with `brainId: 'alice-fork'` produces a fork with no shared identity.
722
+ *
723
+ * @param sourcePath - Source SQLite file path (typically produced by
724
+ * `Brain.exportToSqlite`).
725
+ * @param opts.strategy - `'merge'` (default) upserts on PK collision;
726
+ * `'replace'` wipes all rows for the receiving `brainId` first.
727
+ * @returns Counts of rows imported per table.
728
+ */
729
+ async importFromSqlite(sourcePath, opts = {}) {
730
+ const strategy = opts.strategy ?? 'merge';
731
+ const source = await Brain.openSqlite(sourcePath);
732
+ const tablesImported = {};
733
+ try {
734
+ if (strategy === 'replace') {
735
+ // Wipe existing rows for the receiving brainId in every portable table.
736
+ // Order matters: child tables before parent tables to satisfy FKs.
737
+ for (const table of [...PORTABLE_TABLES].reverse()) {
738
+ await this.run(`DELETE FROM ${table} WHERE brain_id = ?`, [this._brainId]);
739
+ }
740
+ }
741
+ for (const table of PORTABLE_TABLES) {
742
+ // Read every row in the source file regardless of its stored brainId
743
+ // so we capture the full snapshot for re-insertion under our brainId.
744
+ const rows = await source.all(`SELECT * FROM ${table}`);
745
+ tablesImported[table] = rows.length;
746
+ if (rows.length === 0)
747
+ continue;
748
+ // Always use upsert to gracefully handle the brain_meta rows seeded
749
+ // by `_seedMeta` during the receiving Brain's initialization (which
750
+ // would otherwise collide with the source's schema_version/created_at).
751
+ await this._bulkCopy(this, table, rows, this._brainId, { upsert: true });
752
+ }
753
+ }
754
+ finally {
755
+ await source.close();
756
+ }
757
+ return { tablesImported };
758
+ }
759
+ /**
760
+ * Internal helper: bulk-insert `rows` into `target.<table>`, rewriting
761
+ * `brain_id` on each row to `targetBrainId`. When `opts.upsert` is true,
762
+ * uses `dialect.insertOrReplace` so PK collisions overwrite (idempotent).
763
+ */
764
+ async _bulkCopy(target, table, rows, targetBrainId, opts = {}) {
765
+ if (rows.length === 0)
766
+ return;
767
+ const columns = Object.keys(rows[0]);
768
+ const placeholders = columns.map(() => '?').join(', ');
769
+ const colList = columns.join(', ');
770
+ const stmt = opts.upsert
771
+ ? target._features.dialect.insertOrReplace(table, columns, columns.map(() => '?'), PORTABLE_TABLE_PRIMARY_KEYS[table] ?? 'brain_id, id')
772
+ : `INSERT INTO ${table} (${colList}) VALUES (${placeholders})`;
773
+ // Single transaction per table for bulk-insert performance + atomicity.
774
+ await target._adapter.exec('BEGIN');
775
+ try {
776
+ for (const row of rows) {
777
+ const values = columns.map((c) => c === 'brain_id' ? targetBrainId : row[c]);
778
+ await target._adapter.run(stmt, values);
779
+ }
780
+ await target._adapter.exec('COMMIT');
781
+ }
782
+ catch (err) {
783
+ await target._adapter.exec('ROLLBACK');
784
+ throw err;
785
+ }
786
+ }
542
787
  /**
543
788
  * Close the database connection.
544
789
  *
@@ -550,4 +795,50 @@ export class SqliteBrain {
550
795
  await this._adapter.close();
551
796
  }
552
797
  }
553
- //# sourceMappingURL=SqliteBrain.js.map
798
+ // ---------------------------------------------------------------------------
799
+ // Portable-artifact constants
800
+ // ---------------------------------------------------------------------------
801
+ /**
802
+ * Tables exported and imported by `Brain.exportToSqlite` / `importFromSqlite`.
803
+ * Order matters for import: parents before children to satisfy FKs.
804
+ */
805
+ const PORTABLE_TABLES = [
806
+ 'brain_meta',
807
+ 'memory_traces',
808
+ 'knowledge_nodes',
809
+ 'knowledge_edges',
810
+ 'documents',
811
+ 'document_chunks',
812
+ 'document_images',
813
+ 'consolidation_log',
814
+ 'retrieval_feedback',
815
+ 'conversations',
816
+ 'messages',
817
+ 'prospective_items',
818
+ 'archived_traces',
819
+ 'archive_access_log',
820
+ ];
821
+ /**
822
+ * Composite primary key columns for each portable table, used by
823
+ * `dialect.insertOrReplace` as the conflict target during merge import.
824
+ *
825
+ * Tables with `INTEGER PRIMARY KEY AUTOINCREMENT` (consolidation_log,
826
+ * retrieval_feedback) use `id` alone since their PK is system-generated.
827
+ */
828
+ const PORTABLE_TABLE_PRIMARY_KEYS = {
829
+ brain_meta: 'brain_id, key',
830
+ memory_traces: 'brain_id, id',
831
+ knowledge_nodes: 'brain_id, id',
832
+ knowledge_edges: 'brain_id, id',
833
+ documents: 'brain_id, id',
834
+ document_chunks: 'brain_id, id',
835
+ document_images: 'brain_id, id',
836
+ consolidation_log: 'id',
837
+ retrieval_feedback: 'id',
838
+ conversations: 'brain_id, id',
839
+ messages: 'brain_id, id',
840
+ prospective_items: 'brain_id, id',
841
+ archived_traces: 'brain_id, trace_id',
842
+ archive_access_log: 'brain_id, trace_id, accessed_at',
843
+ };
844
+ //# sourceMappingURL=Brain.js.map