UNPKG

jay-code

Version:

Streamlined AI CLI orchestration engine with mathematical rigor and enterprise-grade reliability

761 lines (649 loc) 23.1 kB
import { EventEmitter } from 'node:events'; import { Logger } from '../core/logger.js'; import { EventBus } from '../core/event-bus.js'; import { generateId } from '../utils/helpers.js'; import { SwarmMonitor } from './swarm-monitor.js'; import type { AdvancedTaskScheduler } from './advanced-scheduler.js'; import { MemoryManager } from '../memory/manager.js'; export interface SwarmAgent { id: string; name: string; type: 'researcher' | 'coder' | 'analyst' | 'coordinator' | 'reviewer'; status: 'idle' | 'busy' | 'failed' | 'completed'; capabilities: string[]; currentTask?: SwarmTask; processId?: number; terminalId?: string; metrics: { tasksCompleted: number; tasksFailed: number; totalDuration: number; lastActivity: Date; }; } export interface SwarmTask { id: string; type: string; description: string; priority: number; dependencies: string[]; assignedTo?: string; status: 'pending' | 'running' | 'completed' | 'failed'; result?: any; error?: string; createdAt: Date; startedAt?: Date; completedAt?: Date; retryCount: number; maxRetries: number; timeout?: number; } export interface SwarmObjective { id: string; description: string; strategy: 'auto' | 'research' | 'development' | 'analysis'; tasks: SwarmTask[]; status: 'planning' | 'executing' | 'completed' | 'failed'; createdAt: Date; completedAt?: Date; } export interface SwarmConfig { maxAgents: number; maxConcurrentTasks: number; taskTimeout: number; enableMonitoring: boolean; enableWorkStealing: boolean; enableCircuitBreaker: boolean; memoryNamespace: string; coordinationStrategy: 'centralized' | 'distributed' | 'hybrid'; backgroundTaskInterval: number; healthCheckInterval: number; maxRetries: number; backoffMultiplier: number; } export class SwarmCoordinator extends EventEmitter { private logger: Logger; private config: SwarmConfig; private agents: Map<string, SwarmAgent>; private objectives: Map<string, SwarmObjective>; private tasks: Map<string, SwarmTask>; private monitor?: SwarmMonitor; private scheduler?: AdvancedTaskScheduler; private memoryManager: MemoryManager; private backgroundWorkers: Map<string, NodeJS.Timeout>; private isRunning: boolean = false; private workStealer?: any; private circuitBreaker?: any; constructor(config: Partial<SwarmConfig> = {}) { super(); this.logger = new Logger('SwarmCoordinator'); this.config = { maxAgents: 10, maxConcurrentTasks: 5, taskTimeout: 300000, // 5 minutes enableMonitoring: true, enableWorkStealing: true, enableCircuitBreaker: true, memoryNamespace: 'swarm', coordinationStrategy: 'hybrid', backgroundTaskInterval: 5000, // 5 seconds healthCheckInterval: 10000, // 10 seconds maxRetries: 3, backoffMultiplier: 2, ...config, }; this.agents = new Map(); this.objectives = new Map(); this.tasks = new Map(); this.backgroundWorkers = new Map(); // Initialize memory manager const eventBus = EventBus.getInstance(); this.memoryManager = new MemoryManager( { backend: 'sqlite', namespace: this.config.memoryNamespace, cacheSizeMB: 50, syncOnExit: true, maxEntries: 10000, ttlMinutes: 60, }, eventBus, this.logger, ); if (this.config.enableMonitoring) { this.monitor = new SwarmMonitor({ updateInterval: 1000, enableAlerts: true, enableHistory: true, }); } this.setupEventHandlers(); } private setupEventHandlers(): void { // Monitor events if (this.monitor) { this.monitor.on('alert', (alert: any) => { this.handleMonitorAlert(alert); }); } // Add custom event handlers for swarm coordination this.on('task:completed', (data: any) => { this.handleTaskCompleted(data.taskId, data.result); }); this.on('task:failed', (data: any) => { this.handleTaskFailed(data.taskId, data.error); }); } async start(): Promise<void> { if (this.isRunning) { this.logger.warn('Swarm coordinator already running'); return; } this.logger.info('Starting swarm coordinator...'); this.isRunning = true; // Start subsystems await this.memoryManager.initialize(); if (this.monitor) { await this.monitor.start(); } // Start background workers this.startBackgroundWorkers(); this.emit('coordinator:started'); } async stop(): Promise<void> { if (!this.isRunning) { return; } this.logger.info('Stopping swarm coordinator...'); this.isRunning = false; // Stop background workers this.stopBackgroundWorkers(); // Stop subsystems await this.scheduler.shutdown(); if (this.monitor) { this.monitor.stop(); } this.emit('coordinator:stopped'); } private startBackgroundWorkers(): void { // Task processor worker const taskProcessor = setInterval(() => { this.processBackgroundTasks(); }, this.config.backgroundTaskInterval); this.backgroundWorkers.set('taskProcessor', taskProcessor); // Health check worker const healthChecker = setInterval(() => { this.performHealthChecks(); }, this.config.healthCheckInterval); this.backgroundWorkers.set('healthChecker', healthChecker); // Work stealing worker if (this.workStealer) { const workStealerWorker = setInterval(() => { this.performWorkStealing(); }, this.config.backgroundTaskInterval); this.backgroundWorkers.set('workStealer', workStealerWorker); } // Memory sync worker const memorySync = setInterval(() => { this.syncMemoryState(); }, this.config.backgroundTaskInterval * 2); this.backgroundWorkers.set('memorySync', memorySync); } private stopBackgroundWorkers(): void { for (const [name, worker] of this.backgroundWorkers) { clearInterval(worker); this.logger.debug(`Stopped background worker: ${name}`); } this.backgroundWorkers.clear(); } async createObjective( description: string, strategy: SwarmObjective['strategy'] = 'auto', ): Promise<string> { const objectiveId = generateId('objective'); const objective: SwarmObjective = { id: objectiveId, description, strategy, tasks: [], status: 'planning', createdAt: new Date(), }; this.objectives.set(objectiveId, objective); this.logger.info(`Created objective: ${objectiveId} - ${description}`); // Decompose objective into tasks const tasks = await this.decomposeObjective(objective); objective.tasks = tasks; // Store in memory await this.memoryManager.store({ id: `objective:${objectiveId}`, agentId: 'swarm-coordinator', type: 'objective', content: JSON.stringify(objective), namespace: this.config.memoryNamespace, timestamp: new Date(), metadata: { type: 'objective', strategy, taskCount: tasks.length, }, }); this.emit('objective:created', objective); return objectiveId; } private async decomposeObjective(objective: SwarmObjective): Promise<SwarmTask[]> { const tasks: SwarmTask[] = []; switch (objective.strategy) { case 'research': tasks.push( this.createTask('research', 'Gather information and research materials', 1), this.createTask('analysis', 'Analyze research findings', 2, ['research']), this.createTask('synthesis', 'Synthesize insights and create report', 3, ['analysis']), ); break; case 'development': tasks.push( this.createTask('planning', 'Plan architecture and design', 1), this.createTask('implementation', 'Implement core functionality', 2, ['planning']), this.createTask('testing', 'Test and validate implementation', 3, ['implementation']), this.createTask('documentation', 'Create documentation', 3, ['implementation']), this.createTask('review', 'Peer review and refinement', 4, ['testing', 'documentation']), ); break; case 'analysis': tasks.push( this.createTask('data-collection', 'Collect and prepare data', 1), this.createTask('analysis', 'Perform detailed analysis', 2, ['data-collection']), this.createTask('visualization', 'Create visualizations', 3, ['analysis']), this.createTask('reporting', 'Generate final report', 4, ['analysis', 'visualization']), ); break; default: // auto // Use AI to decompose based on objective description tasks.push( this.createTask('exploration', 'Explore and understand requirements', 1), this.createTask('planning', 'Create execution plan', 2, ['exploration']), this.createTask('execution', 'Execute main tasks', 3, ['planning']), this.createTask('validation', 'Validate and test results', 4, ['execution']), this.createTask('completion', 'Finalize and document', 5, ['validation']), ); } // Register tasks tasks.forEach((task) => { this.tasks.set(task.id, task); }); return tasks; } private createTask( type: string, description: string, priority: number, dependencies: string[] = [], ): SwarmTask { return { id: generateId('task'), type, description, priority, dependencies, status: 'pending', createdAt: new Date(), retryCount: 0, maxRetries: this.config.maxRetries, timeout: this.config.taskTimeout, }; } async registerAgent( name: string, type: SwarmAgent['type'], capabilities: string[] = [], ): Promise<string> { const agentId = generateId('agent'); const agent: SwarmAgent = { id: agentId, name, type, status: 'idle', capabilities, metrics: { tasksCompleted: 0, tasksFailed: 0, totalDuration: 0, lastActivity: new Date(), }, }; this.agents.set(agentId, agent); if (this.monitor) { this.monitor.registerAgent(agentId, name); } // Register with work stealer if enabled if (this.workStealer) { this.workStealer.registerWorker(agentId, 1); } this.logger.info(`Registered agent: ${name} (${agentId}) - Type: ${type}`); this.emit('agent:registered', agent); return agentId; } async assignTask(taskId: string, agentId: string): Promise<void> { const task = this.tasks.get(taskId); const agent = this.agents.get(agentId); if (!task || !agent) { throw new Error('Task or agent not found'); } if (agent.status !== 'idle') { throw new Error('Agent is not available'); } // Check circuit breaker if (this.circuitBreaker && !this.circuitBreaker.canExecute(agentId)) { throw new Error('Agent circuit breaker is open'); } task.assignedTo = agentId; task.status = 'running'; task.startedAt = new Date(); agent.status = 'busy'; agent.currentTask = task; if (this.monitor) { this.monitor.taskStarted(agentId, taskId, task.description); } this.logger.info(`Assigned task ${taskId} to agent ${agentId}`); this.emit('task:assigned', { task, agent }); // Execute task in background this.executeTask(task, agent); } private async executeTask(task: SwarmTask, agent: SwarmAgent): Promise<void> { try { // Simulate task execution // In real implementation, this would spawn actual Claude instances const result = await this.simulateTaskExecution(task, agent); await this.handleTaskCompleted(task.id, result); } catch (error) { await this.handleTaskFailed(task.id, error); } } private async simulateTaskExecution(task: SwarmTask, agent: SwarmAgent): Promise<any> { // This is where we would actually spawn Claude processes // For now, simulate with timeout return new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('Task timeout')); }, task.timeout || this.config.taskTimeout); // Simulate work setTimeout( () => { clearTimeout(timeout); resolve({ taskId: task.id, agentId: agent.id, result: `Completed ${task.type} task`, timestamp: new Date(), }); }, Math.random() * 5000 + 2000, ); }); } private async handleTaskCompleted(taskId: string, result: any): Promise<void> { const task = this.tasks.get(taskId); if (!task) return; const agent = task.assignedTo ? this.agents.get(task.assignedTo) : null; task.status = 'completed'; task.completedAt = new Date(); task.result = result; if (agent) { agent.status = 'idle'; agent.currentTask = undefined; agent.metrics.tasksCompleted++; agent.metrics.totalDuration += task.completedAt.getTime() - (task.startedAt?.getTime() || 0); agent.metrics.lastActivity = new Date(); if (this.monitor) { this.monitor.taskCompleted(agent.id, taskId); } if (this.circuitBreaker) { this.circuitBreaker.recordSuccess(agent.id); } } // Store result in memory await this.memoryManager.store({ id: `task:${taskId}:result`, agentId: agent?.id || 'unknown', type: 'task-result', content: JSON.stringify(result), namespace: this.config.memoryNamespace, timestamp: new Date(), metadata: { type: 'task-result', taskType: task.type, agentId: agent?.id, }, }); this.logger.info(`Task ${taskId} completed successfully`); this.emit('task:completed', { task, result }); // Check if objective is complete this.checkObjectiveCompletion(task); } private async handleTaskFailed(taskId: string, error: any): Promise<void> { const task = this.tasks.get(taskId); if (!task) return; const agent = task.assignedTo ? this.agents.get(task.assignedTo) : null; task.error = (error instanceof Error ? error.message : String(error)) || String(error); task.retryCount++; if (agent) { agent.status = 'idle'; agent.currentTask = undefined; agent.metrics.tasksFailed++; agent.metrics.lastActivity = new Date(); if (this.monitor) { this.monitor.taskFailed(agent.id, taskId, task.error); } if (this.circuitBreaker) { this.circuitBreaker.recordFailure(agent.id); } } // Retry logic if (task.retryCount < task.maxRetries) { task.status = 'pending'; task.assignedTo = undefined; this.logger.warn(`Task ${taskId} failed, will retry (${task.retryCount}/${task.maxRetries})`); this.emit('task:retry', { task, error }); } else { task.status = 'failed'; task.completedAt = new Date(); this.logger.error(`Task ${taskId} failed after ${task.retryCount} retries`); this.emit('task:failed', { task, error }); } } private checkObjectiveCompletion(completedTask: SwarmTask): void { for (const [objectiveId, objective] of this.objectives) { if (objective.status !== 'executing') continue; const allTasksComplete = objective.tasks.every((task) => { const t = this.tasks.get(task.id); return t && (t.status === 'completed' || t.status === 'failed'); }); if (allTasksComplete) { objective.status = 'completed'; objective.completedAt = new Date(); this.logger.info(`Objective ${objectiveId} completed`); this.emit('objective:completed', objective); } } } private async processBackgroundTasks(): Promise<void> { if (!this.isRunning) return; try { // Process pending tasks const pendingTasks = Array.from(this.tasks.values()).filter( (t) => t.status === 'pending' && this.areDependenciesMet(t), ); // Get available agents const availableAgents = Array.from(this.agents.values()).filter((a) => a.status === 'idle'); // Assign tasks to agents for (const task of pendingTasks) { if (availableAgents.length === 0) break; const agent = this.selectBestAgent(task, availableAgents); if (agent) { try { await this.assignTask(task.id, agent.id); availableAgents.splice(availableAgents.indexOf(agent), 1); } catch (error) { this.logger.error(`Failed to assign task ${task.id}:`, error); } } } } catch (error) { this.logger.error('Error processing background tasks:', error); } } private areDependenciesMet(task: SwarmTask): boolean { return task.dependencies.every((depId) => { const dep = this.tasks.get(depId); return dep && dep.status === 'completed'; }); } private selectBestAgent(task: SwarmTask, availableAgents: SwarmAgent[]): SwarmAgent | null { // Simple selection based on task type and agent capabilities const compatibleAgents = availableAgents.filter((agent) => { // Match task type to agent type if (task.type.includes('research') && agent.type === 'researcher') return true; if (task.type.includes('implement') && agent.type === 'coder') return true; if (task.type.includes('analysis') && agent.type === 'analyst') return true; if (task.type.includes('review') && agent.type === 'reviewer') return true; return agent.type === 'coordinator'; // Coordinator can do any task }); if (compatibleAgents.length === 0) { return availableAgents[0]; // Fallback to any available agent } // Select agent with best performance metrics return compatibleAgents.reduce((best, agent) => { const bestRatio = best.metrics.tasksCompleted / (best.metrics.tasksFailed + 1); const agentRatio = agent.metrics.tasksCompleted / (agent.metrics.tasksFailed + 1); return agentRatio > bestRatio ? agent : best; }); } private async performHealthChecks(): Promise<void> { if (!this.isRunning) return; try { const now = new Date(); for (const [agentId, agent] of this.agents) { // Check for stalled agents if (agent.status === 'busy' && agent.currentTask) { const taskDuration = now.getTime() - (agent.currentTask.startedAt?.getTime() || 0); if (taskDuration > this.config.taskTimeout) { this.logger.warn(`Agent ${agentId} appears stalled on task ${agent.currentTask.id}`); await this.handleTaskFailed(agent.currentTask.id, new Error('Task timeout')); } } // Check agent health const inactivityTime = now.getTime() - agent.metrics.lastActivity.getTime(); if (inactivityTime > this.config.healthCheckInterval * 3) { this.logger.warn( `Agent ${agentId} has been inactive for ${Math.round(inactivityTime / 1000)}s`, ); } } } catch (error) { this.logger.error('Error performing health checks:', error); } } private async performWorkStealing(): Promise<void> { if (!this.isRunning || !this.workStealer) return; try { // Get agent workloads const workloads = new Map<string, number>(); for (const [agentId, agent] of this.agents) { workloads.set(agentId, agent.status === 'busy' ? 1 : 0); } // Update work stealer this.workStealer.updateLoads(workloads); // Check for work stealing opportunities const stealingSuggestions = this.workStealer.suggestWorkStealing(); for (const suggestion of stealingSuggestions) { this.logger.debug(`Work stealing suggestion: ${suggestion.from} -> ${suggestion.to}`); // In a real implementation, we would reassign tasks here } } catch (error) { this.logger.error('Error performing work stealing:', error); } } private async syncMemoryState(): Promise<void> { if (!this.isRunning) return; try { // Sync current state to memory const state = { objectives: Array.from(this.objectives.values()), tasks: Array.from(this.tasks.values()), agents: Array.from(this.agents.values()).map((a) => ({ ...a, currentTask: undefined, // Don't store transient state })), timestamp: new Date(), }; await this.memoryManager.store({ id: 'swarm:state', agentId: 'swarm-coordinator', type: 'swarm-state', content: JSON.stringify(state), namespace: this.config.memoryNamespace, timestamp: new Date(), metadata: { type: 'swarm-state', objectiveCount: state.objectives.length, taskCount: state.tasks.length, agentCount: state.agents.length, }, }); } catch (error) { this.logger.error('Error syncing memory state:', error); } } private handleMonitorAlert(alert: any): void { this.logger.warn(`Monitor alert: ${alert.message}`); this.emit('monitor:alert', alert); } private handleAgentMessage(message: Message): void { this.logger.debug(`Agent message: ${message.type} from ${message.from}`); this.emit('agent:message', message); } // Public API methods async executeObjective(objectiveId: string): Promise<void> { const objective = this.objectives.get(objectiveId); if (!objective) { throw new Error('Objective not found'); } objective.status = 'executing'; this.logger.info(`Executing objective: ${objectiveId}`); this.emit('objective:started', objective); // Tasks will be processed by background workers } getObjectiveStatus(objectiveId: string): SwarmObjective | undefined { return this.objectives.get(objectiveId); } getAgentStatus(agentId: string): SwarmAgent | undefined { return this.agents.get(agentId); } getSwarmStatus(): { objectives: number; tasks: { total: number; pending: number; running: number; completed: number; failed: number }; agents: { total: number; idle: number; busy: number; failed: number }; uptime: number; } { const tasks = Array.from(this.tasks.values()); const agents = Array.from(this.agents.values()); return { objectives: this.objectives.size, tasks: { total: tasks.length, pending: tasks.filter((t) => t.status === 'pending').length, running: tasks.filter((t) => t.status === 'running').length, completed: tasks.filter((t) => t.status === 'completed').length, failed: tasks.filter((t) => t.status === 'failed').length, }, agents: { total: agents.length, idle: agents.filter((a) => a.status === 'idle').length, busy: agents.filter((a) => a.status === 'busy').length, failed: agents.filter((a) => a.status === 'failed').length, }, uptime: this.monitor ? this.monitor.getSummary().uptime : 0, }; } }