@monoes/memory 1.0.0 → 1.0.2

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 (98) hide show
  1. package/README.md +20 -24
  2. package/dist/agent-memory-scope.d.ts +1 -1
  3. package/dist/agent-memory-scope.js +2 -2
  4. package/dist/agent-memory-scope.js.map +1 -1
  5. package/dist/auto-memory-bridge.d.ts +12 -12
  6. package/dist/auto-memory-bridge.d.ts.map +1 -1
  7. package/dist/auto-memory-bridge.js +24 -18
  8. package/dist/auto-memory-bridge.js.map +1 -1
  9. package/dist/cache-manager.d.ts.map +1 -1
  10. package/dist/cache-manager.js +7 -3
  11. package/dist/cache-manager.js.map +1 -1
  12. package/dist/checkpointer.d.ts.map +1 -1
  13. package/dist/checkpointer.js +14 -4
  14. package/dist/checkpointer.js.map +1 -1
  15. package/dist/controller-registry.d.ts +14 -20
  16. package/dist/controller-registry.d.ts.map +1 -1
  17. package/dist/controller-registry.js +52 -423
  18. package/dist/controller-registry.js.map +1 -1
  19. package/dist/database-provider.d.ts.map +1 -1
  20. package/dist/database-provider.js +3 -15
  21. package/dist/database-provider.js.map +1 -1
  22. package/dist/diskann-backend.d.ts.map +1 -1
  23. package/dist/diskann-backend.js +16 -4
  24. package/dist/diskann-backend.js.map +1 -1
  25. package/dist/episodic-store.js +5 -5
  26. package/dist/episodic-store.js.map +1 -1
  27. package/dist/hnsw-index.d.ts +0 -1
  28. package/dist/hnsw-index.d.ts.map +1 -1
  29. package/dist/hnsw-index.js +2 -48
  30. package/dist/hnsw-index.js.map +1 -1
  31. package/dist/hnsw-lite.d.ts.map +1 -1
  32. package/dist/hnsw-lite.js +5 -1
  33. package/dist/hnsw-lite.js.map +1 -1
  34. package/dist/hybrid-backend.d.ts +29 -31
  35. package/dist/hybrid-backend.d.ts.map +1 -1
  36. package/dist/hybrid-backend.js +153 -152
  37. package/dist/hybrid-backend.js.map +1 -1
  38. package/dist/index.d.ts +19 -31
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +24 -61
  41. package/dist/index.js.map +1 -1
  42. package/dist/lancedb-backend.d.ts +68 -0
  43. package/dist/lancedb-backend.d.ts.map +1 -0
  44. package/dist/lancedb-backend.js +468 -0
  45. package/dist/lancedb-backend.js.map +1 -0
  46. package/dist/learning-bridge.d.ts +19 -1
  47. package/dist/learning-bridge.d.ts.map +1 -1
  48. package/dist/learning-bridge.js +96 -52
  49. package/dist/learning-bridge.js.map +1 -1
  50. package/dist/math-utils.d.ts +6 -0
  51. package/dist/math-utils.d.ts.map +1 -0
  52. package/dist/math-utils.js +34 -0
  53. package/dist/math-utils.js.map +1 -0
  54. package/dist/memory-graph.d.ts +2 -0
  55. package/dist/memory-graph.d.ts.map +1 -1
  56. package/dist/memory-graph.js +22 -5
  57. package/dist/memory-graph.js.map +1 -1
  58. package/dist/migration.d.ts +5 -5
  59. package/dist/migration.d.ts.map +1 -1
  60. package/dist/migration.js +1 -1
  61. package/dist/persistent-sona.d.ts +1 -1
  62. package/dist/persistent-sona.d.ts.map +1 -1
  63. package/dist/persistent-sona.js +3 -25
  64. package/dist/persistent-sona.js.map +1 -1
  65. package/dist/rvf-backend.d.ts +2 -0
  66. package/dist/rvf-backend.d.ts.map +1 -1
  67. package/dist/rvf-backend.js +70 -62
  68. package/dist/rvf-backend.js.map +1 -1
  69. package/dist/sqlite-backend.d.ts +10 -2
  70. package/dist/sqlite-backend.d.ts.map +1 -1
  71. package/dist/sqlite-backend.js +124 -56
  72. package/dist/sqlite-backend.js.map +1 -1
  73. package/dist/sqljs-backend.d.ts +0 -1
  74. package/dist/sqljs-backend.d.ts.map +1 -1
  75. package/dist/sqljs-backend.js +3 -14
  76. package/dist/sqljs-backend.js.map +1 -1
  77. package/dist/tier-manager.d.ts.map +1 -1
  78. package/dist/tier-manager.js +10 -6
  79. package/dist/tier-manager.js.map +1 -1
  80. package/dist/tiers/contextual.d.ts +2 -0
  81. package/dist/tiers/contextual.d.ts.map +1 -1
  82. package/dist/tiers/contextual.js +8 -1
  83. package/dist/tiers/contextual.js.map +1 -1
  84. package/dist/tiers/short-term.js +4 -3
  85. package/dist/tiers/short-term.js.map +1 -1
  86. package/dist/types.d.ts +3 -1
  87. package/dist/types.d.ts.map +1 -1
  88. package/dist/types.js +1 -1
  89. package/dist/types.js.map +1 -1
  90. package/package.json +7 -4
  91. package/dist/agentdb-adapter.d.ts +0 -165
  92. package/dist/agentdb-adapter.d.ts.map +0 -1
  93. package/dist/agentdb-adapter.js +0 -836
  94. package/dist/agentdb-adapter.js.map +0 -1
  95. package/dist/agentdb-backend.d.ts +0 -218
  96. package/dist/agentdb-backend.d.ts.map +0 -1
  97. package/dist/agentdb-backend.js +0 -879
  98. package/dist/agentdb-backend.js.map +0 -1
