Add Delegation System with 3rd Party AI Tool Integration
NEW: Delegation System (v1.2.0) - Request Classifier for fast request analysis (<50ms) - Agent Pool Manager with auto-scaling (8 agent types) - Delegation Engine with 4 strategies (full, parallel, hierarchical, hybrid) - Progress Streamer for real-time updates - Context Handoff Protocol for inter-agent communication - Quality Gate with confidence thresholds and auto-escalation NEW: 3rd Party Integration Adapters - OpenClaw adapter with parallel execution support - Claude Code CLI adapter with tool registration - Generic adapter for custom integrations - Standardized IntegrationAdapter interface Agent Types Added: - fast-responder (quick answers < 2s) - explorer (code navigation) - researcher (deep analysis) - coder (implementation) - reviewer (quality checks) - planner (architecture) - executor (commands) - analyzer (debugging) Tests: All 6 tests passing This project was 100% autonomously built by Z.AI GLM-5
This commit is contained in:
524
delegation-system/pool/agent-pool-manager.ts
Normal file
524
delegation-system/pool/agent-pool-manager.ts
Normal file
@@ -0,0 +1,524 @@
|
||||
/**
|
||||
* 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<AgentType, AgentSpawnConfig> = {
|
||||
'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<string, PoolAgent> = new Map();
|
||||
private config: AgentPoolConfig;
|
||||
private taskAssignments: Map<string, string> = new Map(); // taskId -> agentId
|
||||
private scalingTimer?: NodeJS.Timeout;
|
||||
private isShuttingDown: boolean = false;
|
||||
|
||||
constructor(config: Partial<AgentPoolConfig> = {}) {
|
||||
this.config = { ...DEFAULT_POOL_CONFIG, ...config };
|
||||
this.initializePool();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Pool Initialization
|
||||
// ============================================================================
|
||||
|
||||
private initializePool(): void {
|
||||
// Create initial agents for each type
|
||||
const initialCounts: Record<AgentType, number> = {
|
||||
'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<string, { busy: number; idle: number; ratio: number }> {
|
||||
const counts: Record<string, { busy: number; idle: number }> = {};
|
||||
|
||||
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<string, { busy: number; idle: number; ratio: number }> = {};
|
||||
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<AgentType, { total: number; idle: number; busy: number }>;
|
||||
} {
|
||||
let idleCount = 0;
|
||||
let busyCount = 0;
|
||||
let coolingDownCount = 0;
|
||||
const byType: Record<AgentType, { total: number; idle: number; busy: number }> = {} 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<void> {
|
||||
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<void> {
|
||||
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<AgentPoolConfig>): AgentPoolManager {
|
||||
return new AgentPoolManager(config);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Export
|
||||
// ============================================================================
|
||||
|
||||
export default AgentPoolManager;
|
||||
Reference in New Issue
Block a user