/** * Agent Pool Manager * * Manages a pool of subagents for delegation. * Handles lifecycle, scaling, and task assignment. */ import { PoolAgent, AgentPoolConfig, AgentStatus, AgentType, AgentSpawnConfig } from '../core/types'; // ============================================================================ // Default Configuration // ============================================================================ const DEFAULT_AGENT_CONFIGS: Record = { 'fast-responder': { type: 'fast-responder', capabilities: ['quick-analysis', 'simple-tasks', 'status-checks'], maxConcurrent: 10, timeout: 5000, retryAttempts: 1, priority: 1 }, 'explorer': { type: 'explorer', capabilities: ['code-navigation', 'file-search', 'pattern-matching'], maxConcurrent: 4, timeout: 30000, retryAttempts: 2, priority: 2 }, 'researcher': { type: 'researcher', capabilities: ['deep-analysis', 'documentation', 'best-practices'], maxConcurrent: 3, timeout: 60000, retryAttempts: 2, priority: 3 }, 'coder': { type: 'coder', capabilities: ['code-generation', 'implementation', 'modification'], maxConcurrent: 4, timeout: 45000, retryAttempts: 2, priority: 2 }, 'reviewer': { type: 'reviewer', capabilities: ['code-review', 'quality-check', 'security-audit'], maxConcurrent: 3, timeout: 30000, retryAttempts: 2, priority: 2 }, 'planner': { type: 'planner', capabilities: ['architecture', 'planning', 'decomposition'], maxConcurrent: 2, timeout: 60000, retryAttempts: 1, priority: 4 }, 'executor': { type: 'executor', capabilities: ['command-execution', 'file-operations', 'safe-modification'], maxConcurrent: 3, timeout: 30000, retryAttempts: 3, priority: 1 }, 'analyzer': { type: 'analyzer', capabilities: ['code-analysis', 'debugging', 'performance-profiling'], maxConcurrent: 3, timeout: 45000, retryAttempts: 2, priority: 2 } }; const DEFAULT_POOL_CONFIG: AgentPoolConfig = { maxSize: 50, minIdle: 8, // At least 8 agents ready warmUpTimeout: 5000, coolDownPeriod: 10000, scaleUpThreshold: 0.7, // Scale up when 70% are busy scaleDownThreshold: 0.3, // Scale down when only 30% are busy agentConfigs: DEFAULT_AGENT_CONFIGS }; // ============================================================================ // Agent Pool Manager Class // ============================================================================ export class AgentPoolManager { private agents: Map = new Map(); private config: AgentPoolConfig; private taskAssignments: Map = new Map(); // taskId -> agentId private scalingTimer?: NodeJS.Timeout; private isShuttingDown: boolean = false; constructor(config: Partial = {}) { this.config = { ...DEFAULT_POOL_CONFIG, ...config }; this.initializePool(); } // ============================================================================ // Pool Initialization // ============================================================================ private initializePool(): void { // Create initial agents for each type const initialCounts: Record = { 'fast-responder': 4, // More fast responders for quick tasks 'explorer': 2, 'researcher': 1, 'coder': 2, 'reviewer': 1, 'planner': 1, 'executor': 1, 'analyzer': 1 }; for (const [type, count] of Object.entries(initialCounts)) { for (let i = 0; i < count; i++) { this.spawnAgent(type as AgentType); } } // Start scaling monitor this.startScalingMonitor(); } private spawnAgent(type: AgentType): PoolAgent | null { if (this.agents.size >= this.config.maxSize) { return null; } const config = this.config.agentConfigs[type]; if (!config) return null; const agent: PoolAgent = { id: this.generateAgentId(type), type, status: 'idle', capabilities: config.capabilities, completedTasks: 0, averageResponseTime: 0, successRate: 1.0, createdAt: new Date() }; this.agents.set(agent.id, agent); return agent; } private generateAgentId(type: AgentType): string { return `${type}-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`; } // ============================================================================ // Agent Selection // ============================================================================ /** * Get an available agent for a task */ acquireAgent( type: AgentType, requiredCapabilities?: string[], taskId?: string ): PoolAgent | null { // Find best matching idle agent let bestAgent: PoolAgent | null = null; let bestScore = -1; for (const agent of this.agents.values()) { if (agent.type !== type) continue; if (agent.status !== 'idle') continue; // Check capabilities if (requiredCapabilities && requiredCapabilities.length > 0) { const hasAllCapabilities = requiredCapabilities.every( cap => agent.capabilities.includes(cap) ); if (!hasAllCapabilities) continue; } // Score based on performance const score = this.calculateAgentScore(agent); if (score > bestScore) { bestScore = score; bestAgent = agent; } } if (bestAgent) { bestAgent.status = 'busy'; bestAgent.currentTask = taskId; bestAgent.lastUsed = new Date(); if (taskId) { this.taskAssignments.set(taskId, bestAgent.id); } } return bestAgent; } /** * Acquire multiple agents for parallel execution */ acquireAgents( types: AgentType[], taskId?: string ): PoolAgent[] { const acquired: PoolAgent[] = []; for (const type of types) { const agent = this.acquireAgent(type, undefined, taskId); if (agent) { acquired.push(agent); } } return acquired; } /** * Release an agent back to the pool */ releaseAgent(agentId: string, result?: { success: boolean; responseTime: number; }): void { const agent = this.agents.get(agentId); if (!agent) return; agent.status = 'cooling-down'; agent.currentTask = undefined; // Update stats if (result) { agent.completedTasks++; agent.successRate = this.updateRunningAverage( agent.successRate, result.success ? 1 : 0, agent.completedTasks ); agent.averageResponseTime = this.updateRunningAverage( agent.averageResponseTime, result.responseTime, agent.completedTasks ); } // Remove task assignment for (const [taskId, aId] of this.taskAssignments) { if (aId === agentId) { this.taskAssignments.delete(taskId); break; } } // Schedule return to idle setTimeout(() => { if (this.agents.has(agentId) && !this.isShuttingDown) { const a = this.agents.get(agentId); if (a && a.status === 'cooling-down') { a.status = 'idle'; } } }, this.config.coolDownPeriod); } private calculateAgentScore(agent: PoolAgent): number { // Higher success rate = better // Lower average response time = better // More completed tasks = more reliable const reliabilityScore = agent.successRate * 0.4; const speedScore = (1 - Math.min(agent.averageResponseTime / 60000, 1)) * 0.3; const experienceScore = Math.min(agent.completedTasks / 100, 1) * 0.3; return reliabilityScore + speedScore + experienceScore; } private updateRunningAverage(current: number, newValue: number, count: number): number { return current + (newValue - current) / count; } // ============================================================================ // Pool Scaling // ============================================================================ private startScalingMonitor(): void { this.scalingTimer = setInterval(() => { this.checkScaling(); }, 5000); } private checkScaling(): void { const stats = this.getPoolStats(); // Scale up if needed if (stats.busyPercentage > this.config.scaleUpThreshold) { this.scaleUp(); } // Scale down if needed if (stats.busyPercentage < this.config.scaleDownThreshold && stats.idleCount > this.config.minIdle * 2) { this.scaleDown(); } } private scaleUp(): void { // Find the type with most demand const demandByType = this.getDemandByType(); let highestDemand: { type: AgentType; ratio: number } | null = null; for (const [type, demand] of Object.entries(demandByType)) { if (!highestDemand || demand.ratio > highestDemand.ratio) { highestDemand = { type: type as AgentType, ratio: demand.ratio }; } } if (highestDemand && highestDemand.ratio > 0.5) { this.spawnAgent(highestDemand.type); } } private scaleDown(): void { // Find idle agents that haven't been used recently const now = Date.now(); const maxIdleTime = 5 * 60 * 1000; // 5 minutes for (const [id, agent] of this.agents) { if (agent.status !== 'idle') continue; if (!agent.lastUsed) continue; if (now - agent.lastUsed.getTime() > maxIdleTime) { // Don't remove if it would go below minimum if (this.getPoolStats().idleCount > this.config.minIdle) { this.agents.delete(id); break; // Only remove one at a time } } } } private getDemandByType(): Record { const counts: Record = {}; for (const agent of this.agents.values()) { if (!counts[agent.type]) { counts[agent.type] = { busy: 0, idle: 0 }; } if (agent.status === 'busy') { counts[agent.type].busy++; } else if (agent.status === 'idle') { counts[agent.type].idle++; } } const result: Record = {}; for (const [type, count] of Object.entries(counts)) { const total = count.busy + count.idle; result[type] = { ...count, ratio: total > 0 ? count.busy / total : 0 }; } return result; } // ============================================================================ // Pool Statistics // ============================================================================ getPoolStats(): { total: number; idleCount: number; busyCount: number; coolingDownCount: number; busyPercentage: number; availablePercentage: number; byType: Record; } { let idleCount = 0; let busyCount = 0; let coolingDownCount = 0; const byType: Record = {} as any; for (const agent of this.agents.values()) { // Initialize type stats if (!byType[agent.type]) { byType[agent.type] = { total: 0, idle: 0, busy: 0 }; } byType[agent.type].total++; switch (agent.status) { case 'idle': idleCount++; byType[agent.type].idle++; break; case 'busy': busyCount++; byType[agent.type].busy++; break; case 'cooling-down': coolingDownCount++; break; } } const total = this.agents.size; return { total, idleCount, busyCount, coolingDownCount, busyPercentage: total > 0 ? busyCount / total : 0, availablePercentage: total > 0 ? (idleCount + coolingDownCount) / total : 0, byType }; } /** * Check if pool has available agents */ hasAvailableAgent(type: AgentType): boolean { for (const agent of this.agents.values()) { if (agent.type === type && agent.status === 'idle') { return true; } } return false; } /** * Get available agent count by type */ getAvailableCount(type: AgentType): number { let count = 0; for (const agent of this.agents.values()) { if (agent.type === type && agent.status === 'idle') { count++; } } return count; } // ============================================================================ // Task Tracking // ============================================================================ getAgentByTask(taskId: string): PoolAgent | undefined { const agentId = this.taskAssignments.get(taskId); if (agentId) { return this.agents.get(agentId); } return undefined; } // ============================================================================ // Lifecycle // ============================================================================ /** * Shutdown the pool */ async shutdown(): Promise { this.isShuttingDown = true; if (this.scalingTimer) { clearInterval(this.scalingTimer); } // Wait for busy agents to complete (with timeout) const maxWait = 30000; const startTime = Date.now(); while (Date.now() - startTime < maxWait) { const stats = this.getPoolStats(); if (stats.busyCount === 0) break; await this.sleep(1000); } this.agents.clear(); this.taskAssignments.clear(); } private sleep(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Get all agents (for debugging/monitoring) */ getAllAgents(): PoolAgent[] { return Array.from(this.agents.values()); } } // ============================================================================ // Factory Function // ============================================================================ export function createAgentPoolManager(config?: Partial): AgentPoolManager { return new AgentPoolManager(config); } // ============================================================================ // Export // ============================================================================ export default AgentPoolManager;