@@ -1,22 +1,23 @@
1
1
  /**
2
- * HybridBackend - Combines SQLite (structured queries) + AgentDB (vector search)
2
+ * HybridBackend - Combines SQLite (structured queries) + LanceDB (vector search)
3
3
  *
4
- * Per ADR-009: "HybridBackend (SQLite + AgentDB) as default"
4
+ * Per ADR-009: "HybridBackend (SQLite + LanceDB) as default"
5
5
  * - SQLite for: Structured queries, ACID transactions, exact matches
6
- * - AgentDB for: Semantic search, vector similarity, RAG
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 { AgentDBBackend } from './agentdb-backend.js';
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
- agentdb: {},
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 AgentDB:
48
+ * Intelligently routes queries between SQLite and LanceDB:
48
49
  * - Exact matches, prefix queries → SQLite
49
- * - Semantic search, similarity → AgentDB
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
- agentdb;
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
- agentdbQueries: 0,
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 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));
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
- await Promise.all([this.sqlite.initialize(), this.agentdb.initialize()]);
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
- await Promise.all([this.sqlite.shutdown(), this.agentdb.shutdown()]);
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.agentdb.store(entry)]);
147
+ await Promise.all([this.sqlite.store(entry), this.semantic.store(entry)]);
123
148
  }
124
149
  else {
125
- // Write to primary backend only (AgentDB has vector search)
126
- await this.agentdb.store(entry);
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.agentdb.search(embedding, {
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 AgentDB (has caching enabled).
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.agentdb.get(id);
178
- // Track the read in SQLite for collaborative promotion (best-effort, non-blocking)
179
- if (agentId && entry) {
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, agentdbResult] = await Promise.all([
222
+ const [sqliteResult, semanticResult] = await Promise.all([
197
223
  this.sqlite.update(id, update),
198
- this.agentdb.update(id, update),
224
+ this.semantic.update(id, update),
199
225
  ]);
200
- return agentdbResult || sqliteResult;
226
+ return semanticResult || sqliteResult;
201
227
  }
202
228
  else {
203
- return this.agentdb.update(id, update);
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, agentdbResult] = await Promise.all([
237
+ const [sqliteResult, semanticResult] = await Promise.all([
212
238
  this.sqlite.delete(id),
213
- this.agentdb.delete(id),
239
+ this.semantic.delete(id),
214
240
  ]);
215
- return sqliteResult || agentdbResult;
241
+ return sqliteResult || semanticResult;
216
242
  }
217
243
  else {
218
- return this.agentdb.delete(id);
244
+ return this.semantic.delete(id);
219
245
  }
220
246
  }
221
247
  /**
222
- * Query routing - semantic goes to AgentDB, structured to SQLite
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
- // AgentDB optimized for semantic search
246
- this.stats.agentdbQueries++;
247
- results = await this.agentdb.query(query);
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 AgentDB for HNSW-based vector search.
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.agentdbQueries++;
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.agentdb.search(sqEmbedding, {
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.agentdb.search(embedding, {
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.agentdb);
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 that AgentDB may not enforce
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 AgentDB)
537
+ * Semantic vector search (routes to LanceDB)
512
538
  */
