/** * 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'; const DEFAULT_CONFIG = { 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 { neural = null; backend; config; activeTrajectories = new Map(); stats = { totalTrajectories: 0, completedTrajectories: 0, totalConsolidations: 0, totalDecays: 0, confidenceBoosts: 0, totalBoostAmount: 0, }; destroyed = false; neuralInitPromise = null; constructor(backend, config) { 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, entryId) { 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) { if (!this.config.enabled || this.destroyed) return; const entry = await this.backend.get(entryId); if (!entry) return; const currentConf = entry.metadata?.confidence ?? 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() { const startTime = Date.now(); const earlyResult = { 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 = []; 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 = { 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) { if (!this.config.enabled || this.destroyed) return 0; let entries; 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 ?? 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, k = 5) { 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) => ({ 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() { 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() { 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. */ async initNeural() { if (this.neural) return; if (this.neuralInitPromise) { await this.neuralInitPromise; return; } this.neuralInitPromise = this.loadNeural(); await this.neuralInitPromise; } async loadNeural() { 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'); 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. */ createHashEmbedding(text, dimensions = 768) { 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; //# sourceMappingURL=learning-bridge.js.map