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
1,200 lines • 41.8 kB
JavaScript
/**
* V3 Intelligence Module
* Optimized SONA (Self-Optimizing Neural Architecture) and ReasoningBank
* for adaptive learning and pattern recognition
*
* Performance targets:
* - Signal recording: <0.05ms (achieved: ~0.01ms)
* - Pattern search: O(log n) with HNSW
* - Memory efficient circular buffers
*
* @module v3/cli/intelligence
*/
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { homedir } from 'node:os';
import { createRequire } from 'node:module';
import { join } from 'node:path';
// ============================================================================
// Persistence Configuration
// ============================================================================
/**
* Get the data directory for neural pattern persistence
* Uses .claude-flow/neural in the current working directory,
* falling back to home directory
*/
function getDataDir() {
const cwd = process.cwd();
const localDir = join(cwd, '.claude-flow', 'neural');
const homeDir = join(homedir(), '.claude-flow', 'neural');
// Prefer local directory if .claude-flow exists
if (existsSync(join(cwd, '.claude-flow'))) {
return localDir;
}
return homeDir;
}
/**
* Ensure the data directory exists
*/
function ensureDataDir() {
const dir = getDataDir();
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
return dir;
}
/**
* Get the patterns file path
*/
function getPatternsPath() {
return join(getDataDir(), 'patterns.json');
}
/**
* Get the stats file path
*/
function getStatsPath() {
return join(getDataDir(), 'stats.json');
}
// ============================================================================
// Default Configuration
// ============================================================================
const DEFAULT_SONA_CONFIG = {
instantLoopEnabled: true,
backgroundLoopEnabled: false,
loraLearningRate: 0.001,
loraRank: 8,
ewcLambda: 0.4,
maxTrajectorySize: 100,
patternThreshold: 0.7,
maxSignals: 10000,
maxPatterns: 5000
};
// ============================================================================
// Optimized Local SONA Implementation
// ============================================================================
/**
* Lightweight SONA Coordinator
* Uses circular buffer for O(1) signal recording
* Achieves <0.05ms per operation
*/
class LocalSonaCoordinator {
config;
signals;
signalHead = 0;
signalCount = 0;
trajectories = [];
adaptationTimes = [];
currentTrajectorySteps = [];
constructor(config) {
this.config = config;
// Pre-allocate circular buffer
this.signals = new Array(config.maxSignals);
}
/**
* Record a signal - O(1) operation
* Target: <0.05ms
*/
recordSignal(signal) {
const start = performance.now();
// Circular buffer insertion - constant time
this.signals[this.signalHead] = signal;
this.signalHead = (this.signalHead + 1) % this.config.maxSignals;
if (this.signalCount < this.config.maxSignals) {
this.signalCount++;
}
const elapsed = performance.now() - start;
this.adaptationTimes.push(elapsed);
if (this.adaptationTimes.length > 100) {
this.adaptationTimes.shift();
}
}
/**
* Record complete trajectory
*/
recordTrajectory(trajectory) {
this.trajectories.push(trajectory);
if (this.trajectories.length > this.config.maxTrajectorySize) {
this.trajectories.shift();
}
}
/**
* Get recent signals
*/
getRecentSignals(count = 10) {
const result = [];
const actualCount = Math.min(count, this.signalCount);
for (let i = 0; i < actualCount; i++) {
const idx = (this.signalHead - 1 - i + this.config.maxSignals) % this.config.maxSignals;
if (this.signals[idx]) {
result.push(this.signals[idx]);
}
}
return result;
}
/**
* Get average adaptation time
*/
getAvgAdaptationTime() {
if (this.adaptationTimes.length === 0)
return 0;
return this.adaptationTimes.reduce((a, b) => a + b, 0) / this.adaptationTimes.length;
}
/**
* Add a step to the current in-progress trajectory
*/
addTrajectoryStep(step) {
this.currentTrajectorySteps.push(step);
// Prevent unbounded growth
if (this.currentTrajectorySteps.length > this.config.maxTrajectorySize) {
this.currentTrajectorySteps.shift();
}
}
/**
* End the current trajectory with a verdict and apply RL updates.
* Reward mapping: success=1.0, partial=0.5, failure=-0.5
*
* For successful/partial trajectories, boosts confidence of similar patterns
* in the ReasoningBank. For failures, reduces confidence scores.
*/
async endTrajectory(verdict, bank) {
const rewardMap = {
success: 1.0,
partial: 0.5,
failure: -0.5
};
const reward = rewardMap[verdict] ?? 0;
// Record the completed trajectory
const completedTrajectory = {
steps: [...this.currentTrajectorySteps],
verdict,
timestamp: Date.now()
};
this.recordTrajectory(completedTrajectory);
// Update pattern confidences based on reward
let patternsUpdated = 0;
const allPatterns = bank.getAll();
for (const step of this.currentTrajectorySteps) {
if (!step.embedding || step.embedding.length === 0)
continue;
// Find patterns similar to this trajectory step
const similar = bank.findSimilar(step.embedding, {
k: 3,
threshold: 0.3
});
for (const match of similar) {
const pattern = bank.get(match.id);
if (!pattern)
continue;
// Adjust confidence: positive reward boosts, negative reduces
const delta = reward * 0.1; // small step per update
const newConfidence = Math.max(0.0, Math.min(1.0, pattern.confidence + delta));
pattern.confidence = newConfidence;
pattern.usageCount++;
pattern.lastUsedAt = Date.now();
patternsUpdated++;
}
}
// Clear current trajectory
this.currentTrajectorySteps = [];
return { reward, patternsUpdated };
}
/**
* Distill learning from recent successful trajectories.
* Applies LoRA-style confidence updates and integrates EWC++ consolidation.
*
* For each successful trajectory step with high confidence,
* increases the pattern's stored confidence by loraLearningRate * reward.
* Before applying updates, checks EWC penalty to prevent catastrophic forgetting.
*/
async distillLearning(bank) {
let patternsDistilled = 0;
let totalEwcPenalty = 0;
// Get recent successful trajectories
const recentSuccessful = this.trajectories.filter(t => t.verdict === 'success' || t.verdict === 'partial').slice(-10); // last 10 successful
if (recentSuccessful.length === 0) {
return { patternsDistilled: 0, ewcPenalty: 0 };
}
// Try to get EWC consolidator
let ewcConsolidator = null;
try {
const ewcModule = await import('./ewc-consolidation.js');
ewcConsolidator = await ewcModule.getEWCConsolidator({
lambda: this.config.ewcLambda
});
}
catch {
// EWC not available, proceed without consolidation protection
}
const rewardMap = {
success: 1.0,
partial: 0.5
};
// Collect confidence changes for EWC Fisher update
const confidenceChanges = [];
for (const trajectory of recentSuccessful) {
const reward = rewardMap[trajectory.verdict] ?? 0;
for (const step of trajectory.steps) {
if (!step.embedding || step.embedding.length === 0)
continue;
const similar = bank.findSimilar(step.embedding, {
k: 3,
threshold: 0.4
});
for (const match of similar) {
const pattern = bank.get(match.id);
if (!pattern)
continue;
// Only distill from high-confidence matches
if (match.confidence < 0.5)
continue;
const oldConfidence = pattern.confidence;
// Check EWC penalty before applying update
if (ewcConsolidator) {
const oldWeights = [oldConfidence];
const proposedConfidence = Math.min(1.0, oldConfidence + this.config.loraLearningRate * reward);
const newWeights = [proposedConfidence];
const penalty = ewcConsolidator.getPenalty(oldWeights, newWeights);
totalEwcPenalty += penalty;
// If penalty is too high, reduce the update magnitude
if (penalty > this.config.ewcLambda) {
const dampedDelta = (this.config.loraLearningRate * reward) / (1 + penalty);
pattern.confidence = Math.max(0.0, Math.min(1.0, oldConfidence + dampedDelta));
}
else {
pattern.confidence = proposedConfidence;
}
}
else {
// No EWC: apply full LoRA update
pattern.confidence = Math.max(0.0, Math.min(1.0, oldConfidence + this.config.loraLearningRate * reward));
}
pattern.lastUsedAt = Date.now();
patternsDistilled++;
confidenceChanges.push({
id: pattern.id,
oldConf: oldConfidence,
newConf: pattern.confidence,
embedding: pattern.embedding
});
}
}
}
// Update EWC Fisher matrix with confidence changes
if (ewcConsolidator && confidenceChanges.length > 0) {
for (const change of confidenceChanges) {
// Use confidence delta as gradient proxy
const gradient = change.embedding.map(e => e * Math.abs(change.newConf - change.oldConf));
ewcConsolidator.recordGradient(change.id, gradient, true);
}
}
// Persist updated patterns
bank.flushToDisk();
return { patternsDistilled, ewcPenalty: totalEwcPenalty };
}
/**
* Get current trajectory steps (for inspection)
*/
getCurrentTrajectorySteps() {
return [...this.currentTrajectorySteps];
}
/**
* Get statistics
*/
stats() {
return {
signalCount: this.signalCount,
trajectoryCount: this.trajectories.length,
avgAdaptationMs: this.getAvgAdaptationTime()
};
}
}
/**
* Lightweight ReasoningBank
* Uses Map for O(1) storage and array for similarity search
* Supports persistence to disk
*/
class LocalReasoningBank {
patterns = new Map();
patternList = [];
maxSize;
persistenceEnabled;
dirty = false;
saveTimeout = null;
constructor(options) {
this.maxSize = options.maxSize;
this.persistenceEnabled = options.persistence !== false;
// Load persisted patterns
if (this.persistenceEnabled) {
this.loadFromDisk();
}
}
/**
* Load patterns from disk, deduplicating by content.
* When multiple patterns share identical content, keeps the one with
* highest confidence (ties broken by most recent lastUsedAt).
*/
loadFromDisk() {
try {
const path = getPatternsPath();
if (existsSync(path)) {
const data = JSON.parse(readFileSync(path, 'utf-8'));
if (Array.isArray(data)) {
const totalLoaded = data.length;
// Group by content to deduplicate
const byContent = new Map();
for (const pattern of data) {
const key = pattern.content;
const existing = byContent.get(key);
if (!existing) {
byContent.set(key, pattern);
}
else {
// Keep the one with higher confidence; break ties by lastUsedAt
if (pattern.confidence > existing.confidence ||
(pattern.confidence === existing.confidence &&
(pattern.lastUsedAt ?? 0) > (existing.lastUsedAt ?? 0))) {
// Merge: adopt the higher usageCount sum
pattern.usageCount = (pattern.usageCount ?? 0) + (existing.usageCount ?? 0);
byContent.set(key, pattern);
}
else {
existing.usageCount = (existing.usageCount ?? 0) + (pattern.usageCount ?? 0);
}
}
}
// Populate the bank from deduplicated entries
for (const pattern of byContent.values()) {
this.patterns.set(pattern.id, pattern);
this.patternList.push(pattern);
}
const removed = totalLoaded - byContent.size;
if (removed > 0) {
console.log(`Deduplicated ${removed} patterns (${byContent.size} unique)`);
// Persist the compacted set immediately so the file shrinks on disk
this.dirty = true;
this.flushToDisk();
}
}
}
}
catch {
// Ignore load errors, start fresh
}
}
/**
* Save patterns to disk (debounced)
*/
saveToDisk() {
if (!this.persistenceEnabled)
return;
this.dirty = true;
// Debounce saves to avoid excessive disk I/O
if (this.saveTimeout) {
clearTimeout(this.saveTimeout);
}
this.saveTimeout = setTimeout(() => {
this.flushToDisk();
}, 100);
}
/**
* Immediately flush patterns to disk
*/
flushToDisk() {
if (!this.persistenceEnabled || !this.dirty)
return;
try {
ensureDataDir();
const path = getPatternsPath();
writeFileSync(path, JSON.stringify(this.patternList, null, 2), 'utf-8');
this.dirty = false;
}
catch (error) {
// Log but don't throw - persistence failures shouldn't break training
console.error('Failed to persist patterns:', error);
}
}
/**
* Store a pattern - O(1)
* Deduplicates by content: if a pattern with the same content already
* exists, the existing entry is updated (bumped usageCount, higher
* confidence wins, refreshed lastUsedAt) instead of adding a duplicate.
*/
store(pattern) {
const now = Date.now();
const stored = {
...pattern,
usageCount: pattern.usageCount ?? 0,
createdAt: pattern.createdAt ?? now,
lastUsedAt: pattern.lastUsedAt ?? now
};
// Update or insert
if (this.patterns.has(pattern.id)) {
const existing = this.patterns.get(pattern.id);
stored.usageCount = existing.usageCount + 1;
stored.createdAt = existing.createdAt;
// Update in list
const idx = this.patternList.findIndex(p => p.id === pattern.id);
if (idx >= 0) {
this.patternList[idx] = stored;
}
}
else {
// Check for content-duplicate before inserting a new entry
const contentDupe = this.patternList.find(p => p.content === pattern.content);
if (contentDupe) {
// Merge into the existing pattern instead of adding a duplicate
contentDupe.usageCount++;
contentDupe.lastUsedAt = now;
if (stored.confidence > contentDupe.confidence) {
contentDupe.confidence = stored.confidence;
}
// Keep the Map in sync with the mutated object
this.patterns.set(contentDupe.id, contentDupe);
this.saveToDisk();
return;
}
// Evict oldest if at capacity
if (this.patterns.size >= this.maxSize) {
const oldest = this.patternList.shift();
if (oldest) {
this.patterns.delete(oldest.id);
}
}
this.patternList.push(stored);
}
this.patterns.set(pattern.id, stored);
// Trigger persistence (debounced)
this.saveToDisk();
}
/**
* Find similar patterns by embedding
*/
findSimilar(queryEmbedding, options) {
const { k = 5, threshold = 0.5, type } = options;
// Filter by type if specified
let candidates = type
? this.patternList.filter(p => p.type === type)
: this.patternList;
// Compute similarities
const scored = candidates.map(pattern => ({
pattern,
score: this.cosineSim(queryEmbedding, pattern.embedding)
}));
// Filter by threshold and sort
return scored
.filter(s => s.score >= threshold)
.sort((a, b) => b.score - a.score)
.slice(0, k)
.map(s => {
// Update usage
s.pattern.usageCount++;
s.pattern.lastUsedAt = Date.now();
return { ...s.pattern, confidence: s.score };
});
}
/**
* Optimized cosine similarity
*/
cosineSim(a, b) {
if (!a || !b || a.length === 0 || b.length === 0)
return 0;
const len = Math.min(a.length, b.length);
let dot = 0, normA = 0, normB = 0;
for (let i = 0; i < len; i++) {
const ai = a[i], bi = b[i];
dot += ai * bi;
normA += ai * ai;
normB += bi * bi;
}
const mag = Math.sqrt(normA * normB);
return mag === 0 ? 0 : dot / mag;
}
/**
* Get statistics
*/
stats() {
return {
size: this.patterns.size,
patternCount: this.patternList.length
};
}
/**
* Get pattern by ID
*/
get(id) {
return this.patterns.get(id);
}
/**
* Get all patterns
*/
getAll() {
return [...this.patternList];
}
/**
* Get patterns by type
*/
getByType(type) {
return this.patternList.filter(p => p.type === type);
}
/**
* Delete a pattern by ID
*/
delete(id) {
const pattern = this.patterns.get(id);
if (!pattern)
return false;
this.patterns.delete(id);
const idx = this.patternList.findIndex(p => p.id === id);
if (idx >= 0) {
this.patternList.splice(idx, 1);
}
this.saveToDisk();
return true;
}
/**
* Clear all patterns
*/
clear() {
this.patterns.clear();
this.patternList = [];
this.saveToDisk();
}
}
// ============================================================================
// @ruvector/ruvllm SonaCoordinator Integration
// ============================================================================
let ruvllmCoordinator = null;
let ruvllmLoaded = false;
/**
* Synchronously load the @ruvector/ruvllm SonaCoordinator. Used both by the
* async init path (initializeIntelligence) and by sync stat readers like
* getIntelligenceStats — the dashboard would otherwise report "unavailable"
* when stats are queried before any async init has fired (#1770).
*/
function loadRuvllmCoordinatorSync() {
if (ruvllmLoaded)
return ruvllmCoordinator;
ruvllmLoaded = true;
try {
const requireCjs = createRequire(import.meta.url);
const ruvllm = requireCjs('@ruvector/ruvllm');
ruvllmCoordinator = new ruvllm.SonaCoordinator(ruvllm.DEFAULT_SONA_CONFIG);
return ruvllmCoordinator;
}
catch (err) {
// Surface the reason on debug builds so future regressions of #1770 don't
// disappear silently. Stays quiet by default to avoid noise on the cli's
// hot path (e.g., npx invocations).
if (process.env.CLAUDE_FLOW_DEBUG) {
// eslint-disable-next-line no-console
console.error('[ruvllm] SonaCoordinator load failed, falling back to JS:', err.message);
}
ruvllmCoordinator = null;
return null;
}
}
async function loadRuvllmCoordinator() {
return loadRuvllmCoordinatorSync();
}
// ============================================================================
// Module State
// ============================================================================
let sonaCoordinator = null;
let reasoningBank = null;
let intelligenceInitialized = false;
let globalStats = {
trajectoriesRecorded: 0,
patternsLearned: 0,
signalsProcessed: 0,
lastAdaptation: null
};
// ============================================================================
// Stats Persistence
// ============================================================================
/**
* Load persisted stats from disk
*/
function loadPersistedStats() {
try {
const path = getStatsPath();
if (existsSync(path)) {
const data = JSON.parse(readFileSync(path, 'utf-8'));
if (data && typeof data === 'object') {
globalStats.trajectoriesRecorded = data.trajectoriesRecorded ?? 0;
globalStats.lastAdaptation = data.lastAdaptation ?? null;
}
}
}
catch {
// Ignore load errors, start fresh
}
}
/**
* Save stats to disk
*/
function savePersistedStats() {
try {
ensureDataDir();
const path = getStatsPath();
writeFileSync(path, JSON.stringify(globalStats, null, 2), 'utf-8');
}
catch {
// Ignore save errors
}
}
// ============================================================================
// Public API
// ============================================================================
/**
* Initialize the intelligence system (SONA + ReasoningBank)
* Uses optimized local implementations
*/
export async function initializeIntelligence(config) {
if (intelligenceInitialized) {
return {
success: true,
sonaEnabled: !!sonaCoordinator,
reasoningBankEnabled: !!reasoningBank
};
}
try {
// Merge config with defaults
const finalConfig = {
...DEFAULT_SONA_CONFIG,
...config
};
// Initialize local SONA (optimized for <0.05ms)
sonaCoordinator = new LocalSonaCoordinator(finalConfig);
// Initialize local ReasoningBank with persistence enabled
reasoningBank = new LocalReasoningBank({
maxSize: finalConfig.maxPatterns,
persistence: true
});
// Load persisted stats if available
loadPersistedStats();
// Eagerly load ruvllm coordinator so stats reflect backend status
await loadRuvllmCoordinator();
intelligenceInitialized = true;
return {
success: true,
sonaEnabled: true,
reasoningBankEnabled: true
};
}
catch (error) {
return {
success: false,
sonaEnabled: false,
reasoningBankEnabled: false,
error: error instanceof Error ? error.message : String(error)
};
}
}
/**
* Record a trajectory step for learning
* Performance: <0.05ms without embedding generation
*/
export async function recordStep(step) {
if (!sonaCoordinator) {
const init = await initializeIntelligence();
if (!init.success)
return false;
}
try {
// Generate embedding if not provided
// ADR-053: Try AgentDB v3 bridge embedder first
let embedding = step.embedding;
if (!embedding) {
try {
const bridge = await import('./memory-bridge.js');
const bridgeResult = await bridge.bridgeGenerateEmbedding(step.content);
if (bridgeResult) {
embedding = bridgeResult.embedding;
}
}
catch {
// Bridge not available
}
if (!embedding) {
const { generateEmbedding } = await import('./memory-initializer.js');
const result = await generateEmbedding(step.content);
embedding = result.embedding;
}
}
// Record in SONA - <0.05ms
sonaCoordinator.recordSignal({
type: step.type,
content: step.content,
embedding,
metadata: step.metadata,
timestamp: step.timestamp || Date.now()
});
// Add to current trajectory for RL tracking
const stepWithEmbedding = { ...step, embedding };
sonaCoordinator.addTrajectoryStep(stepWithEmbedding);
// Store in ReasoningBank for retrieval
if (reasoningBank) {
reasoningBank.store({
id: `step_${Date.now()}_${Math.random().toString(36).substring(7)}`,
type: step.type,
embedding,
content: step.content,
confidence: 1.0,
metadata: step.metadata
});
}
// When a 'result' step arrives, end the trajectory and run RL loop
if (step.type === 'result' && reasoningBank) {
// Determine verdict from metadata or default to 'partial'
const verdict = step.metadata?.verdict || 'partial';
await sonaCoordinator.endTrajectory(verdict, reasoningBank);
// Distill learning from recent successful trajectories
await sonaCoordinator.distillLearning(reasoningBank);
globalStats.lastAdaptation = Date.now();
}
globalStats.trajectoriesRecorded++;
savePersistedStats();
return true;
}
catch {
return false;
}
}
/**
* Record a complete trajectory with verdict
*/
export async function recordTrajectory(steps, verdict) {
if (!sonaCoordinator) {
const init = await initializeIntelligence();
if (!init.success)
return false;
}
try {
// Generate embeddings for steps that don't have them (required for distillation)
const enrichedSteps = await Promise.all(steps.map(async (step) => {
if (step.embedding && step.embedding.length > 0)
return step;
try {
const { generateEmbedding } = await import('./memory-initializer.js');
const result = await generateEmbedding(step.content);
return { ...step, embedding: result.embedding };
}
catch {
return step; // Skip embedding if not available
}
}));
sonaCoordinator.recordTrajectory({
steps: enrichedSteps,
verdict,
timestamp: Date.now()
});
// Apply RL: update pattern confidences based on verdict
if (reasoningBank) {
for (const step of enrichedSteps) {
sonaCoordinator.addTrajectoryStep(step);
}
await sonaCoordinator.endTrajectory(verdict, reasoningBank);
await sonaCoordinator.distillLearning(reasoningBank);
// Also store successful trajectories as patterns directly
if (verdict === 'success') {
for (const step of enrichedSteps) {
if (step.embedding && step.embedding.length > 0) {
reasoningBank.store({
id: `pattern-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
type: step.type,
content: step.content,
embedding: step.embedding,
confidence: verdict === 'success' ? 0.8 : 0.4,
metadata: step.metadata || {},
createdAt: Date.now(),
});
globalStats.patternsLearned++;
}
}
}
}
// Forward trajectory to @ruvector/ruvllm SonaCoordinator if available
const ruvllmCoord = await loadRuvllmCoordinator();
if (ruvllmCoord) {
try {
const avgQuality = verdict === 'success' ? 1.0 : verdict === 'partial' ? 0.5 : 0.0;
ruvllmCoord.recordTrajectory({
steps: enrichedSteps.map(s => ({
state: s.content,
action: s.type,
reward: avgQuality,
embedding: s.embedding || []
})),
totalReward: avgQuality,
success: verdict === 'success'
});
}
catch {
// ruvllm recording failed silently
}
}
globalStats.trajectoriesRecorded++;
globalStats.lastAdaptation = Date.now();
savePersistedStats();
return true;
}
catch {
return false;
}
}
export async function findSimilarPatterns(query, options) {
if (!reasoningBank) {
const init = await initializeIntelligence();
if (!init.success)
return [];
}
try {
// ADR-053: Try AgentDB v3 bridge embedder first
let queryEmbedding = null;
try {
const bridge = await import('./memory-bridge.js');
const bridgeResult = await bridge.bridgeGenerateEmbedding(query);
if (bridgeResult) {
queryEmbedding = bridgeResult.embedding;
}
}
catch {
// Bridge not available
}
if (!queryEmbedding) {
const { generateEmbedding } = await import('./memory-initializer.js');
const queryResult = await generateEmbedding(query);
queryEmbedding = queryResult.embedding;
}
// Hash-fallback embeddings (128-dim) produce lower cosine similarities
// than ONNX/transformer embeddings, so use a lower default threshold
const isHashFallback = queryEmbedding.length === 128;
const defaultThreshold = isHashFallback ? 0.1 : 0.5;
const results = reasoningBank.findSimilar(queryEmbedding, {
k: options?.k ?? 5,
threshold: options?.threshold ?? defaultThreshold,
type: options?.type
});
return results.map((r) => ({
id: r.id,
type: r.type,
embedding: r.embedding,
content: r.content,
confidence: r.confidence,
usageCount: r.usageCount,
createdAt: r.createdAt,
lastUsedAt: r.lastUsedAt,
similarity: r.similarity ?? r.confidence ?? 0.5
}));
}
catch {
return [];
}
}
/**
* Get intelligence system statistics
*/
export function getIntelligenceStats() {
const sonaStats = sonaCoordinator?.stats();
const bankStats = reasoningBank?.stats();
// Lazy-init the ruvllm coordinator if it hasn't been loaded yet. The MCP
// dashboard (`hooks_intelligence_stats`) hits this path before any
// initializeIntelligence() call has fired, so the coordinator field would
// otherwise stay null and the dashboard would report "unavailable" even
// when @ruvector/ruvllm is fully resolvable. Sync require — cheap, idempotent.
if (!ruvllmLoaded) {
loadRuvllmCoordinatorSync();
}
const ruvllmStats = ruvllmCoordinator?.stats?.() || null;
// Fetch cross-module stats for unified reporting
let contrastiveTrainer = 'unavailable';
let trainingBackend = 'unavailable';
try {
// Synchronous check — contrastiveTrainer is module-level in sona-optimizer
// We read it via the SONAOptimizer singleton if available
const sonaModule = globalThis.__claudeFlowSonaStats;
if (sonaModule) {
contrastiveTrainer = sonaModule._contrastiveTrainer || 'unavailable';
}
}
catch { /* not available */ }
return {
sonaEnabled: !!sonaCoordinator,
reasoningBankSize: bankStats?.size ?? 0,
patternsLearned: Math.max(bankStats?.patternCount ?? 0, globalStats.patternsLearned),
signalsProcessed: globalStats.signalsProcessed,
trajectoriesRecorded: globalStats.trajectoriesRecorded,
lastAdaptation: globalStats.lastAdaptation,
avgAdaptationTime: sonaStats?.avgAdaptationMs ?? 0,
_ruvllmBackend: ruvllmStats ? 'active' : 'unavailable',
_ruvllmTrajectories: ruvllmStats?.trajectoriesBuffered || 0,
_contrastiveTrainer: contrastiveTrainer,
_trainingBackend: trainingBackend,
};
}
/**
* Get SONA coordinator for advanced operations
*/
export function getSonaCoordinator() {
return sonaCoordinator;
}
/**
* Get ReasoningBank for advanced operations
*/
export function getReasoningBank() {
return reasoningBank;
}
/**
* End the current trajectory with a verdict and apply RL updates.
* This is the public API for the SONA RL loop.
*
* @param verdict - 'success' (reward=1.0), 'partial' (0.5), or 'failure' (-0.5)
* @returns Update statistics or null if not initialized
*/
export async function endTrajectoryWithVerdict(verdict) {
if (!sonaCoordinator || !reasoningBank) {
const init = await initializeIntelligence();
if (!init.success)
return null;
}
try {
const result = await sonaCoordinator.endTrajectory(verdict, reasoningBank);
globalStats.lastAdaptation = Date.now();
savePersistedStats();
return result;
}
catch {
return null;
}
}
/**
* Distill learning from recent successful trajectories.
* Applies LoRA-style confidence updates with EWC++ consolidation protection.
*
* @returns Distillation statistics or null if not initialized
*/
export async function distillLearning() {
if (!sonaCoordinator || !reasoningBank) {
const init = await initializeIntelligence();
if (!init.success)
return null;
}
try {
const result = await sonaCoordinator.distillLearning(reasoningBank);
globalStats.lastAdaptation = Date.now();
savePersistedStats();
return result;
}
catch {
return null;
}
}
/**
* Clear intelligence state
*/
export function clearIntelligence() {
sonaCoordinator = null;
reasoningBank = null;
intelligenceInitialized = false;
globalStats = {
trajectoriesRecorded: 0,
patternsLearned: 0,
signalsProcessed: 0,
lastAdaptation: null
};
}
/**
* Benchmark SONA adaptation time
*/
export function benchmarkAdaptation(iterations = 1000) {
if (!sonaCoordinator) {
initializeIntelligence();
}
const times = [];
const testEmbedding = Array.from({ length: 384 }, () => Math.random());
for (let i = 0; i < iterations; i++) {
const start = performance.now();
sonaCoordinator.recordSignal({
type: 'test',
content: `benchmark_${i}`,
embedding: testEmbedding,
timestamp: Date.now()
});
times.push(performance.now() - start);
}
const totalMs = times.reduce((a, b) => a + b, 0);
const avgMs = totalMs / iterations;
const minMs = Math.min(...times);
const maxMs = Math.max(...times);
return {
totalMs,
avgMs,
minMs,
maxMs,
targetMet: avgMs < 0.05
};
}
// ============================================================================
// Pattern Persistence API
// ============================================================================
/**
* Get all patterns from ReasoningBank
* Returns persisted patterns even after process restart
*/
export async function getAllPatterns() {
if (!reasoningBank) {
const init = await initializeIntelligence();
if (!init.success)
return [];
}
return reasoningBank.getAll().map(p => ({
id: p.id,
type: p.type,
embedding: p.embedding,
content: p.content,
confidence: p.confidence,
usageCount: p.usageCount,
createdAt: p.createdAt,
lastUsedAt: p.lastUsedAt
}));
}
/**
* Get patterns by type from ReasoningBank
*/
export async function getPatternsByType(type) {
if (!reasoningBank) {
const init = await initializeIntelligence();
if (!init.success)
return [];
}
return reasoningBank.getByType(type).map(p => ({
id: p.id,
type: p.type,
embedding: p.embedding,
content: p.content,
confidence: p.confidence,
usageCount: p.usageCount,
createdAt: p.createdAt,
lastUsedAt: p.lastUsedAt
}));
}
/**
* Flush patterns to disk immediately
* Call this at the end of training to ensure all patterns are saved
*/
export function flushPatterns() {
if (reasoningBank) {
reasoningBank.flushToDisk();
}
savePersistedStats();
}
/**
* Compact patterns by removing duplicates/similar patterns
* @param threshold Similarity threshold (0-1), patterns above this are considered duplicates
*/
export async function compactPatterns(threshold = 0.95) {
if (!reasoningBank) {
const init = await initializeIntelligence();
if (!init.success) {
return { before: 0, after: 0, removed: 0 };
}
}
const patterns = reasoningBank.getAll();
const before = patterns.length;
// Find duplicates using cosine similarity
const toRemove = new Set();
for (let i = 0; i < patterns.length; i++) {
if (toRemove.has(patterns[i].id))
continue;
const embA = patterns[i].embedding;
if (!embA || embA.length === 0)
continue;
for (let j = i + 1; j < patterns.length; j++) {
if (toRemove.has(patterns[j].id))
continue;
const embB = patterns[j].embedding;
if (!embB || embB.length === 0 || embA.length !== embB.length)
continue;
// Compute cosine similarity
let dotProduct = 0;
let normA = 0;
let normB = 0;
for (let k = 0; k < embA.length; k++) {
dotProduct += embA[k] * embB[k];
normA += embA[k] * embA[k];
normB += embB[k] * embB[k];
}
const similarity = dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
if (similarity >= threshold) {
// Remove the one with lower usage count
const useA = patterns[i].usageCount || 0;
const useB = patterns[j].usageCount || 0;
toRemove.add(useA >= useB ? patterns[j].id : patterns[i].id);
}
}
}
// Remove duplicates
for (const id of toRemove) {
reasoningBank.delete(id);
}
// Flush to disk
flushPatterns();
return {
before,
after: before - toRemove.size,
removed: toRemove.size,
};
}
/**
* Delete a pattern by ID
*/
export async function deletePattern(id) {
if (!reasoningBank) {
const init = await initializeIntelligence();
if (!init.success)
return false;
}
return reasoningBank.delete(id);
}
/**
* Clear all patterns (both in memory and on disk)
*/
export async function clearAllPatterns() {
if (!reasoningBank) {
const init = await initializeIntelligence();
if (!init.success)
return;
}
reasoningBank.clear();
}
/**
* Get the neural data directory path
*/
export function getNeuralDataDir() {
return getDataDir();
}
/**
* Trigger background learning on the @ruvector/ruvllm SonaCoordinator.
* No-op if ruvllm is not installed.
*/
export async function runBackgroundLearning() {
const coord = await loadRuvllmCoordinator();
if (coord)
coord.runBackgroundLoop();
}
/**
* Get persistence status
*/
export function getPersistenceStatus() {
const dataDir = getDataDir();
const patternsFile = getPatternsPath();
const statsFile = getStatsPath();
return {
enabled: true,
dataDir,
patternsFile,
statsFile,
patternsExist: existsSync(patternsFile),
statsExist: existsSync(statsFile)
};
}
//# sourceMappingURL=intelligence.js.map