@monoes/memory 1.0.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 (178) hide show
  1. package/README.md +902 -0
  2. package/dist/agent-memory-scope.d.ts +131 -0
  3. package/dist/agent-memory-scope.d.ts.map +1 -0
  4. package/dist/agent-memory-scope.js +230 -0
  5. package/dist/agent-memory-scope.js.map +1 -0
  6. package/dist/agentdb-adapter.d.ts +165 -0
  7. package/dist/agentdb-adapter.d.ts.map +1 -0
  8. package/dist/agentdb-adapter.js +836 -0
  9. package/dist/agentdb-adapter.js.map +1 -0
  10. package/dist/agentdb-backend.d.ts +218 -0
  11. package/dist/agentdb-backend.d.ts.map +1 -0
  12. package/dist/agentdb-backend.js +879 -0
  13. package/dist/agentdb-backend.js.map +1 -0
  14. package/dist/auto-memory-bridge.d.ts +230 -0
  15. package/dist/auto-memory-bridge.d.ts.map +1 -0
  16. package/dist/auto-memory-bridge.js +730 -0
  17. package/dist/auto-memory-bridge.js.map +1 -0
  18. package/dist/cache-manager.d.ts +134 -0
  19. package/dist/cache-manager.d.ts.map +1 -0
  20. package/dist/cache-manager.js +410 -0
  21. package/dist/cache-manager.js.map +1 -0
  22. package/dist/checkpointer.d.ts +78 -0
  23. package/dist/checkpointer.d.ts.map +1 -0
  24. package/dist/checkpointer.js +174 -0
  25. package/dist/checkpointer.js.map +1 -0
  26. package/dist/controller-registry.d.ts +216 -0
  27. package/dist/controller-registry.d.ts.map +1 -0
  28. package/dist/controller-registry.js +897 -0
  29. package/dist/controller-registry.js.map +1 -0
  30. package/dist/database-provider.d.ts +87 -0
  31. package/dist/database-provider.d.ts.map +1 -0
  32. package/dist/database-provider.js +434 -0
  33. package/dist/database-provider.js.map +1 -0
  34. package/dist/diskann-backend.d.ts +126 -0
  35. package/dist/diskann-backend.d.ts.map +1 -0
  36. package/dist/diskann-backend.js +324 -0
  37. package/dist/diskann-backend.js.map +1 -0
  38. package/dist/episodic-store.d.ts +58 -0
  39. package/dist/episodic-store.d.ts.map +1 -0
  40. package/dist/episodic-store.js +160 -0
  41. package/dist/episodic-store.js.map +1 -0
  42. package/dist/hnsw-index.d.ts +111 -0
  43. package/dist/hnsw-index.d.ts.map +1 -0
  44. package/dist/hnsw-index.js +778 -0
  45. package/dist/hnsw-index.js.map +1 -0
  46. package/dist/hnsw-lite.d.ts +29 -0
  47. package/dist/hnsw-lite.d.ts.map +1 -0
  48. package/dist/hnsw-lite.js +236 -0
  49. package/dist/hnsw-lite.js.map +1 -0
  50. package/dist/hybrid-backend.d.ts +299 -0
  51. package/dist/hybrid-backend.d.ts.map +1 -0
  52. package/dist/hybrid-backend.js +804 -0
  53. package/dist/hybrid-backend.js.map +1 -0
  54. package/dist/index.d.ts +228 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +379 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/knowledge/document-chunker.d.ts +22 -0
  59. package/dist/knowledge/document-chunker.d.ts.map +1 -0
  60. package/dist/knowledge/document-chunker.js +52 -0
  61. package/dist/knowledge/document-chunker.js.map +1 -0
  62. package/dist/knowledge/index.d.ts +12 -0
  63. package/dist/knowledge/index.d.ts.map +1 -0
  64. package/dist/knowledge/index.js +9 -0
  65. package/dist/knowledge/index.js.map +1 -0
  66. package/dist/knowledge/knowledge-retriever.d.ts +48 -0
  67. package/dist/knowledge/knowledge-retriever.d.ts.map +1 -0
  68. package/dist/knowledge/knowledge-retriever.js +80 -0
  69. package/dist/knowledge/knowledge-retriever.js.map +1 -0
  70. package/dist/knowledge/knowledge-store.d.ts +58 -0
  71. package/dist/knowledge/knowledge-store.d.ts.map +1 -0
  72. package/dist/knowledge/knowledge-store.js +121 -0
  73. package/dist/knowledge/knowledge-store.js.map +1 -0
  74. package/dist/learning-bridge.d.ts +162 -0
  75. package/dist/learning-bridge.d.ts.map +1 -0
  76. package/dist/learning-bridge.js +522 -0
  77. package/dist/learning-bridge.js.map +1 -0
  78. package/dist/memory-graph.d.ts +123 -0
  79. package/dist/memory-graph.d.ts.map +1 -0
  80. package/dist/memory-graph.js +412 -0
  81. package/dist/memory-graph.js.map +1 -0
  82. package/dist/migration.d.ts +68 -0
  83. package/dist/migration.d.ts.map +1 -0
  84. package/dist/migration.js +513 -0
  85. package/dist/migration.js.map +1 -0
  86. package/dist/partitioned-hnsw.d.ts +65 -0
  87. package/dist/partitioned-hnsw.d.ts.map +1 -0
  88. package/dist/partitioned-hnsw.js +209 -0
  89. package/dist/partitioned-hnsw.js.map +1 -0
  90. package/dist/persistent-sona.d.ts +144 -0
  91. package/dist/persistent-sona.d.ts.map +1 -0
  92. package/dist/persistent-sona.js +333 -0
  93. package/dist/persistent-sona.js.map +1 -0
  94. package/dist/procedural/action-record.d.ts +19 -0
  95. package/dist/procedural/action-record.d.ts.map +1 -0
  96. package/dist/procedural/action-record.js +51 -0
  97. package/dist/procedural/action-record.js.map +1 -0
  98. package/dist/procedural/action-sequence-extractor.d.ts +25 -0
  99. package/dist/procedural/action-sequence-extractor.d.ts.map +1 -0
  100. package/dist/procedural/action-sequence-extractor.js +91 -0
  101. package/dist/procedural/action-sequence-extractor.js.map +1 -0
  102. package/dist/procedural/index.d.ts +14 -0
  103. package/dist/procedural/index.d.ts.map +1 -0
  104. package/dist/procedural/index.js +13 -0
  105. package/dist/procedural/index.js.map +1 -0
  106. package/dist/procedural/learned-skill.d.ts +17 -0
  107. package/dist/procedural/learned-skill.d.ts.map +1 -0
  108. package/dist/procedural/learned-skill.js +73 -0
  109. package/dist/procedural/learned-skill.js.map +1 -0
  110. package/dist/procedural/procedure-extractor-worker.d.ts +27 -0
  111. package/dist/procedural/procedure-extractor-worker.d.ts.map +1 -0
  112. package/dist/procedural/procedure-extractor-worker.js +39 -0
  113. package/dist/procedural/procedure-extractor-worker.js.map +1 -0
  114. package/dist/procedural/skill-registry.d.ts +25 -0
  115. package/dist/procedural/skill-registry.d.ts.map +1 -0
  116. package/dist/procedural/skill-registry.js +56 -0
  117. package/dist/procedural/skill-registry.js.map +1 -0
  118. package/dist/procedural/types.d.ts +67 -0
  119. package/dist/procedural/types.d.ts.map +1 -0
  120. package/dist/procedural/types.js +15 -0
  121. package/dist/procedural/types.js.map +1 -0
  122. package/dist/prompt-version-store.d.ts +59 -0
  123. package/dist/prompt-version-store.d.ts.map +1 -0
  124. package/dist/prompt-version-store.js +175 -0
  125. package/dist/prompt-version-store.js.map +1 -0
  126. package/dist/query-builder.d.ts +52 -0
  127. package/dist/query-builder.d.ts.map +1 -0
  128. package/dist/query-builder.js +134 -0
  129. package/dist/query-builder.js.map +1 -0
  130. package/dist/rvf-backend.d.ts +51 -0
  131. package/dist/rvf-backend.d.ts.map +1 -0
  132. package/dist/rvf-backend.js +499 -0
  133. package/dist/rvf-backend.js.map +1 -0
  134. package/dist/rvf-learning-store.d.ts +141 -0
  135. package/dist/rvf-learning-store.d.ts.map +1 -0
  136. package/dist/rvf-learning-store.js +344 -0
  137. package/dist/rvf-learning-store.js.map +1 -0
  138. package/dist/rvf-migration.d.ts +45 -0
  139. package/dist/rvf-migration.d.ts.map +1 -0
  140. package/dist/rvf-migration.js +254 -0
  141. package/dist/rvf-migration.js.map +1 -0
  142. package/dist/sqlite-backend.d.ts +127 -0
  143. package/dist/sqlite-backend.d.ts.map +1 -0
  144. package/dist/sqlite-backend.js +618 -0
  145. package/dist/sqlite-backend.js.map +1 -0
  146. package/dist/sqljs-backend.d.ts +128 -0
  147. package/dist/sqljs-backend.d.ts.map +1 -0
  148. package/dist/sqljs-backend.js +611 -0
  149. package/dist/sqljs-backend.js.map +1 -0
  150. package/dist/tier-manager.d.ts +79 -0
  151. package/dist/tier-manager.d.ts.map +1 -0
  152. package/dist/tier-manager.js +169 -0
  153. package/dist/tier-manager.js.map +1 -0
  154. package/dist/tiers/contextual.d.ts +43 -0
  155. package/dist/tiers/contextual.d.ts.map +1 -0
  156. package/dist/tiers/contextual.js +102 -0
  157. package/dist/tiers/contextual.js.map +1 -0
  158. package/dist/tiers/entity.d.ts +46 -0
  159. package/dist/tiers/entity.d.ts.map +1 -0
  160. package/dist/tiers/entity.js +106 -0
  161. package/dist/tiers/entity.js.map +1 -0
  162. package/dist/tiers/index.d.ts +4 -0
  163. package/dist/tiers/index.d.ts.map +1 -0
  164. package/dist/tiers/index.js +4 -0
  165. package/dist/tiers/index.js.map +1 -0
  166. package/dist/tiers/short-term.d.ts +42 -0
  167. package/dist/tiers/short-term.d.ts.map +1 -0
  168. package/dist/tiers/short-term.js +89 -0
  169. package/dist/tiers/short-term.js.map +1 -0
  170. package/dist/types/checkpoint.d.ts +44 -0
  171. package/dist/types/checkpoint.d.ts.map +1 -0
  172. package/dist/types/checkpoint.js +8 -0
  173. package/dist/types/checkpoint.js.map +1 -0
  174. package/dist/types.d.ts +526 -0
  175. package/dist/types.d.ts.map +1 -0
  176. package/dist/types.js +58 -0
  177. package/dist/types.js.map +1 -0
  178. package/package.json +46 -0
