tasq/node_modules/@claude-flow/memory/dist/controller-registry.js

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