897 lines
34 KiB
JavaScript
897 lines
34 KiB
JavaScript
/**
|
|
* ControllerRegistry - Central controller lifecycle management for AgentDB v3
|
|
*
|
|
* Wraps the AgentDB class and adds CLI-specific controllers from @claude-flow/memory.
|
|
* Manages initialization (level-based ordering), health checks, and graceful shutdown.
|
|
*
|
|
* Per ADR-053: Replaces memory-initializer.js's raw sql.js usage with a unified
|
|
* controller ecosystem routing all memory operations through AgentDB v3.
|
|
*
|
|
* @module @claude-flow/memory/controller-registry
|
|
*/
|
|
import { EventEmitter } from 'node:events';
|
|
import { LearningBridge } from './learning-bridge.js';
|
|
import { MemoryGraph } from './memory-graph.js';
|
|
import { TieredCacheManager } from './cache-manager.js';
|
|
// ===== Initialization Levels =====
|
|
/**
|
|
* Level-based initialization order per ADR-053.
|
|
* Controllers at each level can be initialized in parallel.
|
|
* Each level must complete before the next begins.
|
|
*/
|
|
export const INIT_LEVELS = [
|
|
// Level 0: Foundation - already exists
|
|
{ level: 0, controllers: [] },
|
|
// Level 1: Core intelligence
|
|
{ level: 1, controllers: ['reasoningBank', 'hierarchicalMemory', 'learningBridge', 'hybridSearch', 'tieredCache'] },
|
|
// Level 2: Graph & security
|
|
{ level: 2, controllers: ['memoryGraph', 'agentMemoryScope', 'vectorBackend', 'mutationGuard', 'gnnService'] },
|
|
// Level 3: Specialization
|
|
{ level: 3, controllers: ['skills', 'explainableRecall', 'reflexion', 'attestationLog', 'batchOperations', 'memoryConsolidation'] },
|
|
// Level 4: Causal & routing
|
|
{ level: 4, controllers: ['causalGraph', 'nightlyLearner', 'learningSystem', 'semanticRouter'] },
|
|
// Level 5: Advanced services
|
|
{ level: 5, controllers: ['graphTransformer', 'sonaTrajectory', 'contextSynthesizer', 'rvfOptimizer', 'mmrDiversityRanker', 'guardedVectorBackend'] },
|
|
// Level 6: Session management
|
|
{ level: 6, controllers: ['federatedSession', 'graphAdapter'] },
|
|
];
|
|
// ===== ControllerRegistry =====
|
|
/**
|
|
* Central registry for AgentDB v3 controller lifecycle management.
|
|
*
|
|
* Handles:
|
|
* - Level-based initialization ordering (levels 0-6)
|
|
* - Graceful degradation (each controller fails independently)
|
|
* - Config-driven activation (controllers only instantiate when enabled)
|
|
* - Health check aggregation across all controllers
|
|
* - Ordered shutdown (reverse initialization order)
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const registry = new ControllerRegistry();
|
|
* await registry.initialize({
|
|
* dbPath: './data/memory.db',
|
|
* dimension: 384,
|
|
* memory: {
|
|
* enableHNSW: true,
|
|
* learningBridge: { sonaMode: 'balanced' },
|
|
* memoryGraph: { pageRankDamping: 0.85 },
|
|
* },
|
|
* });
|
|
*
|
|
* const reasoning = registry.get<ReasoningBank>('reasoningBank');
|
|
* const graph = registry.get<MemoryGraph>('memoryGraph');
|
|
*
|
|
* await registry.shutdown();
|
|
* ```
|
|
*/
|
|
export class ControllerRegistry extends EventEmitter {
|
|
controllers = new Map();
|
|
agentdb = null;
|
|
backend = null;
|
|
config = {};
|
|
initialized = false;
|
|
initTimeMs = 0;
|
|
/**
|
|
* Initialize all controllers in level-based order.
|
|
*
|
|
* Each level's controllers are initialized in parallel within the level.
|
|
* Failures are isolated: a controller that fails to init is marked as
|
|
* unavailable but does not block other controllers.
|
|
*/
|
|
async initialize(config = {}) {
|
|
if (this.initialized)
|
|
return;
|
|
this.initialized = true; // Set early to prevent concurrent re-entry
|
|
this.config = config;
|
|
const startTime = performance.now();
|
|
// Step 1: Initialize AgentDB (the core)
|
|
await this.initAgentDB(config);
|
|
// Step 2: Set up the backend
|
|
this.backend = config.backend || null;
|
|
// Step 3: Initialize controllers level by level
|
|
for (const level of INIT_LEVELS) {
|
|
const controllersToInit = level.controllers.filter((name) => this.isControllerEnabled(name));
|
|
if (controllersToInit.length === 0)
|
|
continue;
|
|
// Initialize all controllers in this level in parallel
|
|
const results = await Promise.allSettled(controllersToInit.map((name) => this.initController(name, level.level)));
|
|
// Process results
|
|
for (let i = 0; i < results.length; i++) {
|
|
const result = results[i];
|
|
const name = controllersToInit[i];
|
|
if (result.status === 'rejected') {
|
|
const errorMsg = result.reason instanceof Error
|
|
? result.reason.message
|
|
: String(result.reason);
|
|
this.controllers.set(name, {
|
|
name,
|
|
instance: null,
|
|
level: level.level,
|
|
initTimeMs: 0,
|
|
enabled: false,
|
|
error: errorMsg,
|
|
});
|
|
this.emit('controller:failed', { name, error: errorMsg, level: level.level });
|
|
}
|
|
}
|
|
}
|
|
this.initTimeMs = performance.now() - startTime;
|
|
this.emit('initialized', {
|
|
initTimeMs: this.initTimeMs,
|
|
activeControllers: this.getActiveCount(),
|
|
totalControllers: this.controllers.size,
|
|
});
|
|
}
|
|
/**
|
|
* Shutdown all controllers in reverse initialization order.
|
|
*/
|
|
async shutdown() {
|
|
if (!this.initialized)
|
|
return;
|
|
// Shutdown in reverse level order
|
|
const reverseLevels = [...INIT_LEVELS].reverse();
|
|
for (const level of reverseLevels) {
|
|
const controllersToShutdown = level.controllers
|
|
.filter((name) => {
|
|
const entry = this.controllers.get(name);
|
|
return entry?.enabled && entry?.instance;
|
|
});
|
|
await Promise.allSettled(controllersToShutdown.map((name) => this.shutdownController(name)));
|
|
}
|
|
// Shutdown AgentDB
|
|
if (this.agentdb) {
|
|
try {
|
|
if (typeof this.agentdb.close === 'function') {
|
|
await this.agentdb.close();
|
|
}
|
|
}
|
|
catch {
|
|
// Best-effort cleanup
|
|
}
|
|
this.agentdb = null;
|
|
}
|
|
this.controllers.clear();
|
|
this.initialized = false;
|
|
this.emit('shutdown');
|
|
}
|
|
/**
|
|
* Get a controller instance by name.
|
|
* Returns null if the controller is not initialized or unavailable.
|
|
*/
|
|
get(name) {
|
|
// First check CLI-layer controllers
|
|
const entry = this.controllers.get(name);
|
|
if (entry?.enabled && entry?.instance) {
|
|
return entry.instance;
|
|
}
|
|
// Fall back to AgentDB internal controllers
|
|
if (this.agentdb && typeof this.agentdb.getController === 'function') {
|
|
try {
|
|
const controller = this.agentdb.getController(name);
|
|
if (controller)
|
|
return controller;
|
|
}
|
|
catch {
|
|
// Controller not available in AgentDB
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
/**
|
|
* Check if a controller is enabled and initialized.
|
|
*/
|
|
isEnabled(name) {
|
|
const entry = this.controllers.get(name);
|
|
if (entry?.enabled)
|
|
return true;
|
|
// Check AgentDB internal controllers
|
|
if (this.agentdb && typeof this.agentdb.getController === 'function') {
|
|
try {
|
|
return this.agentdb.getController(name) !== null;
|
|
}
|
|
catch {
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* Aggregate health check across all controllers.
|
|
*/
|
|
async healthCheck() {
|
|
const controllerHealth = [];
|
|
for (const [name, entry] of this.controllers) {
|
|
controllerHealth.push({
|
|
name,
|
|
status: entry.enabled
|
|
? 'healthy'
|
|
: entry.error
|
|
? 'unavailable'
|
|
: 'degraded',
|
|
initTimeMs: entry.initTimeMs,
|
|
error: entry.error,
|
|
});
|
|
}
|
|
// Check AgentDB health
|
|
let agentdbAvailable = false;
|
|
if (this.agentdb) {
|
|
try {
|
|
agentdbAvailable = typeof this.agentdb.getController === 'function';
|
|
}
|
|
catch {
|
|
agentdbAvailable = false;
|
|
}
|
|
}
|
|
const active = controllerHealth.filter((c) => c.status === 'healthy').length;
|
|
const unavailable = controllerHealth.filter((c) => c.status === 'unavailable').length;
|
|
let status = 'healthy';
|
|
if (unavailable > 0 && active === 0) {
|
|
status = 'unhealthy';
|
|
}
|
|
else if (unavailable > 0) {
|
|
status = 'degraded';
|
|
}
|
|
return {
|
|
status,
|
|
controllers: controllerHealth,
|
|
agentdbAvailable,
|
|
initTimeMs: this.initTimeMs,
|
|
timestamp: Date.now(),
|
|
activeControllers: active,
|
|
totalControllers: controllerHealth.length,
|
|
};
|
|
}
|
|
/**
|
|
* Get the underlying AgentDB instance.
|
|
*/
|
|
getAgentDB() {
|
|
return this.agentdb;
|
|
}
|
|
/**
|
|
* Get the memory backend.
|
|
*/
|
|
getBackend() {
|
|
return this.backend;
|
|
}
|
|
/**
|
|
* Check if the registry is initialized.
|
|
*/
|
|
isInitialized() {
|
|
return this.initialized;
|
|
}
|
|
/**
|
|
* Get the number of active (successfully initialized) controllers.
|
|
*/
|
|
getActiveCount() {
|
|
let count = 0;
|
|
for (const entry of this.controllers.values()) {
|
|
if (entry.enabled)
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
/**
|
|
* List all registered controller names and their status.
|
|
*/
|
|
listControllers() {
|
|
return Array.from(this.controllers.entries()).map(([name, entry]) => ({
|
|
name,
|
|
enabled: entry.enabled,
|
|
level: entry.level,
|
|
}));
|
|
}
|
|
// ===== Private Methods =====
|
|
/**
|
|
* Initialize AgentDB instance with dynamic import and fallback chain.
|
|
*/
|
|
async initAgentDB(config) {
|
|
try {
|
|
// Validate dbPath to prevent path traversal
|
|
const dbPath = config.dbPath || ':memory:';
|
|
if (dbPath !== ':memory:') {
|
|
// Use dynamic import instead of require() — require() is not defined in ESM
|
|
// context and silently kills initAgentDB(), disabling all 15+ controllers (#1492).
|
|
const { resolve: resolvePath } = await import('node:path');
|
|
const resolved = resolvePath(dbPath);
|
|
if (resolved.includes('..')) {
|
|
this.emit('agentdb:unavailable', { reason: 'Invalid dbPath' });
|
|
return;
|
|
}
|
|
}
|
|
const agentdbModule = await import('agentdb');
|
|
const AgentDBClass = agentdbModule.AgentDB || agentdbModule.default;
|
|
if (!AgentDBClass) {
|
|
this.emit('agentdb:unavailable', { reason: 'No AgentDB class found' });
|
|
return;
|
|
}
|
|
this.agentdb = new AgentDBClass({ dbPath });
|
|
// Suppress agentdb's noisy info-level output during init
|
|
// using stderr redirect instead of monkey-patching console.log
|
|
const origLog = console.log;
|
|
const suppressFilter = (args) => {
|
|
const msg = String(args[0] ?? '');
|
|
return msg.includes('Transformers.js') ||
|
|
msg.includes('better-sqlite3') ||
|
|
msg.includes('[AgentDB]');
|
|
};
|
|
console.log = (...args) => {
|
|
if (!suppressFilter(args))
|
|
origLog.apply(console, args);
|
|
};
|
|
try {
|
|
await this.agentdb.initialize();
|
|
}
|
|
finally {
|
|
console.log = origLog;
|
|
}
|
|
this.emit('agentdb:initialized');
|
|
}
|
|
catch (error) {
|
|
const msg = error instanceof Error ? error.message : String(error);
|
|
this.emit('agentdb:unavailable', { reason: msg.substring(0, 200) });
|
|
this.agentdb = null;
|
|
}
|
|
}
|
|
/**
|
|
* Check whether a controller should be initialized based on config.
|
|
*/
|
|
isControllerEnabled(name) {
|
|
// Explicit enable/disable from config
|
|
if (this.config.controllers) {
|
|
const explicit = this.config.controllers[name];
|
|
if (explicit !== undefined)
|
|
return explicit;
|
|
}
|
|
// Default behavior: enable based on category
|
|
switch (name) {
|
|
// Core intelligence — enabled by default
|
|
case 'reasoningBank':
|
|
case 'learningBridge':
|
|
case 'tieredCache':
|
|
case 'hierarchicalMemory':
|
|
return true;
|
|
// Graph — enabled if backend available
|
|
case 'memoryGraph':
|
|
return !!(this.config.memory?.memoryGraph || this.backend);
|
|
// Security — enabled if AgentDB available
|
|
case 'mutationGuard':
|
|
case 'attestationLog':
|
|
case 'vectorBackend':
|
|
case 'guardedVectorBackend':
|
|
return this.agentdb !== null;
|
|
// AgentDB-internal controllers — only if AgentDB available
|
|
case 'skills':
|
|
case 'reflexion':
|
|
case 'causalGraph':
|
|
case 'causalRecall':
|
|
case 'learningSystem':
|
|
case 'explainableRecall':
|
|
case 'nightlyLearner':
|
|
case 'graphTransformer':
|
|
case 'graphAdapter':
|
|
case 'gnnService':
|
|
case 'memoryConsolidation':
|
|
case 'batchOperations':
|
|
case 'contextSynthesizer':
|
|
case 'rvfOptimizer':
|
|
case 'mmrDiversityRanker':
|
|
return this.agentdb !== null;
|
|
// SemanticRouter — auto-enable if agentdb available (exported since alpha.10)
|
|
case 'semanticRouter':
|
|
return this.agentdb !== null;
|
|
// Optional controllers
|
|
case 'hybridSearch':
|
|
case 'agentMemoryScope':
|
|
case 'sonaTrajectory':
|
|
case 'federatedSession':
|
|
return false; // Require explicit enabling
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
/**
|
|
* Initialize a single controller with error isolation.
|
|
*/
|
|
async initController(name, level) {
|
|
const startTime = performance.now();
|
|
try {
|
|
const instance = await this.createController(name);
|
|
const initTimeMs = performance.now() - startTime;
|
|
this.controllers.set(name, {
|
|
name,
|
|
instance,
|
|
level,
|
|
initTimeMs,
|
|
enabled: instance !== null,
|
|
error: instance === null ? 'Controller returned null' : undefined,
|
|
});
|
|
if (instance !== null) {
|
|
this.emit('controller:initialized', { name, level, initTimeMs });
|
|
}
|
|
}
|
|
catch (error) {
|
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
const initTimeMs = performance.now() - startTime;
|
|
this.controllers.set(name, {
|
|
name,
|
|
instance: null,
|
|
level,
|
|
initTimeMs,
|
|
enabled: false,
|
|
error: errorMsg,
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
/**
|
|
* Factory method to create a controller instance.
|
|
* Handles CLI-layer controllers; AgentDB-internal controllers are
|
|
* accessed via agentdb.getController().
|
|
*/
|
|
async createController(name) {
|
|
switch (name) {
|
|
// ----- CLI-layer controllers -----
|
|
case 'learningBridge': {
|
|
if (!this.backend)
|
|
return null;
|
|
const config = this.config.memory?.learningBridge || {};
|
|
const bridge = new LearningBridge(this.backend, {
|
|
sonaMode: config.sonaMode || this.config.neural?.sonaMode || 'balanced',
|
|
confidenceDecayRate: config.confidenceDecayRate,
|
|
accessBoostAmount: config.accessBoostAmount,
|
|
consolidationThreshold: config.consolidationThreshold,
|
|
enabled: true,
|
|
});
|
|
return bridge;
|
|
}
|
|
case 'memoryGraph': {
|
|
const config = this.config.memory?.memoryGraph || {};
|
|
const graph = new MemoryGraph({
|
|
pageRankDamping: config.pageRankDamping,
|
|
maxNodes: config.maxNodes,
|
|
...config,
|
|
});
|
|
// Build from backend if available
|
|
if (this.backend) {
|
|
try {
|
|
await graph.buildFromBackend(this.backend);
|
|
}
|
|
catch {
|
|
// Graph build from backend failed — empty graph is still usable
|
|
}
|
|
}
|
|
return graph;
|
|
}
|
|
case 'tieredCache': {
|
|
const config = this.config.memory?.tieredCache || {};
|
|
const cache = new TieredCacheManager({
|
|
maxSize: config.maxSize || 10000,
|
|
ttl: config.ttl || 300000,
|
|
lruEnabled: true,
|
|
writeThrough: false,
|
|
...config,
|
|
});
|
|
return cache;
|
|
}
|
|
case 'hybridSearch':
|
|
// BM25 hybrid search — placeholder for future implementation
|
|
return null;
|
|
case 'agentMemoryScope':
|
|
// Agent memory scope — placeholder, activated when explicitly enabled
|
|
return null;
|
|
case 'semanticRouter': {
|
|
// SemanticRouter exported from agentdb 3.0.0-alpha.10 (ADR-062)
|
|
// Constructor: () — requires initialize() after construction
|
|
try {
|
|
const agentdbModule = await import('agentdb');
|
|
const SR = agentdbModule.SemanticRouter;
|
|
if (!SR)
|
|
return null;
|
|
const router = new SR();
|
|
await router.initialize();
|
|
return router;
|
|
}
|
|
catch {
|
|
return null;
|
|
}
|
|
}
|
|
case 'sonaTrajectory':
|
|
// Delegate to AgentDB's SonaTrajectoryService if available
|
|
if (this.agentdb && typeof this.agentdb.getController === 'function') {
|
|
try {
|
|
return this.agentdb.getController('sonaTrajectory');
|
|
}
|
|
catch {
|
|
return null;
|
|
}
|
|
}
|
|
return null;
|
|
case 'hierarchicalMemory': {
|
|
// HierarchicalMemory exported from agentdb 3.0.0-alpha.10 (ADR-066 Phase P2-3)
|
|
// Constructor: (db, embedder, vectorBackend?, graphBackend?, config?)
|
|
if (!this.agentdb)
|
|
return this.createTieredMemoryStub();
|
|
try {
|
|
const agentdbModule = await import('agentdb');
|
|
const HM = agentdbModule.HierarchicalMemory;
|
|
if (!HM)
|
|
return this.createTieredMemoryStub();
|
|
const embedder = this.createEmbeddingService();
|
|
const hm = new HM(this.agentdb.database, embedder);
|
|
await hm.initializeDatabase();
|
|
return hm;
|
|
}
|
|
catch {
|
|
return this.createTieredMemoryStub();
|
|
}
|
|
}
|
|
case 'memoryConsolidation': {
|
|
// MemoryConsolidation exported from agentdb 3.0.0-alpha.10 (ADR-066 Phase P2-3)
|
|
// Constructor: (db, hierarchicalMemory, embedder, vectorBackend?, graphBackend?, config?)
|
|
if (!this.agentdb)
|
|
return this.createConsolidationStub();
|
|
try {
|
|
const agentdbModule = await import('agentdb');
|
|
const MC = agentdbModule.MemoryConsolidation;
|
|
if (!MC)
|
|
return this.createConsolidationStub();
|
|
// Get the HierarchicalMemory instance (must be initialized at level 1 before us at level 3)
|
|
const hm = this.get('hierarchicalMemory');
|
|
if (!hm || typeof hm.recall !== 'function' || typeof hm.store !== 'function') {
|
|
return this.createConsolidationStub();
|
|
}
|
|
const embedder = this.createEmbeddingService();
|
|
const mc = new MC(this.agentdb.database, hm, embedder);
|
|
await mc.initializeDatabase();
|
|
return mc;
|
|
}
|
|
catch {
|
|
return this.createConsolidationStub();
|
|
}
|
|
}
|
|
case 'federatedSession':
|
|
// Federated session — placeholder for Phase 4
|
|
return null;
|
|
// ----- AgentDB-internal controllers (via getController) -----
|
|
// AgentDB.getController() only supports: reflexion/memory, skills, causalGraph/causal
|
|
case 'reasoningBank': {
|
|
// ReasoningBank is exported directly, not via getController
|
|
if (!this.agentdb)
|
|
return null;
|
|
try {
|
|
const agentdbModule = await import('agentdb');
|
|
const RB = agentdbModule.ReasoningBank;
|
|
if (!RB)
|
|
return null;
|
|
const embedder = this.createEmbeddingService();
|
|
return new RB(this.agentdb.database, embedder);
|
|
}
|
|
catch {
|
|
return null;
|
|
}
|
|
}
|
|
case 'skills':
|
|
case 'reflexion':
|
|
case 'causalGraph': {
|
|
if (!this.agentdb || typeof this.agentdb.getController !== 'function')
|
|
return null;
|
|
try {
|
|
return this.agentdb.getController(name) ?? null;
|
|
}
|
|
catch {
|
|
return null;
|
|
}
|
|
}
|
|
case 'causalRecall': {
|
|
if (!this.agentdb)
|
|
return null;
|
|
try {
|
|
const agentdbModule = await import('agentdb');
|
|
const CR = agentdbModule.CausalRecall;
|
|
if (!CR)
|
|
return null;
|
|
return new CR(this.agentdb.database);
|
|
}
|
|
catch {
|
|
return null;
|
|
}
|
|
}
|
|
case 'learningSystem': {
|
|
if (!this.agentdb)
|
|
return null;
|
|
try {
|
|
const agentdbModule = await import('agentdb');
|
|
const LS = agentdbModule.LearningSystem;
|
|
if (!LS)
|
|
return null;
|
|
return new LS(this.agentdb.database);
|
|
}
|
|
catch {
|
|
return null;
|
|
}
|
|
}
|
|
case 'explainableRecall': {
|
|
if (!this.agentdb)
|
|
return null;
|
|
try {
|
|
const agentdbModule = await import('agentdb');
|
|
const ER = agentdbModule.ExplainableRecall;
|
|
if (!ER)
|
|
return null;
|
|
return new ER(this.agentdb.database);
|
|
}
|
|
catch {
|
|
return null;
|
|
}
|
|
}
|
|
case 'nightlyLearner': {
|
|
if (!this.agentdb)
|
|
return null;
|
|
try {
|
|
const agentdbModule = await import('agentdb');
|
|
const NL = agentdbModule.NightlyLearner;
|
|
if (!NL)
|
|
return null;
|
|
return new NL(this.agentdb.database);
|
|
}
|
|
catch {
|
|
return null;
|
|
}
|
|
}
|
|
case 'graphTransformer': {
|
|
if (!this.agentdb)
|
|
return null;
|
|
try {
|
|
const agentdbModule = await import('agentdb');
|
|
const GT = agentdbModule.CausalMemoryGraph;
|
|
if (!GT)
|
|
return null;
|
|
return new GT(this.agentdb.database);
|
|
}
|
|
catch {
|
|
return null;
|
|
}
|
|
}
|
|
// ----- Direct-instantiation controllers -----
|
|
case 'batchOperations': {
|
|
if (!this.agentdb)
|
|
return null;
|
|
try {
|
|
const agentdbModule = await import('agentdb');
|
|
const BO = agentdbModule.BatchOperations;
|
|
if (!BO)
|
|
return null;
|
|
const embedder = this.config.embeddingGenerator || null;
|
|
return new BO(this.agentdb.database, embedder);
|
|
}
|
|
catch {
|
|
return null;
|
|
}
|
|
}
|
|
case 'contextSynthesizer': {
|
|
// ContextSynthesizer.synthesize is static — return the class itself
|
|
try {
|
|
const agentdbModule = await import('agentdb');
|
|
return agentdbModule.ContextSynthesizer ?? null;
|
|
}
|
|
catch {
|
|
return null;
|
|
}
|
|
}
|
|
case 'mmrDiversityRanker': {
|
|
try {
|
|
const agentdbModule = await import('agentdb');
|
|
const MMR = agentdbModule.MMRDiversityRanker;
|
|
if (!MMR)
|
|
return null;
|
|
return new MMR();
|
|
}
|
|
catch {
|
|
return null;
|
|
}
|
|
}
|
|
case 'mutationGuard': {
|
|
// MutationGuard exported from agentdb 3.0.0-alpha.10 (ADR-060)
|
|
// Constructor: (config?) where config.dimension, config.maxElements, config.enableWasmProofs
|
|
if (!this.agentdb)
|
|
return null;
|
|
try {
|
|
const agentdbModule = await import('agentdb');
|
|
const MG = agentdbModule.MutationGuard;
|
|
if (!MG)
|
|
return null;
|
|
return new MG({ dimension: this.config.dimension || 384 });
|
|
}
|
|
catch {
|
|
return null;
|
|
}
|
|
}
|
|
case 'attestationLog': {
|
|
// AttestationLog exported from agentdb 3.0.0-alpha.10 (ADR-060)
|
|
// Constructor: (db) — uses database for append-only audit log
|
|
if (!this.agentdb)
|
|
return null;
|
|
try {
|
|
const agentdbModule = await import('agentdb');
|
|
const AL = agentdbModule.AttestationLog;
|
|
if (!AL)
|
|
return null;
|
|
return new AL(this.agentdb.database);
|
|
}
|
|
catch {
|
|
return null;
|
|
}
|
|
}
|
|
case 'gnnService': {
|
|
// GNNService exported from agentdb 3.0.0-alpha.10 (ADR-062)
|
|
// Constructor: (config?) — requires initialize() after construction
|
|
try {
|
|
const agentdbModule = await import('agentdb');
|
|
const GNN = agentdbModule.GNNService;
|
|
if (!GNN)
|
|
return null;
|
|
const gnn = new GNN({ inputDim: this.config.dimension || 384 });
|
|
await gnn.initialize();
|
|
return gnn;
|
|
}
|
|
catch {
|
|
return null;
|
|
}
|
|
}
|
|
case 'rvfOptimizer': {
|
|
// RVFOptimizer exported from agentdb 3.0.0-alpha.10 (ADR-062/065)
|
|
// Constructor: (config?) — no-arg for defaults
|
|
try {
|
|
const agentdbModule = await import('agentdb');
|
|
const RVF = agentdbModule.RVFOptimizer;
|
|
if (!RVF)
|
|
return null;
|
|
return new RVF();
|
|
}
|
|
catch {
|
|
return null;
|
|
}
|
|
}
|
|
case 'guardedVectorBackend': {
|
|
// GuardedVectorBackend exported from agentdb 3.0.0-alpha.10 (ADR-060)
|
|
// Constructor: (innerBackend, mutationGuard, attestationLog?)
|
|
// Requires vectorBackend and mutationGuard to be initialized first (level 2)
|
|
if (!this.agentdb)
|
|
return null;
|
|
try {
|
|
const vb = this.get('vectorBackend');
|
|
const guard = this.get('mutationGuard');
|
|
if (!vb || !guard)
|
|
return null;
|
|
const agentdbModule = await import('agentdb');
|
|
const GVB = agentdbModule.GuardedVectorBackend;
|
|
if (!GVB)
|
|
return null;
|
|
const log = this.get('attestationLog');
|
|
return new GVB(vb, guard, log || undefined);
|
|
}
|
|
catch {
|
|
return null;
|
|
}
|
|
}
|
|
case 'vectorBackend':
|
|
case 'graphAdapter': {
|
|
// These are accessed via AgentDB internal state, not direct construction
|
|
if (!this.agentdb)
|
|
return null;
|
|
try {
|
|
if (typeof this.agentdb.getController === 'function') {
|
|
return this.agentdb.getController(name) ?? null;
|
|
}
|
|
}
|
|
catch { /* fallthrough */ }
|
|
return null;
|
|
}
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
/**
|
|
* Shutdown a single controller gracefully.
|
|
*/
|
|
async shutdownController(name) {
|
|
const entry = this.controllers.get(name);
|
|
if (!entry?.instance)
|
|
return;
|
|
try {
|
|
const instance = entry.instance;
|
|
// Try known shutdown methods (always await for safety)
|
|
if (typeof instance.destroy === 'function') {
|
|
await instance.destroy();
|
|
}
|
|
else if (typeof instance.shutdown === 'function') {
|
|
await instance.shutdown();
|
|
}
|
|
else if (typeof instance.close === 'function') {
|
|
await instance.close();
|
|
}
|
|
}
|
|
catch {
|
|
// Best-effort cleanup
|
|
}
|
|
entry.enabled = false;
|
|
entry.instance = null;
|
|
}
|
|
/**
|
|
* Create an EmbeddingService for controllers that need it.
|
|
* Uses the config's embedding generator or creates a minimal local service.
|
|
*/
|
|
createEmbeddingService() {
|
|
// If user provided an embedding generator, wrap it
|
|
if (this.config.embeddingGenerator) {
|
|
return {
|
|
embed: async (text) => this.config.embeddingGenerator(text),
|
|
embedBatch: async (texts) => Promise.all(texts.map(t => this.config.embeddingGenerator(t))),
|
|
initialize: async () => { },
|
|
};
|
|
}
|
|
// Return a minimal stub — HierarchicalMemory falls back to manualSearch without embeddings
|
|
return {
|
|
embed: async () => new Float32Array(this.config.dimension || 384),
|
|
embedBatch: async (texts) => texts.map(() => new Float32Array(this.config.dimension || 384)),
|
|
initialize: async () => { },
|
|
};
|
|
}
|
|
/**
|
|
* Lightweight in-memory tiered store (fallback when HierarchicalMemory
|
|
* cannot be initialized from agentdb).
|
|
* Enforces per-tier size limits to prevent unbounded memory growth.
|
|
*/
|
|
createTieredMemoryStub() {
|
|
const MAX_PER_TIER = 5000;
|
|
const tiers = {
|
|
working: new Map(),
|
|
episodic: new Map(),
|
|
semantic: new Map(),
|
|
};
|
|
return {
|
|
store(key, value, tier = 'working') {
|
|
const t = tiers[tier] || tiers.working;
|
|
// Evict oldest if at capacity
|
|
if (t.size >= MAX_PER_TIER) {
|
|
const oldest = t.keys().next().value;
|
|
if (oldest !== undefined)
|
|
t.delete(oldest);
|
|
}
|
|
t.set(key, { value: value.substring(0, 100_000), ts: Date.now() });
|
|
},
|
|
recall(query, topK = 5) {
|
|
const safeTopK = Math.min(Math.max(1, topK), 100);
|
|
const q = query.toLowerCase().substring(0, 10_000);
|
|
const results = [];
|
|
for (const [tierName, map] of Object.entries(tiers)) {
|
|
for (const [key, entry] of map) {
|
|
if (key.toLowerCase().includes(q) || entry.value.toLowerCase().includes(q)) {
|
|
results.push({ key, value: entry.value, tier: tierName, ts: entry.ts });
|
|
if (results.length >= safeTopK * 3)
|
|
break; // Early exit for large stores
|
|
}
|
|
}
|
|
}
|
|
return results.sort((a, b) => b.ts - a.ts).slice(0, safeTopK);
|
|
},
|
|
getTierStats() {
|
|
return Object.fromEntries(Object.entries(tiers).map(([name, map]) => [name, map.size]));
|
|
},
|
|
};
|
|
}
|
|
/**
|
|
* No-op consolidation stub (fallback when MemoryConsolidation
|
|
* cannot be initialized from agentdb).
|
|
*/
|
|
createConsolidationStub() {
|
|
return {
|
|
consolidate() {
|
|
return { promoted: 0, pruned: 0, timestamp: Date.now() };
|
|
},
|
|
};
|
|
}
|
|
}
|
|
export default ControllerRegistry;
|
|
//# sourceMappingURL=controller-registry.js.map
|