UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

454 lines (385 loc) 13.1 kB
/** * LearningBridge - Connects AutoMemoryBridge to NeuralLearningSystem * * When insights are recorded via AutoMemoryBridge, this module triggers * neural learning trajectories so the system continuously improves from * its own discoveries. The NeuralLearningSystem dependency is optional: * when unavailable, all operations degrade gracefully to no-ops. * * @module @claude-flow/memory/learning-bridge */ import { EventEmitter } from 'node:events'; import type { IMemoryBackend, MemoryEntry, SONAMode } from './types.js'; import type { MemoryInsight, InsightCategory } from './auto-memory-bridge.js'; // ===== Types ===== /** * Factory function that returns a neural system instance. * Used for dependency injection so tests can supply a mock. */ export type NeuralLoader = () => Promise<any>; /** Configuration for the LearningBridge */ export interface LearningBridgeConfig { /** SONA operating mode (default: 'balanced') */ sonaMode?: SONAMode; /** Per-hour confidence decay rate (default: 0.005) */ confidenceDecayRate?: number; /** Confidence boost per access (default: 0.03) */ accessBoostAmount?: number; /** Maximum confidence value (default: 1.0) */ maxConfidence?: number; /** Minimum confidence floor (default: 0.1) */ minConfidence?: number; /** EWC regularization strength (default: 2000) */ ewcLambda?: number; /** Min active trajectories before consolidation runs (default: 10) */ consolidationThreshold?: number; /** Enable the bridge (default: true). When false all methods are no-ops */ enabled?: boolean; /** * Optional factory for the neural learning system. * When provided, this replaces the default dynamic import of @claude-flow/neural. * Primarily used for testing. */ neuralLoader?: NeuralLoader; } /** Aggregated learning statistics */ export interface LearningStats { totalTrajectories: number; completedTrajectories: number; activeTrajectories: number; totalConsolidations: number; totalDecays: number; avgConfidenceBoost: number; neuralAvailable: boolean; } /** Result returned by consolidate() */ export interface ConsolidateResult { trajectoriesCompleted: number; patternsLearned: number; entriesUpdated: number; durationMs: number; } /** A single pattern match returned by findSimilarPatterns() */ export interface PatternMatch { content: string; similarity: number; category: string; confidence: number; } // ===== Defaults ===== /** Internal resolved config type where neuralLoader stays optional */ type ResolvedConfig = Required<Omit<LearningBridgeConfig, 'neuralLoader'>> & { neuralLoader?: NeuralLoader; }; const DEFAULT_CONFIG: ResolvedConfig = { sonaMode: 'balanced', confidenceDecayRate: 0.005, accessBoostAmount: 0.03, maxConfidence: 1.0, minConfidence: 0.1, ewcLambda: 2000, consolidationThreshold: 10, enabled: true, }; const MS_PER_HOUR = 3_600_000; // ===== LearningBridge ===== /** * Connects AutoMemoryBridge insights to the NeuralLearningSystem. * * @example * ```typescript * const bridge = new LearningBridge(memoryBackend); * await bridge.onInsightRecorded(insight, entryId); * await bridge.onInsightAccessed(entryId); * const result = await bridge.consolidate(); * ``` */ export class LearningBridge extends EventEmitter { private neural: any | null = null; private backend: IMemoryBackend; private config: ResolvedConfig; private activeTrajectories: Map<string, string> = new Map(); private stats = { totalTrajectories: 0, completedTrajectories: 0, totalConsolidations: 0, totalDecays: 0, confidenceBoosts: 0, totalBoostAmount: 0, }; private destroyed = false; private neuralInitPromise: Promise<void> | null = null; constructor(backend: IMemoryBackend, config?: LearningBridgeConfig) { super(); this.backend = backend; this.config = { ...DEFAULT_CONFIG, ...config }; } // ===== Public API ===== /** * Notify the bridge that an insight has been recorded in AgentDB. * Creates a learning trajectory so the neural system can track the * insight's lifecycle. */ async onInsightRecorded(insight: MemoryInsight, entryId: string): Promise<void> { if (!this.config.enabled || this.destroyed) return; await this.initNeural(); if (this.neural) { try { const trajectoryId = this.neural.beginTask(insight.summary, 'general'); this.activeTrajectories.set(entryId, trajectoryId); this.stats.totalTrajectories++; const embedding = this.createHashEmbedding(insight.summary); this.neural.recordStep(trajectoryId, { action: `record:${insight.category}`, reward: insight.confidence, stateEmbedding: embedding, }); } catch { // Neural system failure is non-fatal } } this.emit('insight:learning-started', { entryId, category: insight.category }); } /** * Notify the bridge that an insight entry was accessed. * Boosts confidence in the backend and records a step in the * trajectory if one exists. */ async onInsightAccessed(entryId: string): Promise<void> { if (!this.config.enabled || this.destroyed) return; const entry = await this.backend.get(entryId); if (!entry) return; const currentConf = (entry.metadata?.confidence as number) ?? 0.5; const newConf = Math.min( this.config.maxConfidence, currentConf + this.config.accessBoostAmount, ); await this.backend.update(entryId, { metadata: { ...entry.metadata, confidence: newConf }, }); this.stats.confidenceBoosts++; this.stats.totalBoostAmount += this.config.accessBoostAmount; if (this.neural && this.activeTrajectories.has(entryId)) { try { const trajectoryId = this.activeTrajectories.get(entryId)!; this.neural.recordStep(trajectoryId, { action: 'access', reward: this.config.accessBoostAmount, }); } catch { // Non-fatal } } this.emit('insight:accessed', { entryId, newConfidence: newConf }); } /** * Consolidate active trajectories by completing them in the neural system. * Only runs when there are enough active trajectories to justify the cost. */ async consolidate(): Promise<ConsolidateResult> { const startTime = Date.now(); const earlyResult: ConsolidateResult = { trajectoriesCompleted: 0, patternsLearned: 0, entriesUpdated: 0, durationMs: 0, }; if (!this.config.enabled || this.destroyed) { return earlyResult; } if (!this.neural || this.activeTrajectories.size < this.config.consolidationThreshold) { earlyResult.durationMs = Date.now() - startTime; return earlyResult; } let completed = 0; let patternsLearned = 0; const toRemove: string[] = []; const entries = Array.from(this.activeTrajectories.entries()); for (const [entryId, trajectoryId] of entries) { try { await this.neural.completeTask(trajectoryId, 1.0); completed++; patternsLearned++; toRemove.push(entryId); } catch { // Skip failed completions } } for (const key of toRemove) { this.activeTrajectories.delete(key); } this.stats.completedTrajectories += completed; this.stats.totalConsolidations++; const result: ConsolidateResult = { trajectoriesCompleted: completed, patternsLearned, entriesUpdated: completed, durationMs: Date.now() - startTime, }; this.emit('consolidation:completed', result); return result; } /** * Apply time-based confidence decay to entries in the given namespace. * Entries not accessed for more than one hour see their confidence reduced * proportionally to the hours elapsed, down to minConfidence. * * @returns number of entries whose confidence was lowered */ async decayConfidences(namespace: string): Promise<number> { if (!this.config.enabled || this.destroyed) return 0; let entries: MemoryEntry[]; try { entries = await this.backend.query({ type: 'hybrid', namespace, limit: 1000, }); } catch { return 0; } const now = Date.now(); let decayed = 0; for (const entry of entries) { const hoursSinceUpdate = (now - entry.updatedAt) / MS_PER_HOUR; if (hoursSinceUpdate < 1) continue; const currentConf = (entry.metadata?.confidence as number) ?? 0.5; const newConf = Math.max( this.config.minConfidence, currentConf - this.config.confidenceDecayRate * hoursSinceUpdate, ); if (newConf < currentConf) { try { await this.backend.update(entry.id, { metadata: { ...entry.metadata, confidence: newConf }, }); decayed++; } catch { // Skip failed updates } } } this.stats.totalDecays += decayed; return decayed; } /** * Find patterns similar to the given content using the neural system. * Returns an empty array when the neural system is unavailable. */ async findSimilarPatterns(content: string, k: number = 5): Promise<PatternMatch[]> { if (!this.config.enabled || this.destroyed) return []; await this.initNeural(); if (!this.neural) return []; try { const embedding = this.createHashEmbedding(content); const results = await this.neural.findPatterns(embedding, k); if (!Array.isArray(results)) return []; return results.map((r: any) => ({ content: r.content ?? r.data ?? '', similarity: r.similarity ?? r.score ?? 0, category: r.category ?? 'unknown', confidence: r.confidence ?? r.reward ?? 0, })); } catch { return []; } } /** Return aggregated learning statistics */ getStats(): LearningStats { const avgBoost = this.stats.confidenceBoosts > 0 ? this.stats.totalBoostAmount / this.stats.confidenceBoosts : 0; return { totalTrajectories: this.stats.totalTrajectories, completedTrajectories: this.stats.completedTrajectories, activeTrajectories: this.activeTrajectories.size, totalConsolidations: this.stats.totalConsolidations, totalDecays: this.stats.totalDecays, avgConfidenceBoost: avgBoost, neuralAvailable: this.neural !== null, }; } /** Tear down the bridge. Subsequent method calls become no-ops. */ destroy(): void { this.destroyed = true; this.activeTrajectories.clear(); if (this.neural && typeof this.neural.cleanup === 'function') { try { this.neural.cleanup(); } catch { // Best-effort cleanup } } this.neural = null; this.neuralInitPromise = null; this.removeAllListeners(); } // ===== Private ===== /** * Lazily attempt to load and initialize the NeuralLearningSystem. * The promise is cached so that repeated calls do not re-attempt * after a failure. */ private async initNeural(): Promise<void> { if (this.neural) return; if (this.neuralInitPromise) { await this.neuralInitPromise; return; } this.neuralInitPromise = this.loadNeural(); await this.neuralInitPromise; } private async loadNeural(): Promise<void> { try { if (this.config.neuralLoader) { // Use injected loader (test / custom integrations) this.neural = await this.config.neuralLoader(); return; } const mod = await import('@claude-flow/neural' as string); const NeuralLearningSystem = mod.NeuralLearningSystem ?? mod.default; if (!NeuralLearningSystem) return; const instance = new NeuralLearningSystem({ mode: this.config.sonaMode, ewcLambda: this.config.ewcLambda, }); if (typeof instance.initialize === 'function') { await instance.initialize(); } this.neural = instance; } catch { // @claude-flow/neural not installed or failed to initialize. // This is expected in many environments; degrade silently. this.neural = null; } } /** * Create a deterministic hash-based embedding for content. * This is a lightweight stand-in for a real embedding model, * suitable for pattern matching within the neural trajectory system. */ private createHashEmbedding(text: string, dimensions: number = 768): Float32Array { const embedding = new Float32Array(dimensions); const normalized = text.toLowerCase().trim(); for (let i = 0; i < dimensions; i++) { let hash = 0; for (let j = 0; j < normalized.length; j++) { hash = ((hash << 5) - hash + normalized.charCodeAt(j) * (i + 1)) | 0; } embedding[i] = (Math.sin(hash) + 1) / 2; } let norm = 0; for (let i = 0; i < dimensions; i++) { norm += embedding[i] * embedding[i]; } norm = Math.sqrt(norm); if (norm > 0) { for (let i = 0; i < dimensions; i++) { embedding[i] /= norm; } } return embedding; } } export default LearningBridge;