UNPKG

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

365 lines • 13.5 kB
/** * V3 Agent Pool * Manages agent lifecycle, pooling, and auto-scaling */ import { EventEmitter } from 'events'; import { SWARM_CONSTANTS, } from './types.js'; export class AgentPool extends EventEmitter { config; pooledAgents = new Map(); available = new Set(); busy = new Set(); pendingScale = 0; lastScaleOperation; healthCheckInterval; agentCounter = 0; constructor(config = {}) { super(); this.config = { name: config.name ?? 'default-pool', type: config.type ?? 'worker', minSize: config.minSize ?? 1, maxSize: config.maxSize ?? 10, scaleUpThreshold: config.scaleUpThreshold ?? 0.8, scaleDownThreshold: config.scaleDownThreshold ?? 0.2, cooldownMs: config.cooldownMs ?? 30000, healthCheckIntervalMs: config.healthCheckIntervalMs ?? SWARM_CONSTANTS.DEFAULT_HEALTH_CHECK_INTERVAL_MS, }; } async initialize(config) { if (config) { this.config = { ...this.config, ...config }; } // Start with minimum pool size - create agents in parallel const createPromises = Array.from({ length: this.config.minSize }, () => this.createPooledAgent()); await Promise.all(createPromises); // Start health checks this.startHealthChecks(); this.emit('initialized', { poolName: this.config.name, size: this.pooledAgents.size }); } async shutdown() { if (this.healthCheckInterval) { clearInterval(this.healthCheckInterval); this.healthCheckInterval = undefined; } // Mark all agents as terminated for (const pooled of this.pooledAgents.values()) { pooled.agent.status = 'terminated'; } this.pooledAgents.clear(); this.available.clear(); this.busy.clear(); this.emit('shutdown'); } async acquire() { // Try to get an available agent const availableId = this.available.values().next().value; if (availableId) { const pooled = this.pooledAgents.get(availableId); if (pooled) { this.available.delete(availableId); this.busy.add(availableId); pooled.acquiredAt = new Date(); pooled.usageCount++; pooled.agent.status = 'busy'; this.emit('agent.acquired', { agentId: availableId }); // Check if we need to scale up await this.checkScaling(); return pooled.agent; } } // No available agents, try to scale up if (this.pooledAgents.size < this.config.maxSize) { const agent = await this.createPooledAgent(); if (agent) { const pooled = this.pooledAgents.get(agent.id.id); this.available.delete(agent.id.id); this.busy.add(agent.id.id); pooled.acquiredAt = new Date(); pooled.usageCount++; agent.status = 'busy'; this.emit('agent.acquired', { agentId: agent.id.id }); return agent; } } // Pool exhausted this.emit('pool.exhausted'); return undefined; } async release(agentId) { const pooled = this.pooledAgents.get(agentId); if (!pooled) { return; } this.busy.delete(agentId); this.available.add(agentId); pooled.acquiredAt = undefined; pooled.lastUsed = new Date(); pooled.agent.status = 'idle'; pooled.agent.currentTask = undefined; this.emit('agent.released', { agentId }); // Check if we need to scale down await this.checkScaling(); } async add(agent) { if (this.pooledAgents.size >= this.config.maxSize) { throw new Error(`Pool ${this.config.name} is at maximum capacity`); } const pooled = { agent, lastUsed: new Date(), usageCount: 0, }; this.pooledAgents.set(agent.id.id, pooled); this.available.add(agent.id.id); this.emit('agent.added', { agentId: agent.id.id }); } async remove(agentId) { const pooled = this.pooledAgents.get(agentId); if (!pooled) { return; } this.pooledAgents.delete(agentId); this.available.delete(agentId); this.busy.delete(agentId); pooled.agent.status = 'terminated'; this.emit('agent.removed', { agentId }); } async scale(delta) { const now = new Date(); // Check cooldown if (this.lastScaleOperation) { const timeSinceLastScale = now.getTime() - this.lastScaleOperation.getTime(); if (timeSinceLastScale < this.config.cooldownMs) { return; } } if (delta > 0) { // Scale up - create agents in parallel const targetSize = Math.min(this.pooledAgents.size + delta, this.config.maxSize); const toCreate = targetSize - this.pooledAgents.size; const createPromises = Array.from({ length: toCreate }, () => this.createPooledAgent()); await Promise.all(createPromises); this.emit('pool.scaled_up', { added: toCreate }); } else if (delta < 0) { // Scale down - remove agents in parallel const targetSize = Math.max(this.pooledAgents.size + delta, this.config.minSize); const toRemove = this.pooledAgents.size - targetSize; // Remove least recently used available agents const sortedAvailable = Array.from(this.available) .map(id => this.pooledAgents.get(id)) .filter(p => p !== undefined) .sort((a, b) => a.lastUsed.getTime() - b.lastUsed.getTime()); const agentsToRemove = sortedAvailable.slice(0, toRemove); await Promise.all(agentsToRemove.map(pooled => this.remove(pooled.agent.id.id))); this.emit('pool.scaled_down', { removed: agentsToRemove.length }); } this.lastScaleOperation = now; } getState() { return { id: `pool_${this.config.name}`, config: { ...this.config }, agents: new Map(Array.from(this.pooledAgents.entries()).map(([id, pooled]) => [id, pooled.agent])), availableAgents: Array.from(this.available), busyAgents: Array.from(this.busy), pendingScale: this.pendingScale, lastScaleOperation: this.lastScaleOperation, }; } getAvailableCount() { return this.available.size; } getBusyCount() { return this.busy.size; } getTotalCount() { return this.pooledAgents.size; } getUtilization() { if (this.pooledAgents.size === 0) return 0; return this.busy.size / this.pooledAgents.size; } // ===== PRIVATE METHODS ===== async createPooledAgent() { if (this.pooledAgents.size >= this.config.maxSize) { return undefined; } this.agentCounter++; const agentId = `${this.config.name}_agent_${this.agentCounter}`; const agent = { id: { id: agentId, swarmId: 'pool', type: this.config.type, instance: this.agentCounter, }, name: `${this.config.name}-${this.agentCounter}`, type: this.config.type, status: 'idle', capabilities: this.createDefaultCapabilities(), metrics: this.createDefaultMetrics(), workload: 0, health: 1.0, lastHeartbeat: new Date(), connections: [], }; const pooled = { agent, lastUsed: new Date(), usageCount: 0, }; this.pooledAgents.set(agentId, pooled); this.available.add(agentId); this.emit('agent.created', { agentId }); return agent; } createDefaultCapabilities() { return { codeGeneration: true, codeReview: true, testing: true, documentation: true, research: true, analysis: true, coordination: this.config.type === 'coordinator', languages: ['typescript', 'javascript', 'python'], frameworks: ['node', 'deno', 'react'], domains: ['development', 'testing', 'analysis'], tools: ['git', 'npm', 'editor'], maxConcurrentTasks: 3, maxMemoryUsage: 512 * 1024 * 1024, maxExecutionTime: SWARM_CONSTANTS.DEFAULT_TASK_TIMEOUT_MS, reliability: 0.95, speed: 1.0, quality: 0.9, }; } createDefaultMetrics() { return { tasksCompleted: 0, tasksFailed: 0, averageExecutionTime: 0, successRate: 1.0, cpuUsage: 0, memoryUsage: 0, messagesProcessed: 0, lastActivity: new Date(), responseTime: 0, health: 1.0, }; } async checkScaling() { const utilization = this.getUtilization(); if (utilization >= this.config.scaleUpThreshold && this.pooledAgents.size < this.config.maxSize) { // Scale up by 1 this.pendingScale = 1; await this.scale(1); this.pendingScale = 0; } else if (utilization <= this.config.scaleDownThreshold && this.pooledAgents.size > this.config.minSize) { // Scale down by 1 this.pendingScale = -1; await this.scale(-1); this.pendingScale = 0; } } startHealthChecks() { this.healthCheckInterval = setInterval(() => { this.performHealthChecks(); }, this.config.healthCheckIntervalMs); } performHealthChecks() { const now = new Date(); const unhealthyThresholdMs = this.config.healthCheckIntervalMs * 3; for (const [agentId, pooled] of this.pooledAgents) { const timeSinceLastActivity = now.getTime() - pooled.agent.lastHeartbeat.getTime(); if (timeSinceLastActivity > unhealthyThresholdMs) { // Agent is unhealthy pooled.agent.health = Math.max(0, pooled.agent.health - 0.2); pooled.agent.status = 'error'; this.emit('agent.unhealthy', { agentId, health: pooled.agent.health }); // If completely unhealthy, remove and replace if (pooled.agent.health <= 0) { this.replaceUnhealthyAgent(agentId); } } else { // Update health positively pooled.agent.health = Math.min(1.0, pooled.agent.health + 0.1); pooled.agent.lastHeartbeat = now; } } } async replaceUnhealthyAgent(agentId) { const pooled = this.pooledAgents.get(agentId); if (!pooled) return; const wasBusy = this.busy.has(agentId); await this.remove(agentId); // Create replacement if below min size or was busy if (this.pooledAgents.size < this.config.minSize || wasBusy) { await this.createPooledAgent(); } this.emit('agent.replaced', { oldAgentId: agentId }); } // ===== UTILITY METHODS ===== getAgent(agentId) { return this.pooledAgents.get(agentId)?.agent; } getAllAgents() { return Array.from(this.pooledAgents.values()).map(p => p.agent); } getAvailableAgents() { return Array.from(this.available) .map(id => this.pooledAgents.get(id)?.agent) .filter((a) => a !== undefined); } getBusyAgents() { return Array.from(this.busy) .map(id => this.pooledAgents.get(id)?.agent) .filter((a) => a !== undefined); } updateAgentHeartbeat(agentId) { const pooled = this.pooledAgents.get(agentId); if (pooled) { pooled.agent.lastHeartbeat = new Date(); pooled.agent.health = Math.min(1.0, pooled.agent.health + 0.05); } } updateAgentMetrics(agentId, metrics) { const pooled = this.pooledAgents.get(agentId); if (pooled) { pooled.agent.metrics = { ...pooled.agent.metrics, ...metrics }; pooled.agent.lastHeartbeat = new Date(); } } getPoolStats() { const agents = Array.from(this.pooledAgents.values()); const avgHealth = agents.length > 0 ? agents.reduce((sum, p) => sum + p.agent.health, 0) / agents.length : 1.0; const avgUsageCount = agents.length > 0 ? agents.reduce((sum, p) => sum + p.usageCount, 0) / agents.length : 0; return { total: this.pooledAgents.size, available: this.available.size, busy: this.busy.size, utilization: this.getUtilization(), avgHealth, avgUsageCount, }; } } export function createAgentPool(config) { return new AgentPool(config); } //# sourceMappingURL=agent-pool.js.map