513
539
  async search(embedding, options) {
514
- this.stats.agentdbQueries++;
515
- return this.agentdb.search(embedding, options);
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.agentdb.bulkInsert(entries)]);
548
+ await Promise.all([this.sqlite.bulkInsert(entries), this.semantic.bulkInsert(entries)]);
523
549
  }
524
550
  else {
525
- await this.agentdb.bulkInsert(entries);
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, agentdbCount] = await Promise.all([
559
+ const [sqliteCount, semanticCount] = await Promise.all([
534
560
  this.sqlite.bulkDelete(ids),
535
- this.agentdb.bulkDelete(ids),
561
+ this.semantic.bulkDelete(ids),
536
562
  ]);
537
- return Math.max(sqliteCount, agentdbCount);
563
+ return Math.max(sqliteCount, semanticCount);
538
564
  }
539
565
  else {
540
- return this.agentdb.bulkDelete(ids);
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, agentdbCount] = await Promise.all([
586
+ const [sqliteCount, semanticCount] = await Promise.all([
561
587
  this.sqlite.clearNamespace(namespace),
562
- this.agentdb.clearNamespace(namespace),
588
+ this.semantic.clearNamespace(namespace),
563
589
  ]);
564
- return Math.max(sqliteCount, agentdbCount);
590
+ return Math.max(sqliteCount, semanticCount);
565
591
  }
566
592
  else {
567
- return this.agentdb.clearNamespace(namespace);
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 [sqliteStats, agentdbStats] = await Promise.all([
575
- this.sqlite.getStats(),
576
- this.agentdb.getStats(),
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, agentdbStats.totalEntries),
580
- entriesByNamespace: agentdbStats.entriesByNamespace,
581
- entriesByType: agentdbStats.entriesByType,
582
- memoryUsage: sqliteStats.memoryUsage + agentdbStats.memoryUsage,
583
- hnswStats: agentdbStats.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: agentdbStats.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.agentdbQueries > 0
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.agentdbQueries)
624
+ (this.stats.hybridQueries + this.stats.sqliteQueries + this.stats.semanticQueries)
598
625
  : 0,
599
- avgSearchTime: agentdbStats.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 [sqliteHealth, agentdbHealth] = await Promise.all([
607
- this.sqlite.healthCheck(),
608
- this.agentdb.healthCheck(),
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, ...agentdbHealth.issues];
638
+ const allIssues = [...sqliteHealth.issues, ...semanticHealth.issues];
611
639
  const allRecommendations = [
612
640
  ...sqliteHealth.recommendations,
613
- ...agentdbHealth.recommendations,
641
+ ...semanticHealth.recommendations,
614
642
  ];
615
643
  // Determine overall status
616
644
  let status = 'healthy';
617
645
  if (sqliteHealth.status === 'unhealthy' ||
618
- agentdbHealth.status === 'unhealthy') {
646
+ semanticHealth.status === 'unhealthy') {
619
647
  status = 'unhealthy';
620
648
  }
621
649
  else if (sqliteHealth.status === 'degraded' ||
622
- agentdbHealth.status === 'degraded') {
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: agentdbHealth.components.index,
630
- cache: agentdbHealth.components.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 (AgentDB)
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.agentdbQueries++;
646
- return this.agentdb.query(query);
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 'agentdb-first':
659
- this.stats.agentdbQueries++;
660
- return this.agentdb.query(query);
686
+ case 'semantic-first':
687
+ this.stats.semanticQueries++;
688
+ return this.semantic.query(query);
661
689
  case 'auto':
662
690
  default:
663
- // Default to AgentDB (has caching)
664
- this.stats.agentdbQueries++;
665
- return this.agentdb.query(query);
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 [...semanticResults, ...structuredResults]) {
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(semanticResults.map((e) => e.id));
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(structuredResults.map((e) => e.id));
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 for AgentDB v1 Controllers (ADR-053 #1212) =====
778
+ // ===== Proxy Methods — stubs for legacy controller API =====
741
779
  /**
742
780
  * 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.
781
+ * Delegates to semantic backend's recordFeedback when available.
782
+ * Gracefully degrades to no-op when unavailable.
745
783
  */
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;
784
+ async recordFeedback(_entryId, _feedback) {
785
+ return false; // monolean: stub — LanceDB has no feedback API
759
786
  }
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'] };
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
- getAgentDBBackend() {
800
- return this.agentdb;
800
+ getSemanticBackend() {
801
+ return this.semantic;
801
802
  }
802
803
  }
803
804
  export default HybridBackend;