/** * Swarm Optimization Module * * Implements hive-mind coordination patterns inspired by: * - ruvector-mincut: Graph partitioning for optimal agent allocation * - Collective intelligence: Emergent behavior from local interactions * - Self-healing networks: Dynamic reconfiguration on failure */ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const DATA_DIR = join(__dirname, 'data'); const SWARM_STATE_FILE = join(DATA_DIR, 'swarm-state.json'); const COORDINATION_FILE = join(DATA_DIR, 'coordination-graph.json'); if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true }); /** * Agent coordination graph - models relationships between agents * Edges represent coordination strength (higher = more interaction needed) */ class CoordinationGraph { constructor() { this.nodes = new Map(); // agentId -> { type, capabilities, load } this.edges = new Map(); // "src:dst" -> { weight, interactions } this.load(); } load() { if (existsSync(COORDINATION_FILE)) { try { const data = JSON.parse(readFileSync(COORDINATION_FILE, 'utf-8')); this.nodes = new Map(Object.entries(data.nodes || {})); this.edges = new Map(Object.entries(data.edges || {})); } catch { /* fresh start */ } } } save() { const data = { nodes: Object.fromEntries(this.nodes), edges: Object.fromEntries(this.edges), lastUpdated: new Date().toISOString() }; writeFileSync(COORDINATION_FILE, JSON.stringify(data, null, 2)); } addAgent(id, type, capabilities = []) { this.nodes.set(id, { type, capabilities, load: 0, active: true }); this.save(); } removeAgent(id) { this.nodes.delete(id); // Remove all edges involving this agent for (const key of this.edges.keys()) { if (key.startsWith(id + ':') || key.endsWith(':' + id)) { this.edges.delete(key); } } this.save(); } recordInteraction(srcId, dstId, weight = 1) { const key = `${srcId}:${dstId}`; const edge = this.edges.get(key) || { weight: 0, interactions: 0 }; edge.weight += weight; edge.interactions++; this.edges.set(key, edge); this.save(); } /** * Find the minimum cut between agent groups * Uses a simplified Karger-like approach for demonstration * In production, this would use ruvector-mincut's subpolynomial algorithm */ findMinCut() { if (this.nodes.size < 2) return { cut: 0, groups: [[...this.nodes.keys()], []] }; const nodes = [...this.nodes.keys()]; const edges = [...this.edges.entries()].map(([key, val]) => { const [src, dst] = key.split(':'); return { src, dst, weight: val.weight }; }); // Simple greedy cut: separate high-load agents const loads = nodes.map(id => ({ id, load: this.nodes.get(id)?.load || 0 })).sort((a, b) => b.load - a.load); const midpoint = Math.ceil(nodes.length / 2); const groupA = loads.slice(0, midpoint).map(n => n.id); const groupB = loads.slice(midpoint).map(n => n.id); // Calculate cut weight let cutWeight = 0; for (const edge of edges) { const srcInA = groupA.includes(edge.src); const dstInA = groupA.includes(edge.dst); if (srcInA !== dstInA) { cutWeight += edge.weight; } } return { cut: cutWeight, groups: [groupA, groupB] }; } /** * Find critical agents (high betweenness centrality approximation) */ findCriticalAgents() { const centrality = new Map(); for (const nodeId of this.nodes.keys()) { let score = 0; for (const [key, edge] of this.edges.entries()) { if (key.includes(nodeId)) { score += edge.weight * edge.interactions; } } centrality.set(nodeId, score); } return [...centrality.entries()] .sort((a, b) => b[1] - a[1]) .slice(0, 5) .map(([id, score]) => ({ id, score, ...this.nodes.get(id) })); } /** * Recommend optimal agent for a task based on graph structure */ recommendAgent(taskType, requiredCapabilities = []) { const candidates = []; for (const [id, node] of this.nodes.entries()) { if (!node.active) continue; // Score based on capabilities match let score = 0; for (const cap of requiredCapabilities) { if (node.capabilities?.includes(cap)) score += 10; } // Prefer agents with lower load score -= node.load * 0.5; // Prefer agents with more connections (more coordination experience) for (const key of this.edges.keys()) { if (key.includes(id)) score += 0.1; } candidates.push({ id, score, ...node }); } return candidates.sort((a, b) => b.score - a.score); } getStats() { const activeAgents = [...this.nodes.values()].filter(n => n.active).length; const totalEdges = this.edges.size; const avgWeight = totalEdges > 0 ? [...this.edges.values()].reduce((sum, e) => sum + e.weight, 0) / totalEdges : 0; return { agents: this.nodes.size, activeAgents, edges: totalEdges, avgEdgeWeight: avgWeight.toFixed(2), criticalAgents: this.findCriticalAgents().slice(0, 3) }; } } /** * Hive Mind Coordinator - emergent collective intelligence */ class HiveMind { constructor(graph) { this.graph = graph; this.consensus = new Map(); // decision -> votes this.history = []; } /** * Propose a decision to the hive mind */ propose(decision, agentId, confidence = 0.5) { const key = this.decisionKey(decision); const votes = this.consensus.get(key) || { yes: 0, no: 0, voters: [] }; if (!votes.voters.includes(agentId)) { votes.voters.push(agentId); if (confidence > 0.5) { votes.yes += confidence; } else { votes.no += (1 - confidence); } this.consensus.set(key, votes); } return this.getConsensus(decision); } /** * Get current consensus on a decision */ getConsensus(decision) { const key = this.decisionKey(decision); const votes = this.consensus.get(key) || { yes: 0, no: 0, voters: [] }; const total = votes.yes + votes.no; if (total === 0) return { approved: null, confidence: 0, voters: 0 }; const approval = votes.yes / total; return { approved: approval > 0.5, confidence: Math.abs(approval - 0.5) * 2, // 0-1 scale approval: approval.toFixed(3), voters: votes.voters.length }; } /** * Self-healing: redistribute load when agent fails */ healPartition(failedAgentId) { const node = this.graph.nodes.get(failedAgentId); if (!node) return { healed: false, reason: 'Agent not found' }; // Mark as inactive node.active = false; this.graph.nodes.set(failedAgentId, node); // Find replacement candidates const candidates = this.graph.recommendAgent(node.type, node.capabilities); const activeCandidate = candidates.find(c => c.id !== failedAgentId); if (activeCandidate) { // Redistribute load const redistributed = Math.floor(node.load / Math.max(1, candidates.length - 1)); for (const candidate of candidates.filter(c => c.id !== failedAgentId)) { const candNode = this.graph.nodes.get(candidate.id); if (candNode) { candNode.load += redistributed; this.graph.nodes.set(candidate.id, candNode); } } this.graph.save(); return { healed: true, failedAgent: failedAgentId, replacedBy: activeCandidate.id, loadRedistributed: redistributed * (candidates.length - 1) }; } return { healed: false, reason: 'No suitable replacement found' }; } decisionKey(decision) { if (typeof decision === 'string') return decision; return JSON.stringify(decision); } } /** * Swarm Optimizer - coordinates multiple agents efficiently */ class SwarmOptimizer { constructor() { this.graph = new CoordinationGraph(); this.hiveMind = new HiveMind(this.graph); this.loadState(); } loadState() { if (existsSync(SWARM_STATE_FILE)) { try { this.state = JSON.parse(readFileSync(SWARM_STATE_FILE, 'utf-8')); } catch { this.state = { tasks: [], optimizations: 0 }; } } else { this.state = { tasks: [], optimizations: 0 }; } } saveState() { this.state.lastUpdated = new Date().toISOString(); writeFileSync(SWARM_STATE_FILE, JSON.stringify(this.state, null, 2)); } /** * Register an agent in the swarm */ registerAgent(id, type, capabilities = []) { this.graph.addAgent(id, type, capabilities); return { registered: true, id, type }; } /** * Record coordination between agents */ recordCoordination(srcAgent, dstAgent, weight = 1) { this.graph.recordInteraction(srcAgent, dstAgent, weight); return { recorded: true, edge: `${srcAgent} -> ${dstAgent}` }; } /** * Get optimal task distribution across agents */ optimizeTaskDistribution(tasks) { const { cut, groups } = this.graph.findMinCut(); const distribution = { groups: [], cut, optimizations: ++this.state.optimizations }; for (let i = 0; i < groups.length; i++) { const groupTasks = tasks.filter((_, idx) => idx % groups.length === i); distribution.groups.push({ agents: groups[i], tasks: groupTasks, load: groupTasks.length }); } this.saveState(); return distribution; } /** * Get best agent recommendation for a task */ recommendForTask(taskType, capabilities = []) { const candidates = this.graph.recommendAgent(taskType, capabilities); return { recommended: candidates[0] || null, alternatives: candidates.slice(1, 4), reason: candidates[0] ? `Best match for ${taskType} with ${candidates[0].score.toFixed(1)} score` : 'No suitable agents found' }; } /** * Handle agent failure with self-healing */ handleFailure(agentId) { return this.hiveMind.healPartition(agentId); } /** * Get swarm statistics */ getStats() { return { graph: this.graph.getStats(), optimizations: this.state.optimizations, lastUpdated: this.state.lastUpdated }; } } export { SwarmOptimizer, CoordinationGraph, HiveMind }; export default SwarmOptimizer;