@claude-flow/memory 3.0.0-alpha.9 → 3.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.
Files changed (188) hide show
  1. package/README.md +308 -37
  2. package/dist/agent-memory-scope.d.ts.map +1 -1
  3. package/dist/agent-memory-scope.js +10 -2
  4. package/dist/agent-memory-scope.js.map +1 -1
  5. package/dist/agentdb-adapter-key-isolation.test.d.ts +2 -0
  6. package/dist/agentdb-adapter-key-isolation.test.d.ts.map +1 -0
  7. package/dist/agentdb-adapter-key-isolation.test.js +175 -0
  8. package/dist/agentdb-adapter-key-isolation.test.js.map +1 -0
  9. package/dist/agentdb-adapter-upsert.test.d.ts +2 -0
  10. package/dist/agentdb-adapter-upsert.test.d.ts.map +1 -0
  11. package/dist/agentdb-adapter-upsert.test.js +216 -0
  12. package/dist/agentdb-adapter-upsert.test.js.map +1 -0
  13. package/dist/agentdb-adapter.d.ts +70 -1
  14. package/dist/agentdb-adapter.d.ts.map +1 -1
  15. package/dist/agentdb-adapter.js +365 -32
  16. package/dist/agentdb-adapter.js.map +1 -1
  17. package/dist/agentdb-backend.d.ts +25 -0
  18. package/dist/agentdb-backend.d.ts.map +1 -1
  19. package/dist/agentdb-backend.js +113 -28
  20. package/dist/agentdb-backend.js.map +1 -1
  21. package/dist/agentdb-retrieval-guard.d.ts +76 -0
  22. package/dist/agentdb-retrieval-guard.d.ts.map +1 -0
  23. package/dist/agentdb-retrieval-guard.js +77 -0
  24. package/dist/agentdb-retrieval-guard.js.map +1 -0
  25. package/dist/agentdb-retrieval-guard.test.d.ts +12 -0
  26. package/dist/agentdb-retrieval-guard.test.d.ts.map +1 -0
  27. package/dist/agentdb-retrieval-guard.test.js +129 -0
  28. package/dist/agentdb-retrieval-guard.test.js.map +1 -0
  29. package/dist/auto-memory-bridge.d.ts +12 -1
  30. package/dist/auto-memory-bridge.d.ts.map +1 -1
  31. package/dist/auto-memory-bridge.js +101 -7
  32. package/dist/auto-memory-bridge.js.map +1 -1
  33. package/dist/auto-memory-bridge.test.js +121 -15
  34. package/dist/auto-memory-bridge.test.js.map +1 -1
  35. package/dist/auto-memory-index-3224.test.d.ts +2 -0
  36. package/dist/auto-memory-index-3224.test.d.ts.map +1 -0
  37. package/dist/auto-memory-index-3224.test.js +77 -0
  38. package/dist/auto-memory-index-3224.test.js.map +1 -0
  39. package/dist/consolidator-embedding-benchmark.test.d.ts +39 -0
  40. package/dist/consolidator-embedding-benchmark.test.d.ts.map +1 -0
  41. package/dist/consolidator-embedding-benchmark.test.js +366 -0
  42. package/dist/consolidator-embedding-benchmark.test.js.map +1 -0
  43. package/dist/consolidator.d.ts +131 -0
  44. package/dist/consolidator.d.ts.map +1 -0
  45. package/dist/consolidator.js +309 -0
  46. package/dist/consolidator.js.map +1 -0
  47. package/dist/consolidator.test.d.ts +17 -0
  48. package/dist/consolidator.test.d.ts.map +1 -0
  49. package/dist/consolidator.test.js +376 -0
  50. package/dist/consolidator.test.js.map +1 -0
  51. package/dist/controller-registry.d.ts +290 -0
  52. package/dist/controller-registry.d.ts.map +1 -0
  53. package/dist/controller-registry.js +1095 -0
  54. package/dist/controller-registry.js.map +1 -0
  55. package/dist/controller-registry.test.d.ts +14 -0
  56. package/dist/controller-registry.test.d.ts.map +1 -0
  57. package/dist/controller-registry.test.js +818 -0
  58. package/dist/controller-registry.test.js.map +1 -0
  59. package/dist/database-provider.d.ts +14 -2
  60. package/dist/database-provider.d.ts.map +1 -1
  61. package/dist/database-provider.js +63 -3
  62. package/dist/database-provider.js.map +1 -1
  63. package/dist/database-provider.test.js +56 -3
  64. package/dist/database-provider.test.js.map +1 -1
  65. package/dist/entity-tagger.d.ts +31 -0
  66. package/dist/entity-tagger.d.ts.map +1 -0
  67. package/dist/entity-tagger.js +80 -0
  68. package/dist/entity-tagger.js.map +1 -0
  69. package/dist/entity-tagger.test.d.ts +12 -0
  70. package/dist/entity-tagger.test.d.ts.map +1 -0
  71. package/dist/entity-tagger.test.js +70 -0
  72. package/dist/entity-tagger.test.js.map +1 -0
  73. package/dist/fts5.test.d.ts +13 -0
  74. package/dist/fts5.test.d.ts.map +1 -0
  75. package/dist/fts5.test.js +144 -0
  76. package/dist/fts5.test.js.map +1 -0
  77. package/dist/graceful-retrieval.test.d.ts +14 -0
  78. package/dist/graceful-retrieval.test.d.ts.map +1 -0
  79. package/dist/graceful-retrieval.test.js +251 -0
  80. package/dist/graceful-retrieval.test.js.map +1 -0
  81. package/dist/hnsw-index.d.ts +62 -0
  82. package/dist/hnsw-index.d.ts.map +1 -1
  83. package/dist/hnsw-index.js +461 -16
  84. package/dist/hnsw-index.js.map +1 -1
  85. package/dist/hnsw-persistence.test.d.ts +13 -0
  86. package/dist/hnsw-persistence.test.d.ts.map +1 -0
  87. package/dist/hnsw-persistence.test.js +208 -0
  88. package/dist/hnsw-persistence.test.js.map +1 -0
  89. package/dist/hnsw-quantization.test.d.ts +17 -0
  90. package/dist/hnsw-quantization.test.d.ts.map +1 -0
  91. package/dist/hnsw-quantization.test.js +140 -0
  92. package/dist/hnsw-quantization.test.js.map +1 -0
  93. package/dist/hybrid-backend.d.ts +46 -2
  94. package/dist/hybrid-backend.d.ts.map +1 -1
  95. package/dist/hybrid-backend.js +120 -21
  96. package/dist/hybrid-backend.js.map +1 -1
  97. package/dist/hybrid-backend.test.js +139 -5
  98. package/dist/hybrid-backend.test.js.map +1 -1
  99. package/dist/index.d.ts +164 -10
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +272 -30
  102. package/dist/index.js.map +1 -1
  103. package/dist/index.test.d.ts +10 -0
  104. package/dist/index.test.d.ts.map +1 -0
  105. package/dist/index.test.js +71 -0
  106. package/dist/index.test.js.map +1 -0
  107. package/dist/json-security.d.ts +12 -0
  108. package/dist/json-security.d.ts.map +1 -0
  109. package/dist/json-security.js +20 -0
  110. package/dist/json-security.js.map +1 -0
  111. package/dist/learning-bridge.d.ts +29 -0
  112. package/dist/learning-bridge.d.ts.map +1 -1
  113. package/dist/learning-bridge.js +71 -6
  114. package/dist/learning-bridge.js.map +1 -1
  115. package/dist/learning-bridge.test.js +181 -1
  116. package/dist/learning-bridge.test.js.map +1 -1
  117. package/dist/memory-key.d.ts +3 -0
  118. package/dist/memory-key.d.ts.map +1 -0
  119. package/dist/memory-key.js +5 -0
  120. package/dist/memory-key.js.map +1 -0
  121. package/dist/migration.d.ts +19 -4
  122. package/dist/migration.d.ts.map +1 -1
  123. package/dist/migration.js +99 -24
  124. package/dist/migration.js.map +1 -1
  125. package/dist/migration.test.d.ts +11 -0
  126. package/dist/migration.test.d.ts.map +1 -0
  127. package/dist/migration.test.js +141 -0
  128. package/dist/migration.test.js.map +1 -0
  129. package/dist/mmr-benchmark.test.d.ts +2 -0
  130. package/dist/mmr-benchmark.test.d.ts.map +1 -0
  131. package/dist/mmr-benchmark.test.js +286 -0
  132. package/dist/mmr-benchmark.test.js.map +1 -0
  133. package/dist/mmr-rerank-cosine-cache.test.d.ts +2 -0
  134. package/dist/mmr-rerank-cosine-cache.test.d.ts.map +1 -0
  135. package/dist/mmr-rerank-cosine-cache.test.js +334 -0
  136. package/dist/mmr-rerank-cosine-cache.test.js.map +1 -0
  137. package/dist/mmr-rerank-perf.test.d.ts +2 -0
  138. package/dist/mmr-rerank-perf.test.d.ts.map +1 -0
  139. package/dist/mmr-rerank-perf.test.js +191 -0
  140. package/dist/mmr-rerank-perf.test.js.map +1 -0
  141. package/dist/persistent-sona.d.ts +144 -0
  142. package/dist/persistent-sona.d.ts.map +1 -0
  143. package/dist/persistent-sona.js +332 -0
  144. package/dist/persistent-sona.js.map +1 -0
  145. package/dist/rvf-backend.d.ts +53 -0
  146. package/dist/rvf-backend.d.ts.map +1 -0
  147. package/dist/rvf-backend.js +643 -0
  148. package/dist/rvf-backend.js.map +1 -0
  149. package/dist/rvf-learning-store.d.ts +139 -0
  150. package/dist/rvf-learning-store.d.ts.map +1 -0
  151. package/dist/rvf-learning-store.js +295 -0
  152. package/dist/rvf-learning-store.js.map +1 -0
  153. package/dist/rvf-migration.d.ts +45 -0
  154. package/dist/rvf-migration.d.ts.map +1 -0
  155. package/dist/rvf-migration.js +254 -0
  156. package/dist/rvf-migration.js.map +1 -0
  157. package/dist/smart-retrieval-scores.test.d.ts +2 -0
  158. package/dist/smart-retrieval-scores.test.d.ts.map +1 -0
  159. package/dist/smart-retrieval-scores.test.js +75 -0
  160. package/dist/smart-retrieval-scores.test.js.map +1 -0
  161. package/dist/smart-retrieval.d.ts +141 -0
  162. package/dist/smart-retrieval.d.ts.map +1 -0
  163. package/dist/smart-retrieval.js +399 -0
  164. package/dist/smart-retrieval.js.map +1 -0
  165. package/dist/smart-retrieval.test.d.ts +2 -0
  166. package/dist/smart-retrieval.test.d.ts.map +1 -0
  167. package/dist/smart-retrieval.test.js +418 -0
  168. package/dist/smart-retrieval.test.js.map +1 -0
  169. package/dist/sqlite-backend.d.ts +10 -0
  170. package/dist/sqlite-backend.d.ts.map +1 -1
  171. package/dist/sqlite-backend.js +101 -5
  172. package/dist/sqlite-backend.js.map +1 -1
  173. package/dist/sqljs-backend.d.ts +9 -0
  174. package/dist/sqljs-backend.d.ts.map +1 -1
  175. package/dist/sqljs-backend.js +91 -3
  176. package/dist/sqljs-backend.js.map +1 -1
  177. package/dist/tiered-memory.d.ts +171 -0
  178. package/dist/tiered-memory.d.ts.map +1 -0
  179. package/dist/tiered-memory.js +350 -0
  180. package/dist/tiered-memory.js.map +1 -0
  181. package/dist/tiered-memory.test.d.ts +9 -0
  182. package/dist/tiered-memory.test.d.ts.map +1 -0
  183. package/dist/tiered-memory.test.js +220 -0
  184. package/dist/tiered-memory.test.js.map +1 -0
  185. package/dist/types.d.ts +17 -0
  186. package/dist/types.d.ts.map +1 -1
  187. package/dist/types.js.map +1 -1
  188. package/package.json +10 -8
