/** * V3 Swarm Communication Hooks * * Enables agent-to-agent communication, pattern broadcasting, * consensus building, and task handoff coordination. * * @module @claude-flow/hooks/swarm */ import { EventEmitter } from 'node:events'; import { reasoningBank } from '../reasoningbank/index.js'; const DEFAULT_CONFIG = { agentId: `agent_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`, agentName: 'anonymous', messageRetention: 3600000, // 1 hour consensusTimeout: 30000, // 30 seconds autoAcknowledge: true, autoBroadcastPatterns: true, patternBroadcastThreshold: 0.7, }; // ============================================================================ // SwarmCommunication Class // ============================================================================ /** * Swarm Communication Hub * * Manages agent-to-agent communication within the swarm. */ export class SwarmCommunication extends EventEmitter { config; messages = new Map(); broadcasts = new Map(); consensusRequests = new Map(); handoffs = new Map(); agents = new Map(); initialized = false; cleanupTimer; // Metrics metrics = { messagesSent: 0, messagesReceived: 0, patternsBroadcast: 0, consensusInitiated: 0, consensusResolved: 0, handoffsInitiated: 0, handoffsCompleted: 0, }; constructor(config = {}) { super(); this.config = { ...DEFAULT_CONFIG, ...config }; } /** * Initialize swarm communication */ async initialize() { if (this.initialized) return; // Register self in agent registry this.registerAgent({ id: this.config.agentId, name: this.config.agentName, status: 'idle', lastSeen: Date.now(), capabilities: [], patternsShared: 0, handoffsReceived: 0, handoffsCompleted: 0, }); // Start cleanup interval (store reference to clear on shutdown) this.cleanupTimer = setInterval(() => this.cleanup(), 60000); // Listen for pattern storage to auto-broadcast if (this.config.autoBroadcastPatterns) { reasoningBank.on('pattern:stored', async (data) => { const patterns = await reasoningBank.searchPatterns(data.id, 1); if (patterns.length > 0 && patterns[0].pattern.quality >= this.config.patternBroadcastThreshold) { await this.broadcastPattern(patterns[0].pattern); } }); } this.initialized = true; this.emit('initialized', { agentId: this.config.agentId }); } /** * Shutdown swarm communication and cleanup resources */ async shutdown() { if (!this.initialized) return; // Clear cleanup timer to prevent memory leaks if (this.cleanupTimer) { clearInterval(this.cleanupTimer); this.cleanupTimer = undefined; } // Clear all maps this.messages.clear(); this.broadcasts.clear(); this.consensusRequests.clear(); this.handoffs.clear(); this.agents.clear(); this.initialized = false; this.emit('shutdown', { agentId: this.config.agentId }); } // ============================================================================ // Agent-to-Agent Messaging // ============================================================================ /** * Send a message to another agent */ async sendMessage(to, content, options = {}) { await this.ensureInitialized(); const message = { id: `msg_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`, from: this.config.agentId, to, type: options.type || 'context', content, metadata: options.metadata || {}, timestamp: Date.now(), ttl: options.ttl, priority: options.priority || 'normal', }; this.messages.set(message.id, message); this.metrics.messagesSent++; this.emit('message:sent', message); // If target agent exists, trigger delivery event if (to === '*' || this.agents.has(to)) { this.emit('message:delivered', message); } return message; } /** * Get messages for this agent */ getMessages(options = {}) { const now = Date.now(); let messages = Array.from(this.messages.values()) .filter(m => (m.to === this.config.agentId || m.to === '*') && (!m.ttl || m.timestamp + m.ttl > now)); if (options.from) { messages = messages.filter(m => m.from === options.from); } if (options.type) { messages = messages.filter(m => m.type === options.type); } if (options.since !== undefined) { const sinceTime = options.since; messages = messages.filter(m => m.timestamp > sinceTime); } messages.sort((a, b) => { const priorityOrder = { critical: 0, high: 1, normal: 2, low: 3 }; const pDiff = priorityOrder[a.priority] - priorityOrder[b.priority]; return pDiff !== 0 ? pDiff : b.timestamp - a.timestamp; }); if (options.limit) { messages = messages.slice(0, options.limit); } this.metrics.messagesReceived += messages.length; return messages; } /** * Broadcast context to all agents */ async broadcastContext(content, metadata = {}) { return this.sendMessage('*', content, { type: 'context', priority: 'normal', metadata, }); } /** * Query other agents */ async queryAgents(query) { return this.sendMessage('*', query, { type: 'query', priority: 'normal', }); } // ============================================================================ // Pattern Broadcasting // ============================================================================ /** * Broadcast a learned pattern to the swarm */ async broadcastPattern(pattern, targetAgents) { await this.ensureInitialized(); const broadcast = { id: `bc_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`, sourceAgent: this.config.agentId, pattern, broadcastTime: Date.now(), recipients: targetAgents || Array.from(this.agents.keys()), acknowledgments: [], }; this.broadcasts.set(broadcast.id, broadcast); this.metrics.patternsBroadcast++; // Update agent stats const agentState = this.agents.get(this.config.agentId); if (agentState) { agentState.patternsShared++; } // Send as message await this.sendMessage(targetAgents ? targetAgents.join(',') : '*', JSON.stringify({ broadcastId: broadcast.id, strategy: pattern.strategy, domain: pattern.domain, quality: pattern.quality, }), { type: 'pattern', priority: 'normal', metadata: { broadcastId: broadcast.id }, }); this.emit('pattern:broadcast', broadcast); return broadcast; } /** * Acknowledge receipt of a pattern broadcast */ acknowledgeBroadcast(broadcastId) { const broadcast = this.broadcasts.get(broadcastId); if (!broadcast) return false; if (!broadcast.acknowledgments.includes(this.config.agentId)) { broadcast.acknowledgments.push(this.config.agentId); this.emit('pattern:acknowledged', { broadcastId, agentId: this.config.agentId }); } return true; } /** * Get recent pattern broadcasts */ getPatternBroadcasts(options = {}) { let broadcasts = Array.from(this.broadcasts.values()); if (options.since !== undefined) { const sinceTime = options.since; broadcasts = broadcasts.filter(b => b.broadcastTime > sinceTime); } if (options.domain) { broadcasts = broadcasts.filter(b => b.pattern.domain === options.domain); } if (options.minQuality !== undefined) { const minQ = options.minQuality; broadcasts = broadcasts.filter(b => b.pattern.quality >= minQ); } return broadcasts.sort((a, b) => b.broadcastTime - a.broadcastTime); } /** * Import a broadcast pattern into local ReasoningBank */ async importBroadcastPattern(broadcastId) { const broadcast = this.broadcasts.get(broadcastId); if (!broadcast) return false; await reasoningBank.storePattern(broadcast.pattern.strategy, broadcast.pattern.domain, { sourceAgent: broadcast.sourceAgent, broadcastId, imported: true, }); this.acknowledgeBroadcast(broadcastId); return true; } // ============================================================================ // Consensus Guidance // ============================================================================ /** * Initiate a consensus request */ async initiateConsensus(question, options, timeout) { await this.ensureInitialized(); const request = { id: `cons_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`, initiator: this.config.agentId, question, options, votes: new Map(), deadline: Date.now() + (timeout || this.config.consensusTimeout), status: 'pending', }; this.consensusRequests.set(request.id, request); this.metrics.consensusInitiated++; // Broadcast the consensus request await this.sendMessage('*', JSON.stringify({ consensusId: request.id, question, options, deadline: request.deadline, }), { type: 'consensus', priority: 'high', metadata: { consensusId: request.id }, }); // Set timeout for resolution setTimeout(() => this.resolveConsensus(request.id), timeout || this.config.consensusTimeout); this.emit('consensus:initiated', request); return request; } /** * Vote on a consensus request */ voteConsensus(consensusId, vote) { const request = this.consensusRequests.get(consensusId); if (!request || request.status !== 'pending') return false; if (!request.options.includes(vote)) return false; if (Date.now() > request.deadline) return false; request.votes.set(this.config.agentId, vote); this.emit('consensus:voted', { consensusId, agentId: this.config.agentId, vote }); // Check if all known agents have voted const agentCount = this.agents.size; if (request.votes.size >= agentCount) { this.resolveConsensus(consensusId); } return true; } /** * Resolve a consensus request */ resolveConsensus(consensusId) { const request = this.consensusRequests.get(consensusId); if (!request || request.status !== 'pending') return; const voteCounts = new Map(); for (const vote of request.votes.values()) { voteCounts.set(vote, (voteCounts.get(vote) || 0) + 1); } let winner = ''; let maxVotes = 0; for (const [option, count] of voteCounts) { if (count > maxVotes) { maxVotes = count; winner = option; } } const participation = this.agents.size > 0 ? request.votes.size / this.agents.size : 0; const confidence = request.votes.size > 0 ? maxVotes / request.votes.size : 0; request.status = request.votes.size > 0 ? 'resolved' : 'expired'; request.result = { winner, confidence, participation, }; if (request.status === 'resolved') { this.metrics.consensusResolved++; } this.emit('consensus:resolved', request); } /** * Get consensus request by ID */ getConsensus(consensusId) { return this.consensusRequests.get(consensusId); } /** * Get pending consensus requests */ getPendingConsensus() { return Array.from(this.consensusRequests.values()) .filter(r => r.status === 'pending'); } /** * Generate consensus guidance text */ generateConsensusGuidance(consensusId) { const request = this.consensusRequests.get(consensusId); if (!request) return 'Consensus request not found'; const lines = [ `**Consensus: ${request.question}**`, '', `Status: ${request.status.toUpperCase()}`, `Initiator: ${request.initiator}`, '', '**Options**:', ]; for (const option of request.options) { const votes = Array.from(request.votes.entries()) .filter(([_, v]) => v === option) .map(([agent]) => agent); lines.push(`- ${option}: ${votes.length} votes`); } if (request.result) { lines.push(''); lines.push(`**Result**: ${request.result.winner}`); lines.push(`Confidence: ${(request.result.confidence * 100).toFixed(0)}%`); lines.push(`Participation: ${(request.result.participation * 100).toFixed(0)}%`); } return lines.join('\n'); } // ============================================================================ // Task Handoff // ============================================================================ /** * Initiate a task handoff to another agent */ async initiateHandoff(toAgent, taskDescription, context) { await this.ensureInitialized(); const handoff = { id: `ho_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`, taskId: `task_${Date.now()}`, description: taskDescription, fromAgent: this.config.agentId, toAgent, context, status: 'pending', timestamp: Date.now(), }; this.handoffs.set(handoff.id, handoff); this.metrics.handoffsInitiated++; // Send handoff message await this.sendMessage(toAgent, JSON.stringify({ handoffId: handoff.id, description: taskDescription, context, }), { type: 'handoff', priority: 'high', metadata: { handoffId: handoff.id }, }); this.emit('handoff:initiated', handoff); return handoff; } /** * Accept a task handoff */ acceptHandoff(handoffId) { const handoff = this.handoffs.get(handoffId); if (!handoff || handoff.toAgent !== this.config.agentId) return false; if (handoff.status !== 'pending') return false; handoff.status = 'accepted'; // Update agent stats const agentState = this.agents.get(this.config.agentId); if (agentState) { agentState.handoffsReceived++; agentState.currentTask = handoff.description; agentState.status = 'busy'; } this.emit('handoff:accepted', handoff); return true; } /** * Reject a task handoff */ rejectHandoff(handoffId, reason) { const handoff = this.handoffs.get(handoffId); if (!handoff || handoff.toAgent !== this.config.agentId) return false; if (handoff.status !== 'pending') return false; handoff.status = 'rejected'; if (reason) { handoff.context.blockers.push(reason); } this.emit('handoff:rejected', { handoff, reason }); return true; } /** * Complete a task handoff */ completeHandoff(handoffId, result) { const handoff = this.handoffs.get(handoffId); if (!handoff || handoff.toAgent !== this.config.agentId) return false; if (handoff.status !== 'accepted') return false; handoff.status = 'completed'; handoff.completedAt = Date.now(); if (result) { handoff.context = { ...handoff.context, ...result }; } // Update agent stats const agentState = this.agents.get(this.config.agentId); if (agentState) { agentState.handoffsCompleted++; agentState.currentTask = undefined; agentState.status = 'idle'; } this.metrics.handoffsCompleted++; this.emit('handoff:completed', handoff); return true; } /** * Get handoff by ID */ getHandoff(handoffId) { return this.handoffs.get(handoffId); } /** * Get pending handoffs for this agent */ getPendingHandoffs() { return Array.from(this.handoffs.values()) .filter(h => h.toAgent === this.config.agentId && h.status === 'pending'); } /** * Generate handoff context text for Claude */ generateHandoffContext(handoffId) { const handoff = this.handoffs.get(handoffId); if (!handoff) return 'Handoff not found'; const lines = [ `## Task Handoff from ${handoff.fromAgent}`, '', `**Task**: ${handoff.description}`, `**Status**: ${handoff.status.toUpperCase()}`, '', ]; if (handoff.context.filesModified.length > 0) { lines.push('**Files Modified**:'); for (const file of handoff.context.filesModified) { lines.push(`- ${file}`); } lines.push(''); } if (handoff.context.patternsUsed.length > 0) { lines.push('**Patterns Used**:'); for (const pattern of handoff.context.patternsUsed) { lines.push(`- ${pattern}`); } lines.push(''); } if (handoff.context.decisions.length > 0) { lines.push('**Decisions Made**:'); for (const decision of handoff.context.decisions) { lines.push(`- ${decision}`); } lines.push(''); } if (handoff.context.blockers.length > 0) { lines.push('**Blockers**:'); for (const blocker of handoff.context.blockers) { lines.push(`- ⚠️ ${blocker}`); } lines.push(''); } if (handoff.context.nextSteps.length > 0) { lines.push('**Next Steps**:'); for (const step of handoff.context.nextSteps) { lines.push(`- [ ] ${step}`); } } return lines.join('\n'); } // ============================================================================ // Agent Registry // ============================================================================ /** * Register an agent in the swarm */ registerAgent(agent) { this.agents.set(agent.id, agent); this.emit('agent:registered', agent); } /** * Update agent status */ updateAgentStatus(agentId, status) { const agent = this.agents.get(agentId); if (agent) { agent.status = status; agent.lastSeen = Date.now(); this.emit('agent:updated', agent); } } /** * Get all registered agents */ getAgents() { return Array.from(this.agents.values()); } /** * Get agent by ID */ getAgent(agentId) { return this.agents.get(agentId); } // ============================================================================ // Statistics & Utilities // ============================================================================ /** * Get communication statistics */ getStats() { return { agentId: this.config.agentId, agentCount: this.agents.size, metrics: { ...this.metrics }, pendingMessages: this.getMessages({ limit: 1000 }).length, pendingHandoffs: this.getPendingHandoffs().length, pendingConsensus: this.getPendingConsensus().length, }; } /** * Cleanup old messages and data */ cleanup() { const now = Date.now(); const retention = this.config.messageRetention; // Cleanup old messages for (const [id, message] of this.messages) { if (now - message.timestamp > retention) { this.messages.delete(id); } } // Cleanup old broadcasts for (const [id, broadcast] of this.broadcasts) { if (now - broadcast.broadcastTime > retention) { this.broadcasts.delete(id); } } // Cleanup expired consensus requests for (const [id, request] of this.consensusRequests) { if (request.status === 'pending' && now > request.deadline) { this.resolveConsensus(id); } } // Mark offline agents for (const agent of this.agents.values()) { if (now - agent.lastSeen > 300000) { // 5 minutes agent.status = 'offline'; } } } async ensureInitialized() { if (!this.initialized) { await this.initialize(); } } } // ============================================================================ // Exports // ============================================================================ export const swarmComm = new SwarmCommunication(); export { SwarmCommunication as default, }; //# sourceMappingURL=index.js.map