@@ -0,0 +1,804 @@
1
+ /**
2
+ * HybridBackend - Combines SQLite (structured queries) + AgentDB (vector search)
3
+ *
4
+ * Per ADR-009: "HybridBackend (SQLite + AgentDB) as default"
5
+ * - SQLite for: Structured queries, ACID transactions, exact matches
6
+ * - AgentDB for: Semantic search, vector similarity, RAG
7
+ *
8
+ * @module v1/memory/hybrid-backend
9
+ */
10
+ import { EventEmitter } from 'node:events';
11
+ import { SQLiteBackend } from './sqlite-backend.js';
12
+ import { AgentDBBackend } from './agentdb-backend.js';
13
+ import { MemoryGraph } from './memory-graph.js';
14
+ /**
15
+ * Default configuration
16
+ */
17
+ const DEFAULT_CONFIG = {
18
+ sqlite: {},
19
+ agentdb: {},
20
+ defaultNamespace: 'default',
21
+ embeddingGenerator: undefined,
22
+ routingStrategy: 'auto',
23
+ dualWrite: true,
24
+ semanticThreshold: 0.7,
25
+ hybridMaxResults: 100,
26
+ filterInjection: false,
27
+ memoragRewriter: undefined,
28
+ };
29
+ /**
30
+ * Structural prompt-injection patterns for filtering externally-sourced content.
31
+ * Source: arXiv:2302.12173, arXiv:2310.12815 — indirect prompt injection in RAG pipelines.
32
+ */
33
+ const INJECTION_PATTERNS = [
34
+ /ignore\s+(all\s+)?previous\s+instructions?/i,
35
+ /you\s+are\s+now\s+(a|an)\s+/i,
36
+ /system\s*prompt\s*[:=]/i,
37
+ /\[\s*system\s*\]/i,
38
+ /\bforget\s+(your\s+)?(previous\s+)?instructions?\b/i,
39
+ /act\s+as\s+(if\s+you\s+(are|were)\s+)?/i,
40
+ /jailbreak/i,
41
+ /\bDAN\b/,
42
+ /<\s*\/?system\s*>/i,
43
+ ];
44
+ /**
45
+ * HybridBackend Implementation
46
+ *
47
+ * Intelligently routes queries between SQLite and AgentDB:
48
+ * - Exact matches, prefix queries → SQLite
49
+ * - Semantic search, similarity → AgentDB
50
+ * - Complex hybrid queries → Both backends with intelligent merging
51
+ */
52
+ export class HybridBackend extends EventEmitter {
53
+ sqlite;
54
+ agentdb;
55
+ config;
56
+ initialized = false;
57
+ /** Lazy MemoryGraph for HippoRAG PPR re-ranking */
58
+ memoryGraph = null;
59
+ // Performance tracking
60
+ stats = {
61
+ sqliteQueries: 0,
62
+ agentdbQueries: 0,
63
+ hybridQueries: 0,
64
+ totalQueryTime: 0,
65
+ };
66
+ constructor(config = {}) {
67
+ super();
68
+ this.config = { ...DEFAULT_CONFIG, ...config };
69
+ // Initialize SQLite backend
70
+ this.sqlite = new SQLiteBackend({
71
+ ...this.config.sqlite,
72
+ defaultNamespace: this.config.defaultNamespace,
73
+ embeddingGenerator: this.config.embeddingGenerator,
74
+ });
75
+ // Initialize AgentDB backend
76
+ this.agentdb = new AgentDBBackend({
77
+ ...this.config.agentdb,
78
+ namespace: this.config.defaultNamespace,
79
+ embeddingGenerator: this.config.embeddingGenerator,
80
+ });
81
+ // Forward events from both backends
82
+ this.sqlite.on('entry:stored', (data) => this.emit('sqlite:stored', data));
83
+ this.sqlite.on('entry:updated', (data) => this.emit('sqlite:updated', data));
84
+ this.sqlite.on('entry:deleted', (data) => this.emit('sqlite:deleted', data));
85
+ this.agentdb.on('entry:stored', (data) => this.emit('agentdb:stored', data));
86
+ this.agentdb.on('entry:updated', (data) => this.emit('agentdb:updated', data));
87
+ this.agentdb.on('entry:deleted', (data) => this.emit('agentdb:deleted', data));
88
+ this.agentdb.on('cache:hit', (data) => this.emit('cache:hit', data));
89
+ this.agentdb.on('cache:miss', (data) => this.emit('cache:miss', data));
90
+ }
91
+ /**
92
+ * Initialize both backends
93
+ */
94
+ async initialize() {
95
+ if (this.initialized)
96
+ return;
97
+ await Promise.all([this.sqlite.initialize(), this.agentdb.initialize()]);
98
+ this.initialized = true;
99
+ this.emit('initialized');
100
+ }
101
+ /**
102
+ * Shutdown both backends
103
+ */
104
+ async shutdown() {
105
+ if (!this.initialized)
106
+ return;
107
+ await Promise.all([this.sqlite.shutdown(), this.agentdb.shutdown()]);
108
+ this.initialized = false;
109
+ this.emit('shutdown');
110
+ }
111
+ /**
112
+ * Store in both backends (dual-write for consistency).
113
+ *
114
+ * A-MEM auto-linking: after writing, HNSW-searches for the top-3 most similar
115
+ * existing entries and adds bidirectional `references` edges between them and
116
+ * the new entry. Only runs when an `embeddingGenerator` is configured.
117
+ * Source: arXiv:2409.11987 (A-MEM — Zettelkasten-style agent memory)
118
+ */
119
+ async store(entry) {
120
+ if (this.config.dualWrite) {
121
+ // Write to both backends in parallel
122
+ await Promise.all([this.sqlite.store(entry), this.agentdb.store(entry)]);
123
+ }
124
+ else {
125
+ // Write to primary backend only (AgentDB has vector search)
126
+ await this.agentdb.store(entry);
127
+ }
128
+ this.emit('entry:stored', { id: entry.id });
129
+ // A-MEM: auto-link to top-3 semantic neighbors (non-blocking, best-effort)
130
+ if (typeof this.config.embeddingGenerator === 'function' && entry.content) {
131
+ this.aMemAutoLink(entry).catch(() => {
132
+ // Linking is best-effort; storage already succeeded
133
+ });
134
+ }
135
+ }
136
+ /**
137
+ * A-MEM bidirectional reference linking.
138
+ * Finds the top-3 semantically similar existing entries and creates
139
+ * `references` edges in both directions.
140
+ */
141
+ async aMemAutoLink(newEntry) {
142
+ try {
143
+ const embedding = await this.config.embeddingGenerator(newEntry.content);
144
+ const neighbors = await this.agentdb.search(embedding, {
145
+ k: 4, // fetch 4 to exclude self
146
+ threshold: 0.75,
147
+ });
148
+ const topNeighbors = neighbors
149
+ .filter(r => r.entry.id !== newEntry.id)
150
+ .slice(0, 3);
151
+ if (topNeighbors.length === 0)
152
+ return;
153
+ const neighborIds = topNeighbors.map(r => r.entry.id);
154
+ // Update new entry to reference neighbors
155
+ const newRefs = [...new Set([...(newEntry.references ?? []), ...neighborIds])];
156
+ await this.update(newEntry.id, { references: newRefs });
157
+ // Update each neighbor to back-reference the new entry
158
+ await Promise.all(topNeighbors.map(async ({ entry: neighbor }) => {
159
+ const backRefs = [...new Set([...(neighbor.references ?? []), newEntry.id])];
160
+ await this.update(neighbor.id, { references: backRefs });
161
+ }));
162
+ this.emit('amem:linked', { id: newEntry.id, linkedTo: neighborIds });
163
+ }
164
+ catch {
165
+ // Linking is best-effort
166
+ }
167
+ }
168
+ /**
169
+ * Get from AgentDB (has caching enabled).
170
+ *
171
+ * Collaborative memory promotion: when `agentId` is supplied, also notifies
172
+ * the SQLite backend so it can track the read and promote the entry's
173
+ * AccessLevel to 'team' once 3+ distinct agents have accessed it.
174
+ * Source: newinnovation.md §2.7 — memory access-level promotion.
175
+ */
176
+ async get(id, agentId) {
177
+ const entry = await this.agentdb.get(id);
178
+ // Track the read in SQLite for collaborative promotion (best-effort, non-blocking)
179
+ if (agentId && entry) {
180
+ this.sqlite.get(id, agentId).catch(() => { });
181
+ }
182
+ return entry;
183
+ }
184
+ /**
185
+ * Get by key (SQLite optimized for exact matches)
186
+ */
187
+ async getByKey(namespace, key) {
188
+ return this.sqlite.getByKey(namespace, key);
189
+ }
190
+ /**
191
+ * Update in both backends
192
+ */
193
+ async update(id, update) {
194
+ if (this.config.dualWrite) {
195
+ // Update both backends
196
+ const [sqliteResult, agentdbResult] = await Promise.all([
197
+ this.sqlite.update(id, update),
198
+ this.agentdb.update(id, update),
199
+ ]);
200
+ return agentdbResult || sqliteResult;
201
+ }
202
+ else {
203
+ return this.agentdb.update(id, update);
204
+ }
205
+ }
206
+ /**
207
+ * Delete from both backends
208
+ */
209
+ async delete(id) {
210
+ if (this.config.dualWrite) {
211
+ const [sqliteResult, agentdbResult] = await Promise.all([
212
+ this.sqlite.delete(id),
213
+ this.agentdb.delete(id),
214
+ ]);
215
+ return sqliteResult || agentdbResult;
216
+ }
217
+ else {
218
+ return this.agentdb.delete(id);
219
+ }
220
+ }
221
+ /**
222
+ * Query routing - semantic goes to AgentDB, structured to SQLite
223
+ */
224
+ async query(query) {
225
+ const startTime = performance.now();
226
+ let results;
227
+ // Route based on query type
228
+ switch (query.type) {
229
+ case 'exact':
230
+ // SQLite optimized for exact matches
231
+ this.stats.sqliteQueries++;
232
+ results = await this.sqlite.query(query);
233
+ break;
234
+ case 'prefix':
235
+ // SQLite optimized for prefix queries
236
+ this.stats.sqliteQueries++;
237
+ results = await this.sqlite.query(query);
238
+ break;
239
+ case 'tag':
240
+ // Both can handle tags, use SQLite for structured filtering
241
+ this.stats.sqliteQueries++;
242
+ results = await this.sqlite.query(query);
243
+ break;
244
+ case 'semantic':
245
+ // AgentDB optimized for semantic search
246
+ this.stats.agentdbQueries++;
247
+ results = await this.agentdb.query(query);
248
+ break;
249
+ case 'hybrid':
250
+ // Use hybrid query combining both backends
251
+ this.stats.hybridQueries++;
252
+ results = await this.queryHybridInternal(query);
253
+ break;
254
+ default:
255
+ // Auto-routing based on query properties
256
+ results = await this.autoRoute(query);
257
+ }
258
+ const duration = performance.now() - startTime;
259
+ this.stats.totalQueryTime += duration;
260
+ this.emit('query:completed', { type: query.type, duration, count: results.length });
261
+ return results;
262
+ }
263
+ /**
264
+ * Structured queries (SQL)
265
+ * Routes to SQLite for optimal performance
266
+ */
267
+ async queryStructured(query) {
268
+ this.stats.sqliteQueries++;
269
+ const memoryQuery = {
270
+ type: query.key ? 'exact' : query.keyPrefix ? 'prefix' : 'hybrid',
271
+ key: query.key,
272
+ keyPrefix: query.keyPrefix,
273
+ namespace: query.namespace,
274
+ ownerId: query.ownerId,
275
+ memoryType: query.type,
276
+ createdAfter: query.createdAfter,
277
+ createdBefore: query.createdBefore,
278
+ updatedAfter: query.updatedAfter,
279
+ updatedBefore: query.updatedBefore,
280
+ limit: query.limit || 100,
281
+ offset: query.offset || 0,
282
+ };
283
+ return this.sqlite.query(memoryQuery);
284
+ }
285
+ /**
286
+ * Apply injection filtering and structured field filters to a set of entries.
287
+ * Used by the MemoRAG path (RRF-fused results) and as a shared post-processing step.
288
+ */
289
+ postProcessSemanticResults(entries, query) {
290
+ let result = entries;
291
+ if (this.config.filterInjection) {
292
+ result = result.filter(entry => {
293
+ const content = entry.content ?? '';
294
+ if (INJECTION_PATTERNS.some(rx => rx.test(content))) {
295
+ this.emit('injection:blocked', { id: entry.id, namespace: entry.namespace });
296
+ return false;
297
+ }
298
+ return true;
299
+ });
300
+ }
301
+ if (query.filters) {
302
+ const f = query.filters;
303
+ if (f.tags && Array.isArray(f.tags)) {
304
+ const requiredTags = f.tags;
305
+ result = result.filter(e => requiredTags.every(t => e.tags.includes(t)));
306
+ }
307
+ if (f.namespace && typeof f.namespace === 'string') {
308
+ result = result.filter(e => e.namespace === f.namespace);
309
+ }
310
+ if (f.type && typeof f.type === 'string' && f.type !== 'semantic') {
311
+ result = result.filter(e => e.type === f.type);
312
+ }
313
+ }
314
+ return result.slice(0, query.k || 10);
315
+ }
316
+ /**
317
+ * Reciprocal Rank Fusion — merge multiple ranked result lists.
318
+ * Score for entry i in list j: 1 / (k + rank_j(i)) where k=60 (standard).
319
+ * Source: Cormack et al., "Reciprocal Rank Fusion outperforms Condorcet and individual
320
+ * Rank Learning Methods" (SIGIR 2009). Used by MemoRAG sub-query fusion.
321
+ */
322
+ rrfFuse(rankedLists, k = 60) {
323
+ const scores = new Map();
324
+ const byId = new Map();
325
+ for (const list of rankedLists) {
326
+ for (let rank = 0; rank < list.length; rank++) {
327
+ const entry = list[rank];
328
+ byId.set(entry.id, entry);
329
+ scores.set(entry.id, (scores.get(entry.id) ?? 0) + 1 / (k + rank + 1));
330
+ }
331
+ }
332
+ return [...byId.keys()]
333
+ .sort((a, b) => (scores.get(b) ?? 0) - (scores.get(a) ?? 0))
334
+ .map(id => byId.get(id));
335
+ }
336
+ /**
337
+ * Semantic queries (vector)
338
+ * Routes to AgentDB for HNSW-based vector search.
339
+ *
340
+ * MemoRAG query rewriting (arXiv:2409.05591): when `memoragRewriter` is configured
341
+ * and `query.content` is present, generates 2-3 reformulated sub-queries, embeds
342
+ * each, searches HNSW independently, then fuses with Reciprocal Rank Fusion (RRF)
343
+ * before HippoRAG PPR re-ranking.
344
+ */
345
+ async querySemantic(query) {
346
+ this.stats.agentdbQueries++;
347
+ let searchResults = [];
348
+ // MemoRAG: expand the query into multiple sub-queries for richer recall
349
+ let memoragUsed = false;
350
+ if (query.content && typeof this.config.memoragRewriter === 'function') {
351
+ try {
352
+ const subQueries = await this.config.memoragRewriter(query.content);
353
+ if (subQueries.length > 0) {
354
+ // Embed all sub-queries and search in parallel
355
+ const subResults = await Promise.all(subQueries.map(async (sq) => {
356
+ if (!this.config.embeddingGenerator)
357
+ return [];
358
+ const sqEmbedding = await this.config.embeddingGenerator(sq);
359
+ const results = await this.agentdb.search(sqEmbedding, {
360
+ k: (query.k || 10) * 2,
361
+ threshold: query.threshold || this.config.semanticThreshold,
362
+ filters: query.filters,
363
+ });
364
+ return results.map(r => r.entry);
365
+ }));
366
+ this.emit('memorag:rewritten', { original: query.content, subQueries, count: subResults.flat().length });
367
+ // RRF-fuse the sub-query results; assign to searchResults to fall through to PPR/GraphRAG
368
+ const fused = this.rrfFuse(subResults);
369
+ searchResults = fused.map(entry => ({ entry, score: 1.0, distance: 0 }));
370
+ memoragUsed = true;
371
+ }
372
+ }
373
+ catch {
374
+ // Rewriter failed — fall through to standard path
375
+ }
376
+ }
377
+ if (!memoragUsed) {
378
+ let embedding = query.embedding;
379
+ // Generate embedding if content provided
380
+ if (!embedding && query.content && this.config.embeddingGenerator) {
381
+ embedding = await this.config.embeddingGenerator(query.content);
382
+ }
383
+ if (!embedding) {
384
+ throw new Error('SemanticQuery requires either content or embedding');
385
+ }
386
+ searchResults = await this.agentdb.search(embedding, {
387
+ k: (query.k || 10) * 2, // Over-fetch to account for post-filtering
388
+ threshold: query.threshold || this.config.semanticThreshold,
389
+ filters: query.filters,
390
+ });
391
+ }
392
+ // HippoRAG PPR re-ranking: expand one hop through MemoryEntry.references
393
+ // Source: https://arxiv.org/abs/2405.14831
394
+ let entries;
395
+ if (searchResults.length > 0) {
396
+ if (!this.memoryGraph) {
397
+ this.memoryGraph = new MemoryGraph();
398
+ // Seed graph with reference edges from search results
399
+ for (const r of searchResults) {
400
+ this.memoryGraph.addNode(r.entry);
401
+ for (const refId of (r.entry.references ?? [])) {
402
+ this.memoryGraph.addEdge(r.entry.id, refId, 'reference', 1.0);
403
+ }
404
+ }
405
+ }
406
+ else {
407
+ for (const r of searchResults) {
408
+ this.memoryGraph.addNode(r.entry);
409
+ for (const refId of (r.entry.references ?? [])) {
410
+ this.memoryGraph.addEdge(r.entry.id, refId, 'reference', 1.0);
411
+ }
412
+ }
413
+ }
414
+ // GraphRAG: compute community summaries before PPR so communities are populated.
415
+ // Community summaries are prepended to each entry's metadata so downstream callers
416
+ // can use global thematic context alongside local vector similarity.
417
+ // Source: https://arxiv.org/abs/2404.16130 (Microsoft GraphRAG)
418
+ const communitySummaries = this.memoryGraph.getCommunitySummaries(5);
419
+ // Build a nodeId → community summary lookup for O(1) annotation
420
+ const nodeToSummary = new Map();
421
+ for (const summary of communitySummaries) {
422
+ for (const nodeId of summary.topNodeIds) {
423
+ nodeToSummary.set(nodeId, {
424
+ communityId: summary.communityId,
425
+ nodeCount: summary.nodeCount,
426
+ avgPageRank: summary.avgPageRank,
427
+ });
428
+ }
429
+ }
430
+ const reranked = await this.memoryGraph.pprRerank(searchResults, this.agentdb);
431
+ // Annotate each entry with its community ID and summary from GraphRAG
432
+ entries = reranked.map((r) => {
433
+ const commSummary = nodeToSummary.get(r.entry.id);
434
+ const community = r.community ?? commSummary?.communityId;
435
+ if (!community && !commSummary)
436
+ return r.entry;
437
+ return {
438
+ ...r.entry,
439
+ metadata: {
440
+ ...(r.entry.metadata ?? {}),
441
+ community,
442
+ ...(commSummary ? {
443
+ communityNodeCount: commSummary.nodeCount,
444
+ communityAvgPageRank: commSummary.avgPageRank,
445
+ } : {}),
446
+ },
447
+ };
448
+ });
449
+ }
450
+ else {
451
+ entries = searchResults.map((r) => r.entry);
452
+ }
453
+ // Injection filter: remove entries whose content matches known injection signatures
454
+ if (this.config.filterInjection) {
455
+ const safe = [];
456
+ for (const entry of entries) {
457
+ const content = entry.content ?? '';
458
+ if (INJECTION_PATTERNS.some(rx => rx.test(content))) {
459
+ this.emit('injection:blocked', { id: entry.id, namespace: entry.namespace });
460
+ }
461
+ else {
462
+ safe.push(entry);
463
+ }
464
+ }
465
+ entries = safe;
466
+ }
467
+ // Apply tag/namespace/type filters that AgentDB may not enforce
468
+ if (query.filters) {
469
+ const f = query.filters;
470
+ if (f.tags && Array.isArray(f.tags)) {
471
+ const requiredTags = f.tags;
472
+ entries = entries.filter((e) => requiredTags.every((t) => e.tags.includes(t)));
473
+ }
474
+ if (f.namespace && typeof f.namespace === 'string') {
475
+ entries = entries.filter((e) => e.namespace === f.namespace);
476
+ }
477
+ if (f.type && typeof f.type === 'string' && f.type !== 'semantic') {
478
+ entries = entries.filter((e) => e.type === f.type);
479
+ }
480
+ }
481
+ return entries.slice(0, query.k || 10);
482
+ }
483
+ /**
484
+ * Hybrid queries (combine both)
485
+ * Intelligently merges results from both backends
486
+ */
487
+ async queryHybrid(query) {
488
+ this.stats.hybridQueries++;
489
+ const strategy = query.combineStrategy || 'semantic-first';
490
+ const weights = query.weights || { semantic: 0.7, structured: 0.3 };
491
+ // Execute both queries in parallel
492
+ const [semanticResults, structuredResults] = await Promise.all([
493
+ this.querySemantic(query.semantic),
494
+ query.structured ? this.queryStructured(query.structured) : Promise.resolve([]),
495
+ ]);
496
+ // Combine results based on strategy
497
+ switch (strategy) {
498
+ case 'union':
499
+ return this.combineUnion(semanticResults, structuredResults);
500
+ case 'intersection':
501
+ return this.combineIntersection(semanticResults, structuredResults);
502
+ case 'semantic-first':
503
+ return this.combineSemanticFirst(semanticResults, structuredResults);
504
+ case 'structured-first':
505
+ return this.combineStructuredFirst(semanticResults, structuredResults);
506
+ default:
507
+ return this.combineUnion(semanticResults, structuredResults);
508
+ }
509
+ }
510
+ /**
511
+ * Semantic vector search (routes to AgentDB)
512
+ */
513
+ async search(embedding, options) {
514
+ this.stats.agentdbQueries++;
515
+ return this.agentdb.search(embedding, options);
516
+ }
517
+ /**
518
+ * Bulk insert to both backends
519
+ */
520
+ async bulkInsert(entries) {
521
+ if (this.config.dualWrite) {
522
+ await Promise.all([this.sqlite.bulkInsert(entries), this.agentdb.bulkInsert(entries)]);
523
+ }
524
+ else {
525
+ await this.agentdb.bulkInsert(entries);
526
+ }
527
+ }
528
+ /**
529
+ * Bulk delete from both backends
530
+ */
531
+ async bulkDelete(ids) {
532
+ if (this.config.dualWrite) {
533
+ const [sqliteCount, agentdbCount] = await Promise.all([
534
+ this.sqlite.bulkDelete(ids),
535
+ this.agentdb.bulkDelete(ids),
536
+ ]);
537
+ return Math.max(sqliteCount, agentdbCount);
538
+ }
539
+ else {
540
+ return this.agentdb.bulkDelete(ids);
541
+ }
542
+ }
543
+ /**
544
+ * Count entries (use SQLite for efficiency)
545
+ */
546
+ async count(namespace) {
547
+ return this.sqlite.count(namespace);
548
+ }
549
+ /**
550
+ * List namespaces (use SQLite)
551
+ */
552
+ async listNamespaces() {
553
+ return this.sqlite.listNamespaces();
554
+ }
555
+ /**
556
+ * Clear namespace in both backends
557
+ */
558
+ async clearNamespace(namespace) {
559
+ if (this.config.dualWrite) {
560
+ const [sqliteCount, agentdbCount] = await Promise.all([
561
+ this.sqlite.clearNamespace(namespace),
562
+ this.agentdb.clearNamespace(namespace),
563
+ ]);
564
+ return Math.max(sqliteCount, agentdbCount);
565
+ }
566
+ else {
567
+ return this.agentdb.clearNamespace(namespace);
568
+ }
569
+ }
570
+ /**
571
+ * Get combined statistics from both backends
572
+ */
573
+ async getStats() {
574
+ const [sqliteStats, agentdbStats] = await Promise.all([
575
+ this.sqlite.getStats(),
576
+ this.agentdb.getStats(),
577
+ ]);
578
+ return {
579
+ totalEntries: Math.max(sqliteStats.totalEntries, agentdbStats.totalEntries),
580
+ entriesByNamespace: agentdbStats.entriesByNamespace,
581
+ entriesByType: agentdbStats.entriesByType,
582
+ memoryUsage: sqliteStats.memoryUsage + agentdbStats.memoryUsage,
583
+ hnswStats: agentdbStats.hnswStats ?? {
584
+ vectorCount: 0, // 0 = no active HNSW index; do not fabricate from totalEntries
585
+ memoryUsage: 0,
586
+ avgSearchTime: 0,
587
+ buildTime: 0,
588
+ compressionRatio: 1.0,
589
+ },
590
+ cacheStats: agentdbStats.cacheStats ?? {
591
+ hitRate: 0,
592
+ size: 0,
593
+ maxSize: 1000,
594
+ },
595
+ avgQueryTime: this.stats.hybridQueries + this.stats.sqliteQueries + this.stats.agentdbQueries > 0
596
+ ? this.stats.totalQueryTime /
597
+ (this.stats.hybridQueries + this.stats.sqliteQueries + this.stats.agentdbQueries)
598
+ : 0,
599
+ avgSearchTime: agentdbStats.avgSearchTime,
600
+ };
601
+ }
602
+ /**
603
+ * Health check for both backends
604
+ */
605
+ async healthCheck() {
606
+ const [sqliteHealth, agentdbHealth] = await Promise.all([
607
+ this.sqlite.healthCheck(),
608
+ this.agentdb.healthCheck(),
609
+ ]);
610
+ const allIssues = [...sqliteHealth.issues, ...agentdbHealth.issues];
611
+ const allRecommendations = [
612
+ ...sqliteHealth.recommendations,
613
+ ...agentdbHealth.recommendations,
614
+ ];
615
+ // Determine overall status
616
+ let status = 'healthy';
617
+ if (sqliteHealth.status === 'unhealthy' ||
618
+ agentdbHealth.status === 'unhealthy') {
619
+ status = 'unhealthy';
620
+ }
621
+ else if (sqliteHealth.status === 'degraded' ||
622
+ agentdbHealth.status === 'degraded') {
623
+ status = 'degraded';
624
+ }
625
+ return {
626
+ status,
627
+ components: {
628
+ storage: sqliteHealth.components.storage,
629
+ index: agentdbHealth.components.index,
630
+ cache: agentdbHealth.components.cache,
631
+ },
632
+ timestamp: Date.now(),
633
+ issues: allIssues,
634
+ recommendations: allRecommendations,
635
+ };
636
+ }
637
+ // ===== Private Methods =====
638
+ /**
639
+ * Auto-route queries based on properties
640
+ */
641
+ async autoRoute(query) {
642
+ // If has embedding or content, use semantic search (AgentDB)
643
+ const hasEmbeddingGenerator = typeof this.config.embeddingGenerator === 'function';
644
+ if (query.embedding || (query.content && hasEmbeddingGenerator)) {
645
+ this.stats.agentdbQueries++;
646
+ return this.agentdb.query(query);
647
+ }
648
+ // If has exact key or prefix, use structured search (SQLite)
649
+ if (query.key || query.keyPrefix) {
650
+ this.stats.sqliteQueries++;
651
+ return this.sqlite.query(query);
652
+ }
653
+ // For other filters, use routing strategy
654
+ switch (this.config.routingStrategy) {
655
+ case 'sqlite-first':
656
+ this.stats.sqliteQueries++;
657
+ return this.sqlite.query(query);
658
+ case 'agentdb-first':
659
+ this.stats.agentdbQueries++;
660
+ return this.agentdb.query(query);
661
+ case 'auto':
662
+ default:
663
+ // Default to AgentDB (has caching)
664
+ this.stats.agentdbQueries++;
665
+ return this.agentdb.query(query);
666
+ }
667
+ }
668
+ /**
669
+ * Internal hybrid query implementation
670
+ */
671
+ async queryHybridInternal(query) {
672
+ // If semantic component exists, use hybrid
673
+ if (query.embedding || query.content) {
674
+ const semanticQuery = {
675
+ content: query.content,
676
+ embedding: query.embedding,
677
+ k: query.limit || 10,
678
+ threshold: query.threshold,
679
+ filters: query,
680
+ };
681
+ const structuredQuery = {
682
+ namespace: query.namespace,
683
+ key: query.key,
684
+ keyPrefix: query.keyPrefix,
685
+ ownerId: query.ownerId,
686
+ type: query.memoryType,
687
+ createdAfter: query.createdAfter,
688
+ createdBefore: query.createdBefore,
689
+ updatedAfter: query.updatedAfter,
690
+ updatedBefore: query.updatedBefore,
691
+ limit: query.limit,
692
+ offset: query.offset,
693
+ };
694
+ return this.queryHybrid({
695
+ semantic: semanticQuery,
696
+ structured: structuredQuery,
697
+ combineStrategy: 'semantic-first',
698
+ });
699
+ }
700
+ // Otherwise, route to structured
701
+ return this.autoRoute(query);
702
+ }
703
+ /**
704
+ * Combine results using union (all unique results)
705
+ */
706
+ combineUnion(semanticResults, structuredResults) {
707
+ const seen = new Set();
708
+ const combined = [];
709
+ for (const entry of [...semanticResults, ...structuredResults]) {
710
+ if (!seen.has(entry.id)) {
711
+ seen.add(entry.id);
712
+ combined.push(entry);
713
+ }
714
+ }
715
+ return combined;
716
+ }
717
+ /**
718
+ * Combine results using intersection (only common results)
719
+ */
720
+ combineIntersection(semanticResults, structuredResults) {
721
+ const semanticIds = new Set(semanticResults.map((e) => e.id));
722
+ return structuredResults.filter((e) => semanticIds.has(e.id));
723
+ }
724
+ /**
725
+ * Semantic-first: Prefer semantic results, add structured if not present
726
+ */
727
+ combineSemanticFirst(semanticResults, structuredResults) {
728
+ const semanticIds = new Set(semanticResults.map((e) => e.id));
729
+ const additional = structuredResults.filter((e) => !semanticIds.has(e.id));
730
+ return [...semanticResults, ...additional];
731
+ }
732
+ /**
733
+ * Structured-first: Prefer structured results, add semantic if not present
734
+ */
735
+ combineStructuredFirst(semanticResults, structuredResults) {
736
+ const structuredIds = new Set(structuredResults.map((e) => e.id));
737
+ const additional = semanticResults.filter((e) => !structuredIds.has(e.id));
738
+ return [...structuredResults, ...additional];
739
+ }
740
+ // ===== Proxy Methods for AgentDB v1 Controllers (ADR-053 #1212) =====
741
+ /**
742
+ * Record feedback for a memory entry.
743
+ * Delegates to AgentDB's recordFeedback when available.
744
+ * Gracefully degrades to a no-op when AgentDB is unavailable.
745
+ */
746
+ async recordFeedback(entryId, feedback) {
747
+ const agentdbInstance = this.agentdb.getAgentDB?.();
748
+ if (agentdbInstance && typeof agentdbInstance.recordFeedback === 'function') {
749
+ try {
750
+ await agentdbInstance.recordFeedback(entryId, feedback);
751
+ this.emit('feedback:recorded', { entryId, score: feedback.score });
752
+ return true;
753
+ }
754
+ catch {
755
+ // AgentDB feedback recording failed — degrade silently
756
+ }
757
+ }
758
+ return false;
759
+ }
760
+ /**
761
+ * Verify a witness chain for a memory entry.
762
+ * Delegates to AgentDB's verifyWitnessChain when available.
763
+ */
764
+ async verifyWitnessChain(entryId) {
765
+ const agentdbInstance = this.agentdb.getAgentDB?.();
766
+ if (agentdbInstance && typeof agentdbInstance.verifyWitnessChain === 'function') {
767
+ try {
768
+ return await agentdbInstance.verifyWitnessChain(entryId);
769
+ }
770
+ catch {
771
+ // Verification failed — return degraded result
772
+ }
773
+ }
774
+ return { valid: false, chainLength: 0, errors: ['AgentDB not available'] };
775
+ }
776
+ /**
777
+ * Get the witness chain for a memory entry.
778
+ * Delegates to AgentDB's getWitnessChain when available.
779
+ */
780
+ async getWitnessChain(entryId) {
781
+ const agentdbInstance = this.agentdb.getAgentDB?.();
782
+ if (agentdbInstance && typeof agentdbInstance.getWitnessChain === 'function') {
783
+ try {
784
+ return await agentdbInstance.getWitnessChain(entryId);
785
+ }
786
+ catch {
787
+ // Chain retrieval failed
788
+ }
789
+ }
790
+ return [];
791
+ }
792
+ // ===== Backend Access =====
793
+ /**
794
+ * Get underlying backends for advanced operations
795
+ */
796
+ getSQLiteBackend() {
797
+ return this.sqlite;
798
+ }
799
+ getAgentDBBackend() {
800
+ return this.agentdb;
801
+ }
802
+ }
803
+ export default HybridBackend;
804
+ //# sourceMappingURL=hybrid-backend.js.map