@@ -0,0 +1,1095 @@
1
+ /**
2
+ * ControllerRegistry - Central controller lifecycle management for AgentDB v3
3
+ *
4
+ * Wraps the AgentDB class and adds CLI-specific controllers from @claude-flow/memory.
5
+ * Manages initialization (level-based ordering), health checks, and graceful shutdown.
6
+ *
7
+ * Per ADR-053: Replaces memory-initializer.js's raw sql.js usage with a unified
8
+ * controller ecosystem routing all memory operations through AgentDB v3.
9
+ *
10
+ * @module @claude-flow/memory/controller-registry
11
+ */
12
+ import { EventEmitter } from 'node:events';
13
+ import { LearningBridge } from './learning-bridge.js';
14
+ import { MemoryGraph } from './memory-graph.js';
15
+ import { TieredCacheManager } from './cache-manager.js';
16
+ import { TieredMemoryStore } from './tiered-memory.js';
17
+ function isHybridCapableBackend(backend) {
18
+ if (!backend || typeof backend !== 'object')
19
+ return false;
20
+ const b = backend;
21
+ return typeof b.semanticSearch === 'function' && typeof b.searchKeyword === 'function';
22
+ }
23
+ // ===== Initialization Levels =====
24
+ /**
25
+ * Level-based initialization order per ADR-053.
26
+ * Controllers at each level can be initialized in parallel.
27
+ * Each level must complete before the next begins.
28
+ */
29
+ export const INIT_LEVELS = [
30
+ // Level 0: Foundation - already exists
31
+ { level: 0, controllers: [] },
32
+ // Level 1: Core intelligence
33
+ { level: 1, controllers: ['reasoningBank', 'hierarchicalMemory', 'learningBridge', 'hybridSearch', 'tieredCache'] },
34
+ // Level 2: Graph & security
35
+ { level: 2, controllers: ['memoryGraph', 'agentMemoryScope', 'vectorBackend', 'mutationGuard', 'gnnService'] },
36
+ // Level 3: Specialization
37
+ { level: 3, controllers: ['skills', 'explainableRecall', 'reflexion', 'attestationLog', 'batchOperations', 'memoryConsolidation'] },
38
+ // Level 4: Causal & routing
39
+ { level: 4, controllers: ['causalGraph', 'nightlyLearner', 'learningSystem', 'semanticRouter'] },
40
+ // Level 5: Advanced services
41
+ { level: 5, controllers: ['graphTransformer', 'sonaTrajectory', 'contextSynthesizer', 'rvfOptimizer', 'mmrDiversityRanker', 'guardedVectorBackend'] },
42
+ // Level 6: Session management
43
+ { level: 6, controllers: ['federatedSession', 'graphAdapter'] },
44
+ ];
45
+ // ===== ControllerRegistry =====
46
+ /**
47
+ * Central registry for AgentDB v3 controller lifecycle management.
48
+ *
49
+ * Handles:
50
+ * - Level-based initialization ordering (levels 0-6)
51
+ * - Graceful degradation (each controller fails independently)
52
+ * - Config-driven activation (controllers only instantiate when enabled)
53
+ * - Health check aggregation across all controllers
54
+ * - Ordered shutdown (reverse initialization order)
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * const registry = new ControllerRegistry();
59
+ * await registry.initialize({
60
+ * dbPath: './data/memory.db',
61
+ * dimension: 384,
62
+ * memory: {
63
+ * enableHNSW: true,
64
+ * learningBridge: { sonaMode: 'balanced' },
65
+ * memoryGraph: { pageRankDamping: 0.85 },
66
+ * },
67
+ * });
68
+ *
69
+ * const reasoning = registry.get<ReasoningBank>('reasoningBank');
70
+ * const graph = registry.get<MemoryGraph>('memoryGraph');
71
+ *
72
+ * await registry.shutdown();
73
+ * ```
74
+ */
75
+ export class ControllerRegistry extends EventEmitter {
76
+ controllers = new Map();
77
+ agentdb = null;
78
+ backend = null;
79
+ config = {};
80
+ initialized = false;
81
+ initTimeMs = 0;
82
+ hierarchicalFallback = null;
83
+ /**
84
+ * Initialize all controllers in level-based order.
85
+ *
86
+ * Each level's controllers are initialized in parallel within the level.
87
+ * Failures are isolated: a controller that fails to init is marked as
88
+ * unavailable but does not block other controllers.
89
+ */
90
+ async initialize(config = {}) {
91
+ if (this.initialized)
92
+ return;
93
+ this.initialized = true; // Set early to prevent concurrent re-entry
94
+ this.config = config;
95
+ const startTime = performance.now();
96
+ // Step 1: Initialize AgentDB (the core)
97
+ await this.initAgentDB(config);
98
+ // Step 2: Set up the backend
99
+ this.backend = config.backend || null;
100
+ // Step 3: Initialize controllers level by level
101
+ for (const level of INIT_LEVELS) {
102
+ const controllersToInit = level.controllers.filter((name) => this.isControllerEnabled(name));
103
+ if (controllersToInit.length === 0)
104
+ continue;
105
+ // Initialize all controllers in this level in parallel
106
+ const results = await Promise.allSettled(controllersToInit.map((name) => this.initController(name, level.level)));
107
+ // Process results
108
+ for (let i = 0; i < results.length; i++) {
109
+ const result = results[i];
110
+ const name = controllersToInit[i];
111
+ if (result.status === 'rejected') {
112
+ const errorMsg = result.reason instanceof Error
113
+ ? result.reason.message
114
+ : String(result.reason);
115
+ // #2432 — close any prior instance before replacing.
116
+ await this.closePriorIfAny(name);
117
+ this.controllers.set(name, {
118
+ name,
119
+ instance: null,
120
+ level: level.level,
121
+ initTimeMs: 0,
122
+ enabled: false,
123
+ error: errorMsg,
124
+ });
125
+ this.emit('controller:failed', { name, error: errorMsg, level: level.level });
126
+ }
127
+ }
128
+ }
129
+ this.initTimeMs = performance.now() - startTime;
130
+ this.emit('initialized', {
131
+ initTimeMs: this.initTimeMs,
132
+ activeControllers: this.getActiveCount(),
133
+ totalControllers: this.controllers.size,
134
+ });
135
+ }
136
+ /**
137
+ * Shutdown all controllers in reverse initialization order.
138
+ */
139
+ async shutdown() {
140
+ if (!this.initialized)
141
+ return;
142
+ // Shutdown in reverse level order
143
+ const reverseLevels = [...INIT_LEVELS].reverse();
144
+ for (const level of reverseLevels) {
145
+ const controllersToShutdown = level.controllers
146
+ .filter((name) => {
147
+ const entry = this.controllers.get(name);
148
+ return entry?.enabled && entry?.instance;
149
+ });
150
+ await Promise.allSettled(controllersToShutdown.map((name) => this.shutdownController(name)));
151
+ }
152
+ // Shutdown AgentDB
153
+ if (this.agentdb) {
154
+ try {
155
+ if (typeof this.agentdb.close === 'function') {
156
+ await this.agentdb.close();
157
+ }
158
+ }
159
+ catch {
160
+ // Best-effort cleanup
161
+ }
162
+ this.agentdb = null;
163
+ }
164
+ this.controllers.clear();
165
+ this.initialized = false;
166
+ this.emit('shutdown');
167
+ }
168
+ /**
169
+ * Get a controller instance by name.
170
+ * Returns null if the controller is not initialized or unavailable.
171
+ */
172
+ get(name) {
173
+ // First check CLI-layer controllers
174
+ const entry = this.controllers.get(name);
175
+ if (entry?.enabled && entry?.instance) {
176
+ return entry.instance;
177
+ }
178
+ // Fall back to AgentDB internal controllers. Issue #2019:
179
+ // probe `agentdb[name]` first so we don't depend on the upstream
180
+ // getController switch knowing about every field it carries.
181
+ if (this.agentdb) {
182
+ const agentdb = this.agentdb;
183
+ const direct = agentdb[name];
184
+ if (direct)
185
+ return direct;
186
+ if (typeof agentdb.getController === 'function') {
187
+ try {
188
+ const controller = agentdb.getController(name);
189
+ if (controller)
190
+ return controller;
191
+ }
192
+ catch {
193
+ // Upstream switch threw for an unknown name — fine, fall through.
194
+ }
195
+ }
196
+ }
197
+ return null;
198
+ }
199
+ /**
200
+ * Check if a controller is enabled and initialized.
201
+ */
202
+ isEnabled(name) {
203
+ const entry = this.controllers.get(name);
204
+ if (entry?.enabled)
205
+ return true;
206
+ // Issue #2019: same direct-then-fallback shape as get() above.
207
+ if (this.agentdb) {
208
+ const agentdb = this.agentdb;
209
+ if (agentdb[name])
210
+ return true;
211
+ if (typeof agentdb.getController === 'function') {
212
+ try {
213
+ return agentdb.getController(name) !== null;
214
+ }
215
+ catch {
216
+ return false;
217
+ }
218
+ }
219
+ }
220
+ return false;
221
+ }
222
+ /**
223
+ * Aggregate health check across all controllers.
224
+ */
225
+ async healthCheck() {
226
+ const controllerHealth = [];
227
+ for (const [name, entry] of this.controllers) {
228
+ controllerHealth.push({
229
+ name,
230
+ status: entry.enabled
231
+ ? 'healthy'
232
+ : entry.error
233
+ ? 'unavailable'
234
+ : 'degraded',
235
+ initTimeMs: entry.initTimeMs,
236
+ error: entry.error,
237
+ });
238
+ }
239
+ // Check AgentDB health
240
+ let agentdbAvailable = false;
241
+ if (this.agentdb) {
242
+ try {
243
+ agentdbAvailable = typeof this.agentdb.getController === 'function';
244
+ }
245
+ catch {
246
+ agentdbAvailable = false;
247
+ }
248
+ }
249
+ const active = controllerHealth.filter((c) => c.status === 'healthy').length;
250
+ const unavailable = controllerHealth.filter((c) => c.status === 'unavailable').length;
251
+ let status = 'healthy';
252
+ if (unavailable > 0 && active === 0) {
253
+ status = 'unhealthy';
254
+ }
255
+ else if (unavailable > 0) {
256
+ status = 'degraded';
257
+ }
258
+ return {
259
+ status,
260
+ controllers: controllerHealth,
261
+ agentdbAvailable,
262
+ initTimeMs: this.initTimeMs,
263
+ timestamp: Date.now(),
264
+ activeControllers: active,
265
+ totalControllers: controllerHealth.length,
266
+ };
267
+ }
268
+ /**
269
+ * Get the underlying AgentDB instance.
270
+ */
271
+ getAgentDB() {
272
+ return this.agentdb;
273
+ }
274
+ /**
275
+ * Get the memory backend.
276
+ */
277
+ getBackend() {
278
+ return this.backend;
279
+ }
280
+ /**
281
+ * Check if the registry is initialized.
282
+ */
283
+ isInitialized() {
284
+ return this.initialized;
285
+ }
286
+ /**
287
+ * Get the number of active (successfully initialized) controllers.
288
+ */
289
+ getActiveCount() {
290
+ let count = 0;
291
+ for (const entry of this.controllers.values()) {
292
+ if (entry.enabled)
293
+ count++;
294
+ }
295
+ return count;
296
+ }
297
+ /**
298
+ * List all registered controller names and their status.
299
+ */
300
+ listControllers() {
301
+ return Array.from(this.controllers.entries()).map(([name, entry]) => ({
302
+ name,
303
+ enabled: entry.enabled,
304
+ level: entry.level,
305
+ }));
306
+ }
307
+ // ===== Private Methods =====
308
+ /**
309
+ * Initialize AgentDB instance with dynamic import and fallback chain.
310
+ */
311
+ async initAgentDB(config) {
312
+ // Caller-supplied agentdb wins — used by tests (#2019 regression
313
+ // guards) and by consumers that own the AgentDB lifecycle.
314
+ if (config.agentdb) {
315
+ this.agentdb = config.agentdb;
316
+ this.emit('agentdb:initialized');
317
+ return;
318
+ }
319
+ try {
320
+ // Validate dbPath to prevent path traversal
321
+ const dbPath = config.dbPath || ':memory:';
322
+ if (dbPath !== ':memory:') {
323
+ // Use dynamic import instead of require() — require() is not defined in ESM
324
+ // context and silently kills initAgentDB(), disabling all 15+ controllers (#1492).
325
+ const { resolve: resolvePath } = await import('node:path');
326
+ const resolved = resolvePath(dbPath);
327
+ if (resolved.includes('..')) {
328
+ this.emit('agentdb:unavailable', { reason: 'Invalid dbPath' });
329
+ return;
330
+ }
331
+ }
332
+ const agentdbModule = await import('agentdb');
333
+ const AgentDBClass = agentdbModule.AgentDB || agentdbModule.default;
334
+ if (!AgentDBClass) {
335
+ this.emit('agentdb:unavailable', { reason: 'No AgentDB class found' });
336
+ return;
337
+ }
338
+ this.agentdb = new AgentDBClass({ dbPath });
339
+ // Suppress agentdb's noisy info-level output during init
340
+ // using stderr redirect instead of monkey-patching console.log
341
+ const origLog = console.log;
342
+ const suppressFilter = (args) => {
343
+ const msg = String(args[0] ?? '');
344
+ return msg.includes('Transformers.js') ||
345
+ msg.includes('better-sqlite3') ||
346
+ msg.includes('[AgentDB]');
347
+ };
348
+ console.log = (...args) => {
349
+ if (!suppressFilter(args))
350
+ origLog.apply(console, args);
351
+ };
352
+ try {
353
+ await this.agentdb.initialize();
354
+ }
355
+ finally {
356
+ console.log = origLog;
357
+ }
358
+ this.emit('agentdb:initialized');
359
+ }
360
+ catch (error) {
361
+ const msg = error instanceof Error ? error.message : String(error);
362
+ this.emit('agentdb:unavailable', { reason: msg.substring(0, 200) });
363
+ this.agentdb = null;
364
+ }
365
+ }
366
+ /**
367
+ * Check whether a controller should be initialized based on config.
368
+ */
369
+ isControllerEnabled(name) {
370
+ // Explicit enable/disable from config
371
+ if (this.config.controllers) {
372
+ const explicit = this.config.controllers[name];
373
+ if (explicit !== undefined)
374
+ return explicit;
375
+ }
376
+ // Default behavior: enable based on category
377
+ switch (name) {
378
+ // Core intelligence — enabled by default
379
+ case 'reasoningBank':
380
+ case 'learningBridge':
381
+ case 'tieredCache':
382
+ case 'hierarchicalMemory':
383
+ return true;
384
+ // Graph — enabled if backend available
385
+ case 'memoryGraph':
386
+ return !!(this.config.memory?.memoryGraph || this.backend);
387
+ // Security — enabled if AgentDB available
388
+ case 'mutationGuard':
389
+ case 'attestationLog':
390
+ case 'vectorBackend':
391
+ case 'guardedVectorBackend':
392
+ return this.agentdb !== null;
393
+ // AgentDB-internal controllers — only if AgentDB available
394
+ case 'skills':
395
+ case 'reflexion':
396
+ case 'causalGraph':
397
+ case 'causalRecall':
398
+ case 'learningSystem':
399
+ case 'explainableRecall':
400
+ case 'graphTransformer':
401
+ case 'graphAdapter':
402
+ case 'gnnService':
403
+ case 'memoryConsolidation':
404
+ case 'batchOperations':
405
+ case 'contextSynthesizer':
406
+ case 'rvfOptimizer':
407
+ case 'mmrDiversityRanker':
408
+ return this.agentdb !== null;
409
+ // ADR-125 Phase 4 — nightlyLearner is enabled when EITHER an AgentDB
410
+ // is present (legacy path) OR a MemoryService is registered (new path
411
+ // backed by MemoryConsolidator.runAll).
412
+ case 'nightlyLearner':
413
+ return this.agentdb !== null || !!this.config.memoryService;
414
+ // SemanticRouter — auto-enable if agentdb available (exported since alpha.10)
415
+ case 'semanticRouter':
416
+ return this.agentdb !== null;
417
+ // ADR-125 Phase 5 — hybridSearch auto-enables when a MemoryService is
418
+ // registered. Replaces the prior "placeholder, require explicit enable"
419
+ // posture.
420
+ case 'hybridSearch':
421
+ return !!this.config.memoryService;
422
+ // Optional controllers
423
+ case 'agentMemoryScope':
424
+ case 'sonaTrajectory':
425
+ case 'federatedSession':
426
+ return false; // Require explicit enabling
427
+ default:
428
+ return false;
429
+ }
430
+ }
431
+ /**
432
+ * Close any prior controller instance for `name` before it is replaced.
433
+ *
434
+ * #2432 fix — pre-fix, `controllers.set(name, ...)` silently orphaned the
435
+ * prior instance. For backends that allocate native / WASM resources
436
+ * (notably `SqlJsRvfBackend` which keeps an Emscripten MEMFS file ~11 MB
437
+ * per `new SQL.Database(buffer)` until `close()` runs), GC'ing the JS
438
+ * wrapper does NOT reclaim the underlying allocation. A long-running
439
+ * `mcp start` process that re-init'd controllers hundreds of times grew
440
+ * external memory by ~36 GB in production over 6 weeks.
441
+ *
442
+ * Best-effort close: catch and ignore errors — replacement must proceed
443
+ * even if the prior instance's close throws (it's already orphaned at
444
+ * this point either way).
445
+ */
446
+ async closePriorIfAny(name) {
447
+ const prior = this.controllers.get(name);
448
+ if (!prior?.instance)
449
+ return;
450
+ const inst = prior.instance;
451
+ try {
452
+ if (typeof inst.close === 'function') {
453
+ await inst.close();
454
+ }
455
+ else if (typeof inst.dispose === 'function') {
456
+ await inst.dispose();
457
+ }
458
+ }
459
+ catch {
460
+ // Best-effort — leak is preferable to crashing init on a replacement.
461
+ }
462
+ }
463
+ /**
464
+ * Initialize a single controller with error isolation.
465
+ */
466
+ async initController(name, level) {
467
+ const startTime = performance.now();
468
+ // #2432 — close any prior instance before replacing the map entry,
469
+ // otherwise its native/WASM resources leak (e.g. sql.js MEMFS files).
470
+ await this.closePriorIfAny(name);
471
+ try {
472
+ const instance = await this.createController(name);
473
+ const initTimeMs = performance.now() - startTime;
474
+ this.controllers.set(name, {
475
+ name,
476
+ instance,
477
+ level,
478
+ initTimeMs,
479
+ enabled: instance !== null,
480
+ error: instance === null ? 'Controller returned null' : undefined,
481
+ });
482
+ if (instance !== null) {
483
+ this.emit('controller:initialized', { name, level, initTimeMs });
484
+ }
485
+ }
486
+ catch (error) {
487
+ const errorMsg = error instanceof Error ? error.message : String(error);
488
+ const initTimeMs = performance.now() - startTime;
489
+ this.controllers.set(name, {
490
+ name,
491
+ instance: null,
492
+ level,
493
+ initTimeMs,
494
+ enabled: false,
495
+ error: errorMsg,
496
+ });
497
+ throw error;
498
+ }
499
+ }
500
+ /**
501
+ * Factory method to create a controller instance.
502
+ * Handles CLI-layer controllers; AgentDB-internal controllers are
503
+ * accessed via agentdb.getController().
504
+ */
505
+ async createController(name) {
506
+ switch (name) {
507
+ // ----- CLI-layer controllers -----
508
+ case 'learningBridge': {
509
+ if (!this.backend)
510
+ return null;
511
+ const config = this.config.memory?.learningBridge || {};
512
+ const bridge = new LearningBridge(this.backend, {
513
+ sonaMode: config.sonaMode || this.config.neural?.sonaMode || 'balanced',
514
+ confidenceDecayRate: config.confidenceDecayRate,
515
+ accessBoostAmount: config.accessBoostAmount,
516
+ consolidationThreshold: config.consolidationThreshold,
517
+ enabled: true,
518
+ });
519
+ return bridge;
520
+ }
521
+ case 'memoryGraph': {
522
+ const config = this.config.memory?.memoryGraph || {};
523
+ const graph = new MemoryGraph({
524
+ pageRankDamping: config.pageRankDamping,
525
+ maxNodes: config.maxNodes,
526
+ ...config,
527
+ });
528
+ // Build from backend if available
529
+ if (this.backend) {
530
+ try {
531
+ await graph.buildFromBackend(this.backend);
532
+ }
533
+ catch {
534
+ // Graph build from backend failed — empty graph is still usable
535
+ }
536
+ }
537
+ return graph;
538
+ }
539
+ case 'tieredCache': {
540
+ const config = this.config.memory?.tieredCache || {};
541
+ const cache = new TieredCacheManager({
542
+ maxSize: config.maxSize || 10000,
543
+ ttl: config.ttl || 300000,
544
+ lruEnabled: true,
545
+ writeThrough: false,
546
+ ...config,
547
+ });
548
+ return cache;
549
+ }
550
+ case 'hybridSearch': {
551
+ // ADR-125 Phase 5 + ADR-147 P2 — three-arm RRF + MMR hybrid search.
552
+ // Runs semanticSearch() (dense, with graceful fallback),
553
+ // searchKeyword() (sparse FTS5), and a per-entity keyword search
554
+ // (entity arm, gated on extractEntities(query) returning anything)
555
+ // independently in parallel, fuses via RRF, diversifies via MMR.
556
+ // Results carry a `signals` field naming which arms surfaced each
557
+ // entry (provenance for debugging + downstream rerankers).
558
+ //
559
+ // Dream Cycle 2026-08-18: the auto-enable gate (`isControllerEnabled`)
560
+ // already honors an explicit `controllers: { hybridSearch: true }`
561
+ // override, but until now this factory unconditionally required
562
+ // `config.memoryService` regardless of that override — so explicitly
563
+ // opting in without also hand-building a `UnifiedMemoryService`
564
+ // silently produced a disabled controller (no error, no signal why).
565
+ // When no `memoryService` was supplied, fall back to `this.backend`
566
+ // if it duck-types as AgentDBAdapter-compatible (has `semanticSearch`
567
+ // + `searchKeyword`) — the same two methods this factory already
568
+ // calls below. Only reachable when a caller explicitly requests
569
+ // `hybridSearch`; the default (memoryService-only) auto-enable
570
+ // condition and its behavior are unchanged.
571
+ const memSvc = this.config.memoryService;
572
+ const directBackend = !memSvc && isHybridCapableBackend(this.backend) ? this.backend : null;
573
+ const adapter = memSvc
574
+ ? typeof memSvc.getAdapter === 'function'
575
+ ? memSvc.getAdapter()
576
+ : null
577
+ : directBackend;
578
+ if (!adapter)
579
+ return null;
580
+ const { applyRRF, applyMMR } = await import('./smart-retrieval.js');
581
+ const { extractEntities } = await import('./entity-tagger.js');
582
+ return {
583
+ /**
584
+ * Run a fused hybrid search.
585
+ * @param query Free-form query string.
586
+ * @param opts.limit Final result count (default 10).
587
+ * @param opts.fanOutK Per-arm fanout before fusion (default = limit * 3).
588
+ * @param opts.mmrLambda MMR relevance/diversity balance (default 0.7).
589
+ */
590
+ search: async (query, opts = {}) => {
591
+ const limit = opts.limit ?? 10;
592
+ const fanOutK = opts.fanOutK ?? Math.max(limit * 3, 20);
593
+ const mmrLambda = opts.mmrLambda ?? 0.7;
594
+ // Adapt SearchResult[] → SearchCandidate[] expected by RRF
595
+ const toCands = (results) => results.map((r) => ({
596
+ id: r.entry.id,
597
+ key: r.entry.key,
598
+ content: r.entry.content,
599
+ namespace: r.entry.namespace,
600
+ metadata: r.entry.metadata,
601
+ createdAt: r.entry.createdAt,
602
+ updatedAt: r.entry.updatedAt,
603
+ score: r.score,
604
+ // Dream Cycle 2026-09-03: `r.entry.embedding` is already
605
+ // populated by the adapter (agentdb-backend.ts) — was
606
+ // stashed in `_entry` but never copied to `.embedding`, so
607
+ // applyMMR below fell back to token-Jaccard here too.
608
+ // Float32Array→number[] to match smart-retrieval's shape.
609
+ embedding: r.entry.embedding ? Array.from(r.entry.embedding) : undefined,
610
+ _entry: r.entry,
611
+ }));
612
+ // Entity arm — only fire if the query actually contains
613
+ // extractable entities. Empty arm is dropped from RRF input
614
+ // so it doesn't dilute the fusion.
615
+ const entities = extractEntities(query);
616
+ const entityFanOut = entities.length > 0
617
+ ? Math.max(1, Math.ceil(fanOutK / entities.length))
618
+ : 0;
619
+ // Run all three arms in parallel. Per-arm try/catch (via the
620
+ // .catch(...) tails) keeps one failing backend from blanking
621
+ // the other arms — the same defensive shape used pre-ADR-147.
622
+ const [dense, sparse, entityHits] = await Promise.all([
623
+ adapter.semanticSearch(query, fanOutK).catch(() => []),
624
+ adapter.searchKeyword(query, { k: fanOutK }).catch(() => []),
625
+ entities.length > 0
626
+ ? Promise.all(entities.map((e) => adapter.searchKeyword(e, { k: entityFanOut }).catch(() => []))).then((perEntity) => perEntity.flat())
627
+ : Promise.resolve([]),
628
+ ]);
629
+ const denseCands = toCands(dense);
630
+ const sparseCands = toCands(sparse);
631
+ const entityCands = toCands(entityHits);
632
+ // Build signal-provenance sets keyed by candidate id BEFORE
633
+ // RRF so we can stamp `signals` onto the fused output.
634
+ const candKey = (c) => c.id || c.key || c.content.slice(0, 128);
635
+ const denseIds = new Set(denseCands.map(candKey));
636
+ const sparseIds = new Set(sparseCands.map(candKey));
637
+ const entityIds = new Set(entityCands.map(candKey));
638
+ const arms = [denseCands, sparseCands];
639
+ if (entityCands.length > 0)
640
+ arms.push(entityCands);
641
+ const fused = applyRRF(arms, 60);
642
+ const diverse = applyMMR(fused, mmrLambda, limit);
643
+ return diverse.map((s) => {
644
+ const key = candKey(s.candidate);
645
+ const signals = [];
646
+ if (denseIds.has(key))
647
+ signals.push('vector');
648
+ if (sparseIds.has(key))
649
+ signals.push('bm25');
650
+ if (entityIds.has(key))
651
+ signals.push('entity');
652
+ return {
653
+ entry: s.candidate._entry,
654
+ score: s.score,
655
+ signals,
656
+ };
657
+ });
658
+ },
659
+ source: 'hybrid-rrf-mmr',
660
+ };
661
+ }
662
+ case 'agentMemoryScope':
663
+ // Agent memory scope — placeholder, activated when explicitly enabled
664
+ return null;
665
+ case 'semanticRouter':
666
+ // SemanticRouter was exported by agentdb 3.0.0-alpha.10 (ADR-062) but
667
+ // dropped from every installable range (`^3.0.0-alpha.17`) — issue
668
+ // #2977. No fallback implementation exists for this controller;
669
+ // returning null directly instead of attempting a dynamic import
670
+ // that never resolves.
671
+ return null;
672
+ case 'sonaTrajectory':
673
+ // Delegate to AgentDB's SonaTrajectoryService if available
674
+ if (this.agentdb && typeof this.agentdb.getController === 'function') {
675
+ try {
676
+ return this.agentdb.getController('sonaTrajectory');
677
+ }
678
+ catch {
679
+ return null;
680
+ }
681
+ }
682
+ return null;
683
+ case 'hierarchicalMemory': {
684
+ // HierarchicalMemory was exported by agentdb 3.0.0-alpha.10 (ADR-066
685
+ // Phase P2-3) and REMOVED again at alpha.17 — every installable range
686
+ // (`^3.0.0-alpha.17`) is missing it, and alpha.10 — the one version
687
+ // that DOES export it — throws `RangeError: Too few parameter values
688
+ // were provided` on the very first store() call (issue #2977), so
689
+ // there is no version of agentdb where the native path actually
690
+ // works. The dynamic-import/instantiate attempt is dead code on
691
+ // every install; the tiered fallback is the only implementation that
692
+ // has ever worked here, so it is promoted to first-class instead of
693
+ // being attempted-and-discarded on every init. Still records *why*
694
+ // it's in use, because taking a fallback silently is what made
695
+ // stores look successful while landing nowhere (#2887).
696
+ return this.tieredMemoryFallback(this.agentdb ? 'agentdb-export-missing' : 'agentdb-unavailable');
697
+ }
698
+ case 'memoryConsolidation': {
699
+ // MemoryConsolidation was exported by agentdb 3.0.0-alpha.10 (ADR-066
700
+ // Phase P2-3) and removed at alpha.14 — no installable range exports
701
+ // it, and it requires a working HierarchicalMemory instance to
702
+ // construct, which is itself dead (see hierarchicalMemory above,
703
+ // #2977). The no-op stub is therefore the only implementation that
704
+ // has ever worked here; it is promoted to first-class instead of
705
+ // attempting a dynamic import that never resolves.
706
+ return this.createConsolidationStub();
707
+ }
708
+ case 'federatedSession':
709
+ // Federated session — placeholder for Phase 4
710
+ return null;
711
+ // ----- AgentDB-internal controllers (via getController) -----
712
+ // AgentDB.getController() only supports: reflexion/memory, skills, causalGraph/causal
713
+ case 'reasoningBank': {
714
+ // ReasoningBank is exported directly, not via getController
715
+ if (!this.agentdb)
716
+ return null;
717
+ try {
718
+ const agentdbModule = await import('agentdb');
719
+ const RB = agentdbModule.ReasoningBank;
720
+ if (!RB)
721
+ return null;
722
+ // #3327 Finding A — ReasoningBank MUST get AgentDB's own embedder,
723
+ // not createEmbeddingService(). Two independent reasons, both fatal:
724
+ //
725
+ // 1. agentdb's ReasoningBank calls `embedder.embedPassage()` on
726
+ // store and `embedder.embedQuery()` on search (asymmetric
727
+ // query/passage embedding). createEmbeddingService() returns
728
+ // only {embed, embedBatch, initialize}, so BOTH paths threw
729
+ // `TypeError: this.embedder.embedPassage is not a function`.
730
+ // The throw was swallowed by the bridge's catch, which is why
731
+ // the controller reported `enabled: true` while every write and
732
+ // read silently routed to bridge-fallback/substring instead.
733
+ // 2. Even with the names shimmed, the no-generator branch of
734
+ // createEmbeddingService() returns ZERO vectors, so every
735
+ // pattern would embed identically and rank meaninglessly.
736
+ //
737
+ // `this.agentdb.embedder` is normally the real Transformers.js
738
+ // service, which already implements the full contract. But when the
739
+ // model cannot load (offline, or agentdb's `sharp`/transformers
740
+ // optional dep fails to install) agentdb silently swaps in a MOCK
741
+ // embedder that exposes only `embed` — so taking it as-is
742
+ // reintroduces the exact crash this fixes. Adapt whatever we get,
743
+ // and fall back to the local service only when there is no embedder
744
+ // at all.
745
+ const embedder = this.adaptEmbedderForAgentdb(this.agentdb.embedder)
746
+ ?? this.createEmbeddingService();
747
+ return new RB(this.agentdb.database, embedder);
748
+ }
749
+ catch {
750
+ return null;
751
+ }
752
+ }
753
+ case 'skills':
754
+ case 'reflexion':
755
+ case 'causalGraph': {
756
+ if (!this.agentdb || typeof this.agentdb.getController !== 'function')
757
+ return null;
758
+ try {
759
+ return this.agentdb.getController(name) ?? null;
760
+ }
761
+ catch {
762
+ return null;
763
+ }
764
+ }
765
+ case 'causalRecall': {
766
+ if (!this.agentdb)
767
+ return null;
768
+ try {
769
+ const agentdbModule = await import('agentdb');
770
+ const CR = agentdbModule.CausalRecall;
771
+ if (!CR)
772
+ return null;
773
+ return new CR(this.agentdb.database);
774
+ }
775
+ catch {
776
+ return null;
777
+ }
778
+ }
779
+ case 'learningSystem': {
780
+ if (!this.agentdb)
781
+ return null;
782
+ try {
783
+ const agentdbModule = await import('agentdb');
784
+ const LS = agentdbModule.LearningSystem;
785
+ if (!LS)
786
+ return null;
787
+ return new LS(this.agentdb.database);
788
+ }
789
+ catch {
790
+ return null;
791
+ }
792
+ }
793
+ case 'explainableRecall': {
794
+ if (!this.agentdb)
795
+ return null;
796
+ try {
797
+ const agentdbModule = await import('agentdb');
798
+ const ER = agentdbModule.ExplainableRecall;
799
+ if (!ER)
800
+ return null;
801
+ return new ER(this.agentdb.database);
802
+ }
803
+ catch {
804
+ return null;
805
+ }
806
+ }
807
+ case 'nightlyLearner': {
808
+ // ADR-125 Phase 4 — prefer the MemoryConsolidator when a
809
+ // MemoryService is registered. The consolidator's `runAll()` is the
810
+ // documented entry point for sweep + dedup + compact and replaces the
811
+ // thin delegate to AgentDB's NightlyLearner.
812
+ const memSvc = this.config.memoryService;
813
+ if (memSvc && typeof memSvc.getConsolidator === 'function') {
814
+ try {
815
+ const consolidator = await memSvc.getConsolidator();
816
+ return {
817
+ run: () => consolidator.runAll(),
818
+ runAll: () => consolidator.runAll(),
819
+ sweepExpired: () => consolidator.sweepExpired(),
820
+ dedup: (s) => consolidator.dedup(s),
821
+ compactHnsw: () => consolidator.compactHnsw(),
822
+ source: 'memory-consolidator',
823
+ };
824
+ }
825
+ catch { /* fall through to AgentDB */ }
826
+ }
827
+ if (!this.agentdb)
828
+ return null;
829
+ try {
830
+ const agentdbModule = await import('agentdb');
831
+ const NL = agentdbModule.NightlyLearner;
832
+ if (!NL)
833
+ return null;
834
+ return new NL(this.agentdb.database);
835
+ }
836
+ catch {
837
+ return null;
838
+ }
839
+ }
840
+ case 'graphTransformer': {
841
+ if (!this.agentdb)
842
+ return null;
843
+ try {
844
+ const agentdbModule = await import('agentdb');
845
+ const GT = agentdbModule.CausalMemoryGraph;
846
+ if (!GT)
847
+ return null;
848
+ return new GT(this.agentdb.database);
849
+ }
850
+ catch {
851
+ return null;
852
+ }
853
+ }
854
+ // ----- Direct-instantiation controllers -----
855
+ case 'batchOperations': {
856
+ if (!this.agentdb)
857
+ return null;
858
+ try {
859
+ const agentdbModule = await import('agentdb');
860
+ const BO = agentdbModule.BatchOperations;
861
+ if (!BO)
862
+ return null;
863
+ const embedder = this.config.embeddingGenerator || null;
864
+ return new BO(this.agentdb.database, embedder);
865
+ }
866
+ catch {
867
+ return null;
868
+ }
869
+ }
870
+ case 'contextSynthesizer': {
871
+ // ContextSynthesizer.synthesize is static — return the class itself
872
+ try {
873
+ const agentdbModule = await import('agentdb');
874
+ return agentdbModule.ContextSynthesizer ?? null;
875
+ }
876
+ catch {
877
+ return null;
878
+ }
879
+ }
880
+ case 'mmrDiversityRanker': {
881
+ try {
882
+ const agentdbModule = await import('agentdb');
883
+ const MMR = agentdbModule.MMRDiversityRanker;
884
+ if (!MMR)
885
+ return null;
886
+ return new MMR();
887
+ }
888
+ catch {
889
+ return null;
890
+ }
891
+ }
892
+ // MutationGuard, AttestationLog, GNNService, RVFOptimizer, and
893
+ // GuardedVectorBackend were all exported by agentdb 3.0.0-alpha.10
894
+ // (ADR-060/062/065) but dropped from every installable range
895
+ // (`^3.0.0-alpha.17`) — issue #2977. No fallback implementation exists
896
+ // for any of these five controllers; they return null directly instead
897
+ // of attempting a dynamic import that never resolves.
898
+ case 'mutationGuard':
899
+ case 'attestationLog':
900
+ case 'gnnService':
901
+ case 'rvfOptimizer':
902
+ case 'guardedVectorBackend':
903
+ return null;
904
+ case 'vectorBackend':
905
+ case 'graphAdapter': {
906
+ // These are accessed via AgentDB internal state, not direct
907
+ // construction. Issue #2019: agentdb@3.0.0-alpha.14's
908
+ // `getController()` switch only handles
909
+ // memory/reflexion/skills/causal/causalGraph and throws
910
+ // `Unknown controller: vectorBackend` for everything else —
911
+ // which a try/catch silently swallowed, leaving the controller
912
+ // permanently `enabled: false` even though the field is right
913
+ // there on the agentdb instance (`agentdb.vectorBackend` is
914
+ // assigned in AgentDB.initialize()).
915
+ //
916
+ // Prefer the direct-property access. Fall back to
917
+ // `getController` only if the field is absent — preserves
918
+ // forward-compat with a future agentdb that wires
919
+ // vectorBackend / graphAdapter into the switch but stops
920
+ // exposing them as public fields.
921
+ if (!this.agentdb)
922
+ return null;
923
+ const agentdb = this.agentdb;
924
+ const direct = agentdb[name];
925
+ if (direct)
926
+ return direct;
927
+ try {
928
+ if (typeof agentdb.getController === 'function') {
929
+ return agentdb.getController(name) ?? null;
930
+ }
931
+ }
932
+ catch { /* upstream switch threw for an unknown name */ }
933
+ return null;
934
+ }
935
+ default:
936
+ return null;
937
+ }
938
+ }
939
+ /**
940
+ * Shutdown a single controller gracefully.
941
+ */
942
+ async shutdownController(name) {
943
+ const entry = this.controllers.get(name);
944
+ if (!entry?.instance)
945
+ return;
946
+ try {
947
+ const instance = entry.instance;
948
+ // Try known shutdown methods (always await for safety)
949
+ if (typeof instance.destroy === 'function') {
950
+ await instance.destroy();
951
+ }
952
+ else if (typeof instance.shutdown === 'function') {
953
+ await instance.shutdown();
954
+ }
955
+ else if (typeof instance.close === 'function') {
956
+ await instance.close();
957
+ }
958
+ }
959
+ catch {
960
+ // Best-effort cleanup
961
+ }
962
+ entry.enabled = false;
963
+ entry.instance = null;
964
+ }
965
+ /**
966
+ * Create an EmbeddingService for controllers that need it.
967
+ * Uses the config's embedding generator or creates a minimal local service.
968
+ */
969
+ /**
970
+ * Adapt an arbitrary embedder to the contract agentdb's controllers call
971
+ * (#3327).
972
+ *
973
+ * ReasoningBank uses asymmetric query/passage embedding — `embedQuery()` on
974
+ * search, `embedPassage()` on store. Several embedders reaching here expose
975
+ * only `embed()`:
976
+ * • agentdb's mock embedder, substituted whenever the Transformers.js
977
+ * model fails to load (offline, or a failed `sharp` optional install);
978
+ * • any host-supplied `embeddingGenerator` wrapper.
979
+ *
980
+ * Passing one of those through unchanged throws `... is not a function`
981
+ * deep inside the controller, where the bridge's catch converts it into a
982
+ * silent fallback — the failure this whole fix is about. Aliasing is
983
+ * semantically safe: a symmetric model returns the same vector for both
984
+ * roles, which is precisely what a single `embed()` implies.
985
+ *
986
+ * Returns null when there is nothing usable to adapt.
987
+ */
988
+ adaptEmbedderForAgentdb(embedder) {
989
+ if (!embedder || typeof embedder.embed !== 'function')
990
+ return null;
991
+ if (typeof embedder.embedQuery === 'function' && typeof embedder.embedPassage === 'function') {
992
+ return embedder; // already satisfies the contract — pass through untouched
993
+ }
994
+ const embed = embedder.embed.bind(embedder);
995
+ return Object.assign(Object.create(embedder), {
996
+ embed,
997
+ embedQuery: typeof embedder.embedQuery === 'function' ? embedder.embedQuery.bind(embedder) : embed,
998
+ embedPassage: typeof embedder.embedPassage === 'function' ? embedder.embedPassage.bind(embedder) : embed,
999
+ embedBatch: typeof embedder.embedBatch === 'function'
1000
+ ? embedder.embedBatch.bind(embedder)
1001
+ : async (texts) => Promise.all(texts.map((t) => embed(t))),
1002
+ });
1003
+ }
1004
+ createEmbeddingService() {
1005
+ // #3327 — agentdb controllers call the asymmetric query/passage methods
1006
+ // (`embedQuery` / `embedPassage`), not just `embed`. Anything handed this
1007
+ // object must satisfy that wider contract or it throws at call time, deep
1008
+ // inside a controller, where the bridge's catch turns it into a silent
1009
+ // fallback. Alias them onto both branches.
1010
+ if (this.config.embeddingGenerator) {
1011
+ const embed = async (text) => this.config.embeddingGenerator(text);
1012
+ return {
1013
+ embed,
1014
+ embedQuery: embed,
1015
+ embedPassage: embed,
1016
+ embedBatch: async (texts) => Promise.all(texts.map(t => this.config.embeddingGenerator(t))),
1017
+ initialize: async () => { },
1018
+ };
1019
+ }
1020
+ // Zero-vector stub — HierarchicalMemory falls back to manualSearch without
1021
+ // embeddings. NOTE: these vectors are all-zero and carry NO semantic
1022
+ // signal; every pair scores identically. `isStubEmbedder` is exposed so a
1023
+ // caller that needs real similarity can detect this and refuse, rather
1024
+ // than ranking on noise (#3327).
1025
+ const zero = async () => new Float32Array(this.config.dimension || 384);
1026
+ return {
1027
+ isStubEmbedder: true,
1028
+ embed: zero,
1029
+ embedQuery: zero,
1030
+ embedPassage: zero,
1031
+ embedBatch: async (texts) => texts.map(() => new Float32Array(this.config.dimension || 384)),
1032
+ initialize: async () => { },
1033
+ };
1034
+ }
1035
+ /**
1036
+ * Tiered store used when agentdb's HierarchicalMemory is unavailable.
1037
+ *
1038
+ * Since agentdb 3.0.0-alpha.17 dropped the `HierarchicalMemory` export
1039
+ * entirely, this is the ONLY backing store for `agentdb_hierarchical-*`
1040
+ * in practice — so it is handed AgentDB's SQLite connection and writes
1041
+ * through to it. Without a connection it stays volatile and reports
1042
+ * `isDurable() === false` so callers can refuse to claim success (#2887).
1043
+ *
1044
+ * Keeps the legacy duck-typed API — store(key, value, tier) /
1045
+ * recall(query, topK) / getTierStats() — plus Zep/Graphiti-style temporal
1046
+ * validity (validFrom / validUntil / supersededBy).
1047
+ */
1048
+ createTieredMemoryStub() {
1049
+ const db = this.agentdb?.database ?? null;
1050
+ const usable = db && typeof db.prepare === 'function' && typeof db.exec === 'function'
1051
+ ? db
1052
+ : null;
1053
+ return new TieredMemoryStore({ db: usable });
1054
+ }
1055
+ /** Build the tiered fallback and record/emit why the native one was skipped. */
1056
+ tieredMemoryFallback(reason) {
1057
+ const store = this.createTieredMemoryStub();
1058
+ this.hierarchicalFallback = { reason, persistence: store.getPersistence() };
1059
+ this.emit('controller:degraded', {
1060
+ name: 'hierarchicalMemory',
1061
+ reason,
1062
+ persistence: store.getPersistence(),
1063
+ });
1064
+ return store;
1065
+ }
1066
+ /**
1067
+ * Why `hierarchicalMemory` is served by the tiered fallback, and whether
1068
+ * that fallback is durable. Null when the native controller is in use.
1069
+ */
1070
+ getHierarchicalFallback() {
1071
+ return this.hierarchicalFallback;
1072
+ }
1073
+ /**
1074
+ * No-op consolidation stub — the first-class implementation for
1075
+ * `memoryConsolidation` since agentdb has never shipped a working native
1076
+ * MemoryConsolidation on any installable range (#2977). `promoted: 0,
1077
+ * pruned: 0` on its own reads as "ran and found nothing to do"; `source`
1078
+ * and `note` make it explicit that consolidation never actually ran.
1079
+ */
1080
+ createConsolidationStub() {
1081
+ return {
1082
+ consolidate() {
1083
+ return {
1084
+ promoted: 0,
1085
+ pruned: 0,
1086
+ timestamp: Date.now(),
1087
+ source: 'stub',
1088
+ note: 'no-op: native MemoryConsolidation is unavailable (agentdb never exports a working implementation, #2977) — nothing was consolidated, this did not run',
1089
+ };
1090
+ },
1091
+ };
1092
+ }
1093
+ }
1094
+ export default ControllerRegistry;
1095
+ //# sourceMappingURL=controller-registry.js.map