Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
371
vendor/ruvector/.claude/intelligence/swarm.js
vendored
Normal file
371
vendor/ruvector/.claude/intelligence/swarm.js
vendored
Normal file
@@ -0,0 +1,371 @@
|
||||
/**
|
||||
* 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;
|
||||
Reference in New Issue
Block a user