@monoes/memory 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -24
- package/dist/agent-memory-scope.d.ts +1 -1
- package/dist/agent-memory-scope.js +2 -2
- package/dist/agent-memory-scope.js.map +1 -1
- package/dist/auto-memory-bridge.d.ts +12 -12
- package/dist/auto-memory-bridge.d.ts.map +1 -1
- package/dist/auto-memory-bridge.js +24 -18
- package/dist/auto-memory-bridge.js.map +1 -1
- package/dist/cache-manager.d.ts.map +1 -1
- package/dist/cache-manager.js +7 -3
- package/dist/cache-manager.js.map +1 -1
- package/dist/checkpointer.d.ts.map +1 -1
- package/dist/checkpointer.js +14 -4
- package/dist/checkpointer.js.map +1 -1
- package/dist/controller-registry.d.ts +14 -20
- package/dist/controller-registry.d.ts.map +1 -1
- package/dist/controller-registry.js +52 -423
- package/dist/controller-registry.js.map +1 -1
- package/dist/database-provider.d.ts.map +1 -1
- package/dist/database-provider.js +3 -15
- package/dist/database-provider.js.map +1 -1
- package/dist/diskann-backend.d.ts.map +1 -1
- package/dist/diskann-backend.js +16 -4
- package/dist/diskann-backend.js.map +1 -1
- package/dist/episodic-store.js +5 -5
- package/dist/episodic-store.js.map +1 -1
- package/dist/hnsw-index.d.ts +0 -1
- package/dist/hnsw-index.d.ts.map +1 -1
- package/dist/hnsw-index.js +2 -48
- package/dist/hnsw-index.js.map +1 -1
- package/dist/hnsw-lite.d.ts.map +1 -1
- package/dist/hnsw-lite.js +5 -1
- package/dist/hnsw-lite.js.map +1 -1
- package/dist/hybrid-backend.d.ts +29 -31
- package/dist/hybrid-backend.d.ts.map +1 -1
- package/dist/hybrid-backend.js +153 -152
- package/dist/hybrid-backend.js.map +1 -1
- package/dist/index.d.ts +19 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -61
- package/dist/index.js.map +1 -1
- package/dist/lancedb-backend.d.ts +68 -0
- package/dist/lancedb-backend.d.ts.map +1 -0
- package/dist/lancedb-backend.js +468 -0
- package/dist/lancedb-backend.js.map +1 -0
- package/dist/learning-bridge.d.ts +19 -1
- package/dist/learning-bridge.d.ts.map +1 -1
- package/dist/learning-bridge.js +96 -52
- package/dist/learning-bridge.js.map +1 -1
- package/dist/math-utils.d.ts +6 -0
- package/dist/math-utils.d.ts.map +1 -0
- package/dist/math-utils.js +34 -0
- package/dist/math-utils.js.map +1 -0
- package/dist/memory-graph.d.ts +2 -0
- package/dist/memory-graph.d.ts.map +1 -1
- package/dist/memory-graph.js +22 -5
- package/dist/memory-graph.js.map +1 -1
- package/dist/migration.d.ts +5 -5
- package/dist/migration.d.ts.map +1 -1
- package/dist/migration.js +1 -1
- package/dist/persistent-sona.d.ts +1 -1
- package/dist/persistent-sona.d.ts.map +1 -1
- package/dist/persistent-sona.js +3 -25
- package/dist/persistent-sona.js.map +1 -1
- package/dist/rvf-backend.d.ts +2 -0
- package/dist/rvf-backend.d.ts.map +1 -1
- package/dist/rvf-backend.js +70 -62
- package/dist/rvf-backend.js.map +1 -1
- package/dist/sqlite-backend.d.ts +10 -2
- package/dist/sqlite-backend.d.ts.map +1 -1
- package/dist/sqlite-backend.js +124 -56
- package/dist/sqlite-backend.js.map +1 -1
- package/dist/sqljs-backend.d.ts +0 -1
- package/dist/sqljs-backend.d.ts.map +1 -1
- package/dist/sqljs-backend.js +3 -14
- package/dist/sqljs-backend.js.map +1 -1
- package/dist/tier-manager.d.ts.map +1 -1
- package/dist/tier-manager.js +10 -6
- package/dist/tier-manager.js.map +1 -1
- package/dist/tiers/contextual.d.ts +2 -0
- package/dist/tiers/contextual.d.ts.map +1 -1
- package/dist/tiers/contextual.js +8 -1
- package/dist/tiers/contextual.js.map +1 -1
- package/dist/tiers/short-term.js +4 -3
- package/dist/tiers/short-term.js.map +1 -1
- package/dist/types.d.ts +3 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +6 -3
- package/dist/agentdb-adapter.d.ts +0 -165
- package/dist/agentdb-adapter.d.ts.map +0 -1
- package/dist/agentdb-adapter.js +0 -836
- package/dist/agentdb-adapter.js.map +0 -1
- package/dist/agentdb-backend.d.ts +0 -218
- package/dist/agentdb-backend.d.ts.map +0 -1
- package/dist/agentdb-backend.js +0 -879
- package/dist/agentdb-backend.js.map +0 -1
package/dist/hybrid-backend.js
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* HybridBackend - Combines SQLite (structured queries) +
|
|
2
|
+
* HybridBackend - Combines SQLite (structured queries) + LanceDB (vector search)
|
|
3
3
|
*
|
|
4
|
-
* Per ADR-009: "HybridBackend (SQLite +
|
|
4
|
+
* Per ADR-009: "HybridBackend (SQLite + LanceDB) as default"
|
|
5
5
|
* - SQLite for: Structured queries, ACID transactions, exact matches
|
|
6
|
-
* -
|
|
6
|
+
* - LanceDB for: Semantic search, vector similarity, RAG
|
|
7
7
|
*
|
|
8
8
|
* @module v1/memory/hybrid-backend
|
|
9
9
|
*/
|
|
10
10
|
import { EventEmitter } from 'node:events';
|
|
11
11
|
import { SQLiteBackend } from './sqlite-backend.js';
|
|
12
|
-
import {
|
|
12
|
+
import { LanceDBBackend } from './lancedb-backend.js';
|
|
13
13
|
import { MemoryGraph } from './memory-graph.js';
|
|
14
14
|
/**
|
|
15
15
|
* Default configuration
|
|
16
16
|
*/
|
|
17
17
|
const DEFAULT_CONFIG = {
|
|
18
18
|
sqlite: {},
|
|
19
|
-
|
|
19
|
+
lancedb: {},
|
|
20
|
+
semanticBackend: 'lancedb',
|
|
20
21
|
defaultNamespace: 'default',
|
|
21
22
|
embeddingGenerator: undefined,
|
|
22
23
|
routingStrategy: 'auto',
|
|
@@ -44,14 +45,14 @@ const INJECTION_PATTERNS = [
|
|
|
44
45
|
/**
|
|
45
46
|
* HybridBackend Implementation
|
|
46
47
|
*
|
|
47
|
-
* Intelligently routes queries between SQLite and
|
|
48
|
+
* Intelligently routes queries between SQLite and LanceDB:
|
|
48
49
|
* - Exact matches, prefix queries → SQLite
|
|
49
|
-
* - Semantic search, similarity →
|
|
50
|
+
* - Semantic search, similarity → LanceDB
|
|
50
51
|
* - Complex hybrid queries → Both backends with intelligent merging
|
|
51
52
|
*/
|
|
52
53
|
export class HybridBackend extends EventEmitter {
|
|
53
|
-
sqlite;
|
|
54
|
-
|
|
54
|
+
sqlite; // SQLiteBackend normally; same as semantic when lancedb solo
|
|
55
|
+
semantic; // LanceDBBackend
|
|
55
56
|
config;
|
|
56
57
|
initialized = false;
|
|
57
58
|
/** Lazy MemoryGraph for HippoRAG PPR re-ranking */
|
|
@@ -59,34 +60,52 @@ export class HybridBackend extends EventEmitter {
|
|
|
59
60
|
// Performance tracking
|
|
60
61
|
stats = {
|
|
61
62
|
sqliteQueries: 0,
|
|
62
|
-
|
|
63
|
+
semanticQueries: 0,
|
|
63
64
|
hybridQueries: 0,
|
|
64
65
|
totalQueryTime: 0,
|
|
65
66
|
};
|
|
66
67
|
constructor(config = {}) {
|
|
67
68
|
super();
|
|
68
69
|
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
69
|
-
// Initialize
|
|
70
|
-
this.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
70
|
+
// Initialize semantic backend (LanceDB)
|
|
71
|
+
if (this.config.semanticBackend === 'lancedb') {
|
|
72
|
+
// Solo LanceDB mode: one backend handles all queries (structured + vector).
|
|
73
|
+
// Both this.sqlite and this.semantic point to the same instance so all
|
|
74
|
+
// routing paths (exact/prefix/tag/semantic) transparently use LanceDB.
|
|
75
|
+
// dualWrite forced off to prevent double-writes to the same instance.
|
|
76
|
+
const ldb = new LanceDBBackend({
|
|
77
|
+
...this.config.lancedb,
|
|
78
|
+
namespace: this.config.defaultNamespace,
|
|
79
|
+
embeddingGenerator: this.config.embeddingGenerator,
|
|
80
|
+
});
|
|
81
|
+
this.semantic = ldb;
|
|
82
|
+
this.sqlite = ldb;
|
|
83
|
+
this.config.dualWrite = false;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
this.sqlite = new SQLiteBackend({
|
|
87
|
+
...this.config.sqlite,
|
|
88
|
+
defaultNamespace: this.config.defaultNamespace,
|
|
89
|
+
embeddingGenerator: this.config.embeddingGenerator,
|
|
90
|
+
});
|
|
91
|
+
this.semantic = new LanceDBBackend({
|
|
92
|
+
...this.config.lancedb,
|
|
93
|
+
namespace: this.config.defaultNamespace,
|
|
94
|
+
embeddingGenerator: this.config.embeddingGenerator,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
// Forward events (skip duplicate wiring in solo mode)
|
|
98
|
+
const em = (b) => b;
|
|
99
|
+
em(this.sqlite).on?.('entry:stored', (d) => this.emit('sqlite:stored', d));
|
|
100
|
+
em(this.sqlite).on?.('entry:updated', (d) => this.emit('sqlite:updated', d));
|
|
101
|
+
em(this.sqlite).on?.('entry:deleted', (d) => this.emit('sqlite:deleted', d));
|
|
102
|
+
if (this.semantic !== this.sqlite) {
|
|
103
|
+
em(this.semantic).on?.('entry:stored', (d) => this.emit('semantic:stored', d));
|
|
104
|
+
em(this.semantic).on?.('entry:updated', (d) => this.emit('semantic:updated', d));
|
|
105
|
+
em(this.semantic).on?.('entry:deleted', (d) => this.emit('semantic:deleted', d));
|
|
106
|
+
em(this.semantic).on?.('cache:hit', (d) => this.emit('cache:hit', d));
|
|
107
|
+
em(this.semantic).on?.('cache:miss', (d) => this.emit('cache:miss', d));
|
|
108
|
+
}
|
|
90
109
|
}
|
|
91
110
|
/**
|
|
92
111
|
* Initialize both backends
|
|
@@ -94,7 +113,10 @@ export class HybridBackend extends EventEmitter {
|
|
|
94
113
|
async initialize() {
|
|
95
114
|
if (this.initialized)
|
|
96
115
|
return;
|
|
97
|
-
|
|
116
|
+
const inits = this.semantic === this.sqlite
|
|
117
|
+
? [this.semantic.initialize()]
|
|
118
|
+
: [this.sqlite.initialize(), this.semantic.initialize()];
|
|
119
|
+
await Promise.all(inits);
|
|
98
120
|
this.initialized = true;
|
|
99
121
|
this.emit('initialized');
|
|
100
122
|
}
|
|
@@ -104,7 +126,10 @@ export class HybridBackend extends EventEmitter {
|
|
|
104
126
|
async shutdown() {
|
|
105
127
|
if (!this.initialized)
|
|
106
128
|
return;
|
|
107
|
-
|
|
129
|
+
const shutdowns = this.semantic === this.sqlite
|
|
130
|
+
? [this.semantic.shutdown()]
|
|
131
|
+
: [this.sqlite.shutdown(), this.semantic.shutdown()];
|
|
132
|
+
await Promise.all(shutdowns);
|
|
108
133
|
this.initialized = false;
|
|
109
134
|
this.emit('shutdown');
|
|
110
135
|
}
|
|
@@ -119,11 +144,11 @@ export class HybridBackend extends EventEmitter {
|
|
|
119
144
|
async store(entry) {
|
|
120
145
|
if (this.config.dualWrite) {
|
|
121
146
|
// Write to both backends in parallel
|
|
122
|
-
await Promise.all([this.sqlite.store(entry), this.
|
|
147
|
+
await Promise.all([this.sqlite.store(entry), this.semantic.store(entry)]);
|
|
123
148
|
}
|
|
124
149
|
else {
|
|
125
|
-
// Write to primary backend only
|
|
126
|
-
await this.
|
|
150
|
+
// Write to primary backend only
|
|
151
|
+
await this.semantic.store(entry);
|
|
127
152
|
}
|
|
128
153
|
this.emit('entry:stored', { id: entry.id });
|
|
129
154
|
// A-MEM: auto-link to top-3 semantic neighbors (non-blocking, best-effort)
|
|
@@ -141,7 +166,7 @@ export class HybridBackend extends EventEmitter {
|
|
|
141
166
|
async aMemAutoLink(newEntry) {
|
|
142
167
|
try {
|
|
143
168
|
const embedding = await this.config.embeddingGenerator(newEntry.content);
|
|
144
|
-
const neighbors = await this.
|
|
169
|
+
const neighbors = await this.semantic.search(embedding, {
|
|
145
170
|
k: 4, // fetch 4 to exclude self
|
|
146
171
|
threshold: 0.75,
|
|
147
172
|
});
|
|
@@ -166,7 +191,7 @@ export class HybridBackend extends EventEmitter {
|
|
|
166
191
|
}
|
|
167
192
|
}
|
|
168
193
|
/**
|
|
169
|
-
* Get from
|
|
194
|
+
* Get from semantic backend.
|
|
170
195
|
*
|
|
171
196
|
* Collaborative memory promotion: when `agentId` is supplied, also notifies
|
|
172
197
|
* the SQLite backend so it can track the read and promote the entry's
|
|
@@ -174,9 +199,10 @@ export class HybridBackend extends EventEmitter {
|
|
|
174
199
|
* Source: newinnovation.md §2.7 — memory access-level promotion.
|
|
175
200
|
*/
|
|
176
201
|
async get(id, agentId) {
|
|
177
|
-
const entry = await this.
|
|
178
|
-
// Track the read in SQLite for collaborative promotion (best-effort, non-blocking)
|
|
179
|
-
|
|
202
|
+
const entry = await this.semantic.get(id);
|
|
203
|
+
// Track the read in SQLite for collaborative promotion (best-effort, non-blocking).
|
|
204
|
+
// Skipped in solo-lancedb mode since this.sqlite === this.semantic (already fetched above).
|
|
205
|
+
if (agentId && entry && this.semantic !== this.sqlite) {
|
|
180
206
|
this.sqlite.get(id, agentId).catch(() => { });
|
|
181
207
|
}
|
|
182
208
|
return entry;
|
|
@@ -193,14 +219,14 @@ export class HybridBackend extends EventEmitter {
|
|
|
193
219
|
async update(id, update) {
|
|
194
220
|
if (this.config.dualWrite) {
|
|
195
221
|
// Update both backends
|
|
196
|
-
const [sqliteResult,
|
|
222
|
+
const [sqliteResult, semanticResult] = await Promise.all([
|
|
197
223
|
this.sqlite.update(id, update),
|
|
198
|
-
this.
|
|
224
|
+
this.semantic.update(id, update),
|
|
199
225
|
]);
|
|
200
|
-
return
|
|
226
|
+
return semanticResult || sqliteResult;
|
|
201
227
|
}
|
|
202
228
|
else {
|
|
203
|
-
return this.
|
|
229
|
+
return this.semantic.update(id, update);
|
|
204
230
|
}
|
|
205
231
|
}
|
|
206
232
|
/**
|
|
@@ -208,18 +234,18 @@ export class HybridBackend extends EventEmitter {
|
|
|
208
234
|
*/
|
|
209
235
|
async delete(id) {
|
|
210
236
|
if (this.config.dualWrite) {
|
|
211
|
-
const [sqliteResult,
|
|
237
|
+
const [sqliteResult, semanticResult] = await Promise.all([
|
|
212
238
|
this.sqlite.delete(id),
|
|
213
|
-
this.
|
|
239
|
+
this.semantic.delete(id),
|
|
214
240
|
]);
|
|
215
|
-
return sqliteResult ||
|
|
241
|
+
return sqliteResult || semanticResult;
|
|
216
242
|
}
|
|
217
243
|
else {
|
|
218
|
-
return this.
|
|
244
|
+
return this.semantic.delete(id);
|
|
219
245
|
}
|
|
220
246
|
}
|
|
221
247
|
/**
|
|
222
|
-
* Query routing
|
|
248
|
+
* Query routing — semantic goes to LanceDB, structured to SQLite
|
|
223
249
|
*/
|
|
224
250
|
async query(query) {
|
|
225
251
|
const startTime = performance.now();
|
|
@@ -242,9 +268,9 @@ export class HybridBackend extends EventEmitter {
|
|
|
242
268
|
results = await this.sqlite.query(query);
|
|
243
269
|
break;
|
|
244
270
|
case 'semantic':
|
|
245
|
-
//
|
|
246
|
-
this.stats.
|
|
247
|
-
results = await this.
|
|
271
|
+
// LanceDB handles semantic search
|
|
272
|
+
this.stats.semanticQueries++;
|
|
273
|
+
results = await this.semantic.query(query);
|
|
248
274
|
break;
|
|
249
275
|
case 'hybrid':
|
|
250
276
|
// Use hybrid query combining both backends
|
|
@@ -335,7 +361,7 @@ export class HybridBackend extends EventEmitter {
|
|
|
335
361
|
}
|
|
336
362
|
/**
|
|
337
363
|
* Semantic queries (vector)
|
|
338
|
-
* Routes to
|
|
364
|
+
* Routes to semantic backend for ANN vector search.
|
|
339
365
|
*
|
|
340
366
|
* MemoRAG query rewriting (arXiv:2409.05591): when `memoragRewriter` is configured
|
|
341
367
|
* and `query.content` is present, generates 2-3 reformulated sub-queries, embeds
|
|
@@ -343,7 +369,7 @@ export class HybridBackend extends EventEmitter {
|
|
|
343
369
|
* before HippoRAG PPR re-ranking.
|
|
344
370
|
*/
|
|
345
371
|
async querySemantic(query) {
|
|
346
|
-
this.stats.
|
|
372
|
+
this.stats.semanticQueries++;
|
|
347
373
|
let searchResults = [];
|
|
348
374
|
// MemoRAG: expand the query into multiple sub-queries for richer recall
|
|
349
375
|
let memoragUsed = false;
|
|
@@ -356,7 +382,7 @@ export class HybridBackend extends EventEmitter {
|
|
|
356
382
|
if (!this.config.embeddingGenerator)
|
|
357
383
|
return [];
|
|
358
384
|
const sqEmbedding = await this.config.embeddingGenerator(sq);
|
|
359
|
-
const results = await this.
|
|
385
|
+
const results = await this.semantic.search(sqEmbedding, {
|
|
360
386
|
k: (query.k || 10) * 2,
|
|
361
387
|
threshold: query.threshold || this.config.semanticThreshold,
|
|
362
388
|
filters: query.filters,
|
|
@@ -383,7 +409,7 @@ export class HybridBackend extends EventEmitter {
|
|
|
383
409
|
if (!embedding) {
|
|
384
410
|
throw new Error('SemanticQuery requires either content or embedding');
|
|
385
411
|
}
|
|
386
|
-
searchResults = await this.
|
|
412
|
+
searchResults = await this.semantic.search(embedding, {
|
|
387
413
|
k: (query.k || 10) * 2, // Over-fetch to account for post-filtering
|
|
388
414
|
threshold: query.threshold || this.config.semanticThreshold,
|
|
389
415
|
filters: query.filters,
|
|
@@ -427,7 +453,7 @@ export class HybridBackend extends EventEmitter {
|
|
|
427
453
|
});
|
|
428
454
|
}
|
|
429
455
|
}
|
|
430
|
-
const reranked = await this.memoryGraph.pprRerank(searchResults, this.
|
|
456
|
+
const reranked = await this.memoryGraph.pprRerank(searchResults, this.semantic);
|
|
431
457
|
// Annotate each entry with its community ID and summary from GraphRAG
|
|
432
458
|
entries = reranked.map((r) => {
|
|
433
459
|
const commSummary = nodeToSummary.get(r.entry.id);
|
|
@@ -464,7 +490,7 @@ export class HybridBackend extends EventEmitter {
|
|
|
464
490
|
}
|
|
465
491
|
entries = safe;
|
|
466
492
|
}
|
|
467
|
-
// Apply tag/namespace/type filters
|
|
493
|
+
// Apply tag/namespace/type filters
|
|
468
494
|
if (query.filters) {
|
|
469
495
|
const f = query.filters;
|
|
470
496
|
if (f.tags && Array.isArray(f.tags)) {
|
|
@@ -508,21 +534,21 @@ export class HybridBackend extends EventEmitter {
|
|
|
508
534
|
}
|
|
509
535
|
}
|
|
510
536
|
/**
|
|
511
|
-
* Semantic vector search (routes to
|
|
537
|
+
* Semantic vector search (routes to LanceDB)
|
|
512
538
|
*/
|
|
513
539
|
async search(embedding, options) {
|
|
514
|
-
this.stats.
|
|
515
|
-
return this.
|
|
540
|
+
this.stats.semanticQueries++;
|
|
541
|
+
return this.semantic.search(embedding, options);
|
|
516
542
|
}
|
|
517
543
|
/**
|
|
518
544
|
* Bulk insert to both backends
|
|
519
545
|
*/
|
|
520
546
|
async bulkInsert(entries) {
|
|
521
547
|
if (this.config.dualWrite) {
|
|
522
|
-
await Promise.all([this.sqlite.bulkInsert(entries), this.
|
|
548
|
+
await Promise.all([this.sqlite.bulkInsert(entries), this.semantic.bulkInsert(entries)]);
|
|
523
549
|
}
|
|
524
550
|
else {
|
|
525
|
-
await this.
|
|
551
|
+
await this.semantic.bulkInsert(entries);
|
|
526
552
|
}
|
|
527
553
|
}
|
|
528
554
|
/**
|
|
@@ -530,14 +556,14 @@ export class HybridBackend extends EventEmitter {
|
|
|
530
556
|
*/
|
|
531
557
|
async bulkDelete(ids) {
|
|
532
558
|
if (this.config.dualWrite) {
|
|
533
|
-
const [sqliteCount,
|
|
559
|
+
const [sqliteCount, semanticCount] = await Promise.all([
|
|
534
560
|
this.sqlite.bulkDelete(ids),
|
|
535
|
-
this.
|
|
561
|
+
this.semantic.bulkDelete(ids),
|
|
536
562
|
]);
|
|
537
|
-
return Math.max(sqliteCount,
|
|
563
|
+
return Math.max(sqliteCount, semanticCount);
|
|
538
564
|
}
|
|
539
565
|
else {
|
|
540
|
-
return this.
|
|
566
|
+
return this.semantic.bulkDelete(ids);
|
|
541
567
|
}
|
|
542
568
|
}
|
|
543
569
|
/**
|
|
@@ -557,77 +583,79 @@ export class HybridBackend extends EventEmitter {
|
|
|
557
583
|
*/
|
|
558
584
|
async clearNamespace(namespace) {
|
|
559
585
|
if (this.config.dualWrite) {
|
|
560
|
-
const [sqliteCount,
|
|
586
|
+
const [sqliteCount, semanticCount] = await Promise.all([
|
|
561
587
|
this.sqlite.clearNamespace(namespace),
|
|
562
|
-
this.
|
|
588
|
+
this.semantic.clearNamespace(namespace),
|
|
563
589
|
]);
|
|
564
|
-
return Math.max(sqliteCount,
|
|
590
|
+
return Math.max(sqliteCount, semanticCount);
|
|
565
591
|
}
|
|
566
592
|
else {
|
|
567
|
-
return this.
|
|
593
|
+
return this.semantic.clearNamespace(namespace);
|
|
568
594
|
}
|
|
569
595
|
}
|
|
570
596
|
/**
|
|
571
597
|
* Get combined statistics from both backends
|
|
572
598
|
*/
|
|
573
599
|
async getStats() {
|
|
574
|
-
const
|
|
575
|
-
|
|
576
|
-
this.
|
|
600
|
+
const solo = this.semantic === this.sqlite;
|
|
601
|
+
const [sqliteStats, semanticStats] = await Promise.all([
|
|
602
|
+
solo ? Promise.resolve(null) : this.sqlite.getStats(),
|
|
603
|
+
this.semantic.getStats(),
|
|
577
604
|
]);
|
|
578
605
|
return {
|
|
579
|
-
totalEntries: Math.max(sqliteStats.totalEntries,
|
|
580
|
-
entriesByNamespace:
|
|
581
|
-
entriesByType:
|
|
582
|
-
memoryUsage: sqliteStats.memoryUsage +
|
|
583
|
-
hnswStats:
|
|
606
|
+
totalEntries: solo ? semanticStats.totalEntries : Math.max(sqliteStats.totalEntries, semanticStats.totalEntries),
|
|
607
|
+
entriesByNamespace: semanticStats.entriesByNamespace,
|
|
608
|
+
entriesByType: semanticStats.entriesByType,
|
|
609
|
+
memoryUsage: solo ? semanticStats.memoryUsage : sqliteStats.memoryUsage + semanticStats.memoryUsage,
|
|
610
|
+
hnswStats: semanticStats.hnswStats ?? {
|
|
584
611
|
vectorCount: 0, // 0 = no active HNSW index; do not fabricate from totalEntries
|
|
585
612
|
memoryUsage: 0,
|
|
586
613
|
avgSearchTime: 0,
|
|
587
614
|
buildTime: 0,
|
|
588
615
|
compressionRatio: 1.0,
|
|
589
616
|
},
|
|
590
|
-
cacheStats:
|
|
617
|
+
cacheStats: semanticStats.cacheStats ?? {
|
|
591
618
|
hitRate: 0,
|
|
592
619
|
size: 0,
|
|
593
620
|
maxSize: 1000,
|
|
594
621
|
},
|
|
595
|
-
avgQueryTime: this.stats.hybridQueries + this.stats.sqliteQueries + this.stats.
|
|
622
|
+
avgQueryTime: this.stats.hybridQueries + this.stats.sqliteQueries + this.stats.semanticQueries > 0
|
|
596
623
|
? this.stats.totalQueryTime /
|
|
597
|
-
(this.stats.hybridQueries + this.stats.sqliteQueries + this.stats.
|
|
624
|
+
(this.stats.hybridQueries + this.stats.sqliteQueries + this.stats.semanticQueries)
|
|
598
625
|
: 0,
|
|
599
|
-
avgSearchTime:
|
|
626
|
+
avgSearchTime: semanticStats.avgSearchTime,
|
|
600
627
|
};
|
|
601
628
|
}
|
|
602
629
|
/**
|
|
603
630
|
* Health check for both backends
|
|
604
631
|
*/
|
|
605
632
|
async healthCheck() {
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
this.
|
|
633
|
+
const solo = this.semantic === this.sqlite;
|
|
634
|
+
const [sqliteHealth, semanticHealth] = await Promise.all([
|
|
635
|
+
solo ? Promise.resolve({ status: 'healthy', issues: [], recommendations: [], components: {}, timestamp: Date.now() }) : this.sqlite.healthCheck(),
|
|
636
|
+
this.semantic.healthCheck(),
|
|
609
637
|
]);
|
|
610
|
-
const allIssues = [...sqliteHealth.issues, ...
|
|
638
|
+
const allIssues = [...sqliteHealth.issues, ...semanticHealth.issues];
|
|
611
639
|
const allRecommendations = [
|
|
612
640
|
...sqliteHealth.recommendations,
|
|
613
|
-
...
|
|
641
|
+
...semanticHealth.recommendations,
|
|
614
642
|
];
|
|
615
643
|
// Determine overall status
|
|
616
644
|
let status = 'healthy';
|
|
617
645
|
if (sqliteHealth.status === 'unhealthy' ||
|
|
618
|
-
|
|
646
|
+
semanticHealth.status === 'unhealthy') {
|
|
619
647
|
status = 'unhealthy';
|
|
620
648
|
}
|
|
621
649
|
else if (sqliteHealth.status === 'degraded' ||
|
|
622
|
-
|
|
650
|
+
semanticHealth.status === 'degraded') {
|
|
623
651
|
status = 'degraded';
|
|
624
652
|
}
|
|
625
653
|
return {
|
|
626
654
|
status,
|
|
627
655
|
components: {
|
|
628
656
|
storage: sqliteHealth.components.storage,
|
|
629
|
-
index:
|
|
630
|
-
cache:
|
|
657
|
+
index: semanticHealth.components.index,
|
|
658
|
+
cache: semanticHealth.components.cache,
|
|
631
659
|
},
|
|
632
660
|
timestamp: Date.now(),
|
|
633
661
|
issues: allIssues,
|
|
@@ -639,11 +667,11 @@ export class HybridBackend extends EventEmitter {
|
|
|
639
667
|
* Auto-route queries based on properties
|
|
640
668
|
*/
|
|
641
669
|
async autoRoute(query) {
|
|
642
|
-
// If has embedding or content, use semantic search (
|
|
670
|
+
// If has embedding or content, use semantic search (LanceDB)
|
|
643
671
|
const hasEmbeddingGenerator = typeof this.config.embeddingGenerator === 'function';
|
|
644
672
|
if (query.embedding || (query.content && hasEmbeddingGenerator)) {
|
|
645
|
-
this.stats.
|
|
646
|
-
return this.
|
|
673
|
+
this.stats.semanticQueries++;
|
|
674
|
+
return this.semantic.query(query);
|
|
647
675
|
}
|
|
648
676
|
// If has exact key or prefix, use structured search (SQLite)
|
|
649
677
|
if (query.key || query.keyPrefix) {
|
|
@@ -655,14 +683,14 @@ export class HybridBackend extends EventEmitter {
|
|
|
655
683
|
case 'sqlite-first':
|
|
656
684
|
this.stats.sqliteQueries++;
|
|
657
685
|
return this.sqlite.query(query);
|
|
658
|
-
case '
|
|
659
|
-
this.stats.
|
|
660
|
-
return this.
|
|
686
|
+
case 'semantic-first':
|
|
687
|
+
this.stats.semanticQueries++;
|
|
688
|
+
return this.semantic.query(query);
|
|
661
689
|
case 'auto':
|
|
662
690
|
default:
|
|
663
|
-
// Default to
|
|
664
|
-
this.stats.
|
|
665
|
-
return this.
|
|
691
|
+
// Default to semantic backend (LanceDB)
|
|
692
|
+
this.stats.semanticQueries++;
|
|
693
|
+
return this.semantic.query(query);
|
|
666
694
|
}
|
|
667
695
|
}
|
|
668
696
|
/**
|
|
@@ -706,7 +734,13 @@ export class HybridBackend extends EventEmitter {
|
|
|
706
734
|
combineUnion(semanticResults, structuredResults) {
|
|
707
735
|
const seen = new Set();
|
|
708
736
|
const combined = [];
|
|
709
|
-
for (const entry of
|
|
737
|
+
for (const entry of semanticResults) {
|
|
738
|
+
if (!seen.has(entry.id)) {
|
|
739
|
+
seen.add(entry.id);
|
|
740
|
+
combined.push(entry);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
for (const entry of structuredResults) {
|
|
710
744
|
if (!seen.has(entry.id)) {
|
|
711
745
|
seen.add(entry.id);
|
|
712
746
|
combined.push(entry);
|
|
@@ -725,7 +759,9 @@ export class HybridBackend extends EventEmitter {
|
|
|
725
759
|
* Semantic-first: Prefer semantic results, add structured if not present
|
|
726
760
|
*/
|
|
727
761
|
combineSemanticFirst(semanticResults, structuredResults) {
|
|
728
|
-
const semanticIds = new Set(
|
|
762
|
+
const semanticIds = new Set();
|
|
763
|
+
for (const e of semanticResults)
|
|
764
|
+
semanticIds.add(e.id);
|
|
729
765
|
const additional = structuredResults.filter((e) => !semanticIds.has(e.id));
|
|
730
766
|
return [...semanticResults, ...additional];
|
|
731
767
|
}
|
|
@@ -733,60 +769,25 @@ export class HybridBackend extends EventEmitter {
|
|
|
733
769
|
* Structured-first: Prefer structured results, add semantic if not present
|
|
734
770
|
*/
|
|
735
771
|
combineStructuredFirst(semanticResults, structuredResults) {
|
|
736
|
-
const structuredIds = new Set(
|
|
772
|
+
const structuredIds = new Set();
|
|
773
|
+
for (const e of structuredResults)
|
|
774
|
+
structuredIds.add(e.id);
|
|
737
775
|
const additional = semanticResults.filter((e) => !structuredIds.has(e.id));
|
|
738
776
|
return [...structuredResults, ...additional];
|
|
739
777
|
}
|
|
740
|
-
// ===== Proxy Methods
|
|
778
|
+
// ===== Proxy Methods — stubs for legacy controller API =====
|
|
741
779
|
/**
|
|
742
780
|
* Record feedback for a memory entry.
|
|
743
|
-
* Delegates to
|
|
744
|
-
* Gracefully degrades to
|
|
781
|
+
* Delegates to semantic backend's recordFeedback when available.
|
|
782
|
+
* Gracefully degrades to no-op when unavailable.
|
|
745
783
|
*/
|
|
746
|
-
async recordFeedback(
|
|
747
|
-
|
|
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;
|
|
784
|
+
async recordFeedback(_entryId, _feedback) {
|
|
785
|
+
return false; // monolean: stub — LanceDB has no feedback API
|
|
759
786
|
}
|
|
760
|
-
|
|
761
|
-
|
|
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'] };
|
|
787
|
+
async verifyWitnessChain(_entryId) {
|
|
788
|
+
return { valid: false, chainLength: 0, errors: ['not supported by LanceDB backend'] };
|
|
775
789
|
}
|
|
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
|
+
async getWitnessChain(_entryId) {
|
|
790
791
|
return [];
|
|
791
792
|
}
|
|
792
793
|
// ===== Backend Access =====
|
|
@@ -796,8 +797,8 @@ export class HybridBackend extends EventEmitter {
|
|
|
796
797
|
getSQLiteBackend() {
|
|
797
798
|
return this.sqlite;
|
|
798
799
|
}
|
|
799
|
-
|
|
800
|
-
return this.
|
|
800
|
+
getSemanticBackend() {
|
|
801
|
+
return this.semantic;
|
|
801
802
|
}
|
|
802
803
|
}
|
|
803
804
|
export default HybridBackend;
|