@sethdouglasford/claude-flow
Version:
Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology
544 lines • 22.7 kB
JavaScript
/**
* Enhanced Swarm Coordinator
* Implements hierarchical model strategy with intelligent task decomposition
* Provides advanced AI-powered coordination for multi-agent systems
*/
import { EventEmitter } from "events";
import { logger } from "../core/logger.js";
import { CodeAgent } from "./agents/code-agent.js";
import { StrategyFactory } from "./strategy-factory.js";
/**
* Enhanced Swarm Coordinator with AI-powered task management
* Features:
* - Hierarchical model selection based on task complexity
* - Intelligent context window management
* - Advanced task decomposition and analysis
* - Multi-agent coordination with specialized roles
*/
export class EnhancedSwarmCoordinator extends EventEmitter {
agents = new Map();
taskQueue = [];
activeTasksMap = new Map();
modelConfig;
contextCache = new Map();
strategy;
strategyInstance;
constructor(config) {
super();
this.modelConfig = config.modelConfig;
this.strategy = config.strategy;
this.strategyInstance = StrategyFactory.createStrategy(config.strategy);
this.initializeAgents(config.maxAgents || 5);
this.setupEventHandlers();
logger.info(`Enhanced Swarm Coordinator initialized with strategy: ${config.strategy}`);
}
setupEventHandlers() {
// Setup event handlers for agent communication
this.on("taskCompleted", this.handleAgentTaskCompleted.bind(this));
this.on("taskError", this.handleAgentTaskError.bind(this));
logger.info("Event handlers setup complete");
}
initializeAgents(maxAgents) {
// Create specialized agents with hierarchical model configuration
for (let i = 0; i < maxAgents; i++) {
const agentId = `enhanced-agent-${i}`;
const agent = new CodeAgent(agentId, {
models: {
primary: this.modelConfig.primary,
apply: this.modelConfig.apply,
review: this.modelConfig.review,
},
threshold: this.modelConfig.threshold,
});
// Set up agent event listeners
agent.on("taskCompleted", this.handleAgentTaskCompleted.bind(this));
agent.on("taskError", this.handleAgentTaskError.bind(this));
this.agents.set(agentId, agent);
logger.info(`Initialized ${agent.getType()} agent: ${agentId}`);
}
}
async addObjective(objective) {
logger.info(`Adding objective: ${objective}`);
// Decompose objective using primary model (complex reasoning)
const tasks = await this.decomposeObjective(objective);
// Analyze each task complexity and assign appropriate models
const analyzedTasks = await Promise.all(tasks.map(task => this.analyzeTaskComplexity(task)));
// Add tasks to queue with enhanced metadata
for (const analysis of analyzedTasks) {
const enhancedTask = {
...analysis.task,
metadata: {
...analysis.task.metadata,
customProperties: {
complexity: analysis.complexity,
suggestedModel: analysis.suggestedModel,
estimatedTokens: analysis.estimatedTokens,
requiredContext: analysis.requiredContext,
},
},
};
this.taskQueue.push(enhancedTask);
}
const objectiveId = `obj-${Date.now()}`;
logger.info(`Created objective ${objectiveId} with ${analyzedTasks.length} tasks`);
this.emit("objectiveAdded", { objectiveId, tasks: analyzedTasks.length });
// Start processing tasks asynchronously
this.processTasks().catch(error => {
logger.error("Error processing tasks:", error);
this.emit("error", error);
});
return objectiveId;
}
async decomposeObjective(objective) {
// Enhanced task decomposition using the selected strategy
logger.info(`Decomposing objective with ${this.strategy} strategy: ${objective}`);
// Create SwarmObjective for the strategy
const swarmObjective = {
id: `objective-${Date.now()}`,
name: objective.substring(0, 50), // Truncate for name
description: objective,
strategy: this.strategy,
mode: "centralized",
requirements: {
minAgents: 1,
maxAgents: 5,
agentTypes: ["developer", "researcher", "analyzer"],
estimatedDuration: 60,
maxDuration: 120,
qualityThreshold: 0.8,
reviewCoverage: 0.5,
testCoverage: 0.7,
reliabilityTarget: 0.9,
},
constraints: {
milestones: [],
resourceLimits: {},
minQuality: 0.8,
requiredApprovals: [],
allowedFailures: 1,
recoveryTime: 300,
},
tasks: [],
dependencies: [],
status: "planning",
progress: {
totalTasks: 0,
completedTasks: 0,
failedTasks: 0,
runningTasks: 0,
estimatedCompletion: new Date(Date.now() + 60 * 60 * 1000),
timeRemaining: 3600,
percentComplete: 0,
averageQuality: 0,
passedReviews: 0,
passedTests: 0,
resourceUtilization: {},
costSpent: 0,
activeAgents: 0,
idleAgents: 0,
busyAgents: 0,
},
metrics: {
throughput: 0,
latency: 0,
efficiency: 0,
reliability: 0,
averageQuality: 0,
defectRate: 0,
reworkRate: 0,
resourceUtilization: {},
costEfficiency: 0,
agentUtilization: 0,
agentSatisfaction: 0,
collaborationEffectiveness: 0,
scheduleVariance: 0,
deadlineAdherence: 0,
},
createdAt: new Date(),
updatedAt: new Date(),
};
try {
// Use the strategy instance to decompose the objective
const decompositionResult = await this.strategyInstance.decomposeObjective(swarmObjective);
logger.info(`Strategy decomposed objective into ${decompositionResult.tasks.length} tasks`);
logger.info(`Estimated duration: ${decompositionResult.estimatedDuration} minutes`);
// Update task dependencies based on the decomposition result
decompositionResult.tasks.forEach(task => {
const dependencies = decompositionResult.dependencies.get(task.id.id) || [];
task.constraints.dependencies = dependencies.map(depId => ({
id: depId,
swarmId: task.id.swarmId,
sequence: 1,
priority: 1,
}));
});
return decompositionResult.tasks;
}
catch (error) {
logger.error(`Error decomposing objective with ${this.strategy} strategy:`, error);
// Fallback to simple task creation
return this.createFallbackTasks(objective);
}
}
createFallbackTasks(objective) {
const timestamp = Date.now();
return [{
id: {
id: `fallback-task-${timestamp}`,
swarmId: "enhanced-swarm",
sequence: 1,
priority: 1,
},
name: "Execute Objective",
description: `Execute: ${objective}`,
status: "created",
priority: "high",
type: "research",
requirements: {
capabilities: ["research", "analysis"],
tools: ["memory"],
permissions: ["read", "write"],
},
constraints: {
dependencies: [],
dependents: [],
conflicts: [],
},
input: { objective },
instructions: `Execute the objective: ${objective}`,
context: {},
createdAt: new Date(),
updatedAt: new Date(),
attempts: [],
statusHistory: [],
}];
}
async analyzeTaskComplexity(task) {
// Analyze task using intelligent complexity detection patterns
const description = task.description.toLowerCase();
const name = task.name.toLowerCase();
let complexity = "simple";
let estimatedTokens = 1000;
let suggestedModel = this.modelConfig.apply;
// Complex task indicators
const complexIndicators = [
"architecture", "system design", "integration", "security",
"performance optimization", "database schema", "api design",
];
// Medium task indicators
const mediumIndicators = [
"implement", "refactor", "optimize", "validate", "test",
"function", "method", "class", "component",
];
if (complexIndicators.some(indicator => description.includes(indicator) || name.includes(indicator))) {
complexity = "complex";
estimatedTokens = 4000;
suggestedModel = this.modelConfig.primary;
}
else if (mediumIndicators.some(indicator => description.includes(indicator) || name.includes(indicator))) {
complexity = "medium";
estimatedTokens = 2000;
suggestedModel = this.modelConfig.primary;
}
// Extract required context files
const requiredContext = this.extractContextFiles(`${task.description} ${task.instructions || ""}`);
return {
task,
complexity,
estimatedTokens,
requiredContext,
suggestedModel,
};
}
extractContextFiles(text) {
// Extract file paths and imports from task description
const filePatterns = [
/[\w\-\.\/]+\.(ts|js|tsx|jsx|py|java|cpp|h|css|html|md|json|yaml|yml)/g,
/from\s+['"]([^'"]+)['"]/g,
/import\s+['"]([^'"]+)['"]/g,
];
const files = new Set();
for (const pattern of filePatterns) {
const matches = text.match(pattern);
if (matches) {
matches.forEach(match => files.add(match));
}
}
return Array.from(files);
}
determineChunkType(file) {
const keywords = file.toLowerCase().split(/[\s\-_\.\/]/);
if (keywords.includes("test") || keywords.includes("testing")) {
return "test";
}
if (keywords.includes("config") || keywords.includes("configuration")) {
return "config";
}
if (keywords.includes("type") || keywords.includes("interface")) {
return "module";
}
// Default to module for other files
return "module";
}
async processTasks() {
logger.info("Starting task processing...");
while (this.taskQueue.length > 0 || this.activeTasksMap.size > 0) {
const nextTask = this.getNextTask();
if (!nextTask) {
// No tasks ready, wait for dependencies to complete
await new Promise(resolve => setTimeout(resolve, 1000));
continue;
}
const availableAgents = Array.from(this.agents.values()).filter(agent => agent.isAvailable());
if (availableAgents.length === 0) {
// No agents available, wait
logger.info("No agents available, waiting...");
await new Promise(resolve => setTimeout(resolve, 1000));
continue;
}
const bestAgent = this.findBestAgent(nextTask, availableAgents);
if (!bestAgent) {
// No suitable agent found - try to make agents more flexible
logger.warn(`No suitable agent found for task: ${nextTask.name}`);
// Try with any available agent as fallback
const fallbackAgent = availableAgents[0];
if (fallbackAgent) {
logger.info(`Using fallback agent ${fallbackAgent.getId()} for task: ${nextTask.name}`);
// Prepare context for the agent
await this.prepareContextWindow(nextTask, fallbackAgent);
// Remove task from queue and add to active tasks
const taskIndex = this.taskQueue.indexOf(nextTask);
this.taskQueue.splice(taskIndex, 1);
this.activeTasksMap.set(nextTask.id.id, nextTask);
// Assign task to fallback agent
logger.info(`Assigning task ${nextTask.id.id} to fallback agent ${fallbackAgent.getId()}`);
this.emit("taskStarted", {
taskName: nextTask.name,
taskId: nextTask.id.id,
agentId: fallbackAgent.getId(),
});
await fallbackAgent.assignTask(nextTask);
}
else {
// Still no agent, wait and retry
await new Promise(resolve => setTimeout(resolve, 2000));
}
continue;
}
// Found suitable agent
await this.prepareContextWindow(nextTask, bestAgent);
// Remove task from queue and add to active tasks
const taskIndex = this.taskQueue.indexOf(nextTask);
this.taskQueue.splice(taskIndex, 1);
this.activeTasksMap.set(nextTask.id.id, nextTask);
// Assign task to agent
logger.info(`Assigning task ${nextTask.id.id} to agent ${bestAgent.getId()}`);
this.emit("taskStarted", {
taskName: nextTask.name,
taskId: nextTask.id.id,
agentId: bestAgent.getId(),
});
await bestAgent.assignTask(nextTask);
}
}
getNextTask() {
// Find tasks with all dependencies completed
for (const task of this.taskQueue) {
const dependenciesMet = task.constraints.dependencies.every(depId => {
const depTask = this.activeTasksMap.get(depId.id);
return depTask?.status === "completed";
});
if (dependenciesMet) {
return task;
}
}
return null;
}
findBestAgent(task, availableAgents) {
let bestAgent = null;
let bestScore = 0;
for (const agent of availableAgents) {
// Use more lenient capability checking
if (!this.agentCanHandleTaskLenient(agent, task)) {
continue;
}
const capabilities = agent.getCapabilities();
let score = 0;
// Score based on capability match
const taskCapabilities = task.requirements?.capabilities || [];
const matchingCapabilities = taskCapabilities.filter(capability => {
// Check if agent's tools include this capability or related ones
return capabilities.tools.some(tool => tool.includes(capability) ||
capability.includes(tool) ||
this.areCapabilitiesRelated(capability, tool));
});
score += matchingCapabilities.length * 10;
// Score based on agent reliability
score += capabilities.reliability * 5;
// Score based on agent speed
score += capabilities.speed * 3;
// Bonus for CodeAgent handling development tasks
if (agent.getType() === "developer" &&
(task.type === "coding" || task.type === "analysis" || task.type === "research")) {
score += 20;
}
if (score > bestScore) {
bestScore = score;
bestAgent = agent;
}
}
return bestAgent;
}
agentCanHandleTaskLenient(agent, task) {
// First try the agent's own canHandleTask method
try {
if (agent.canHandleTask(task)) {
return true;
}
}
catch (error) {
logger.warn(`Error checking if agent ${agent.getId()} can handle task ${task.id.id}:`, error);
}
// Fallback: CodeAgent can handle most development tasks
if (agent.getType() === "developer") {
const developmentTaskTypes = ["coding", "analysis", "research", "implementation", "review"];
if (developmentTaskTypes.includes(task.type)) {
return true;
}
}
return false;
}
areCapabilitiesRelated(cap1, cap2) {
const relatedCapabilities = {
"analysis": ["research", "planning", "architecture", "design"],
"coding": ["implementation", "frontend", "backend", "api_development"],
"review": ["quality_assurance", "testing"],
"research": ["analysis", "web_search"],
"planning": ["analysis", "architecture"],
"architecture": ["design", "analysis", "planning"],
"design": ["architecture", "analysis"],
"frontend": ["coding", "implementation"],
"backend": ["coding", "implementation", "api_development"],
"api_development": ["backend", "coding", "implementation"],
};
const related1 = relatedCapabilities[cap1] || [];
const related2 = relatedCapabilities[cap2] || [];
return related1.includes(cap2) || related2.includes(cap1);
}
async prepareContextWindow(task, agent) {
// Prepare context window based on task requirements
const customProps = task.metadata?.customProperties;
const requiredContext = customProps?.requiredContext || [];
const estimatedTokens = customProps?.estimatedTokens || 1000;
const contextWindow = {
files: requiredContext,
maxTokens: estimatedTokens,
priority: this.determineContextPriority(task),
chunks: await this.createContextChunks(requiredContext),
};
this.contextCache.set(task.id.id, contextWindow);
}
determineContextPriority(task) {
const customProps = task.metadata?.customProperties;
const complexity = customProps?.complexity;
switch (complexity) {
case "simple":
return "immediate";
case "medium":
return "extended";
case "complex":
return "project";
default:
return "semantic";
}
}
async createContextChunks(files) {
const chunks = [];
for (const file of files) {
chunks.push({
content: `// Context for ${file}`,
type: this.determineChunkType(file),
priority: this.calculateChunkPriority(file),
tokens: 100, // Simplified token calculation
});
}
return chunks;
}
calculateChunkPriority(file) {
// Higher priority for certain file types
if (file.includes("test"))
return 1;
if (file.includes("config"))
return 2;
if (file.includes(".ts") || file.includes(".js"))
return 3;
return 4;
}
handleAgentTaskCompleted(event) {
logger.info(`Task ${event.taskId} completed by agent ${event.agentId}`);
const task = this.activeTasksMap.get(event.taskId);
if (task) {
task.status = "completed";
task.completedAt = new Date();
task.result = event.result;
this.activeTasksMap.delete(event.taskId);
const duration = task.completedAt.getTime() - task.createdAt.getTime();
this.emit("taskCompleted", {
taskName: task.name,
taskId: event.taskId,
agentId: event.agentId,
duration,
result: event.result,
});
}
}
handleAgentTaskError(event) {
logger.error(`Task ${event.taskId} failed on agent ${event.agentId}:`, event.error);
const task = this.activeTasksMap.get(event.taskId);
if (task) {
task.status = "failed";
task.error = {
type: "execution_error",
message: event.error.message,
recoverable: true,
retryable: true,
context: {},
};
this.activeTasksMap.delete(event.taskId);
this.emit("taskError", {
taskName: task.name,
taskId: event.taskId,
agentId: event.agentId,
error: event.error.message || event.error,
});
}
}
getStatus() {
const busyAgents = Array.from(this.agents.values()).filter(a => !a.isAvailable()).length;
const totalTasks = this.taskQueue.length + this.activeTasksMap.size;
const completedTasks = this.agents.size > 0 ?
Array.from(this.agents.values()).reduce((sum, agent) => sum + agent.getMetrics().tasksCompleted, 0) : 0;
return {
status: this.taskQueue.length === 0 && this.activeTasksMap.size === 0 ? "completed" : "executing",
activeAgents: busyAgents,
totalAgents: this.agents.size,
activeTasks: this.activeTasksMap.size,
totalTasks,
completedTasks,
failedTasks: 0, // TODO: Track failed tasks
queuedTasks: this.taskQueue.length,
strategy: this.strategy,
};
}
async shutdown() {
logger.info("Shutting down Enhanced Swarm Coordinator");
// Cleanup all agents
await Promise.all(Array.from(this.agents.values()).map(agent => agent.cleanup()));
this.agents.clear();
this.taskQueue.length = 0;
this.activeTasksMap.clear();
this.contextCache.clear();
this.emit("shutdown");
}
}
//# sourceMappingURL=enhanced-coordinator.js.map