@sethdouglasford/claude-flow
Version:
Claude Code Flow - Advanced AI-powered development workflows with SPARC methodology
1,050 lines • 40.9 kB
JavaScript
/**
* Comprehensive agent management system
*/
import { EventEmitter } from "node:events";
import { spawn } from "node:child_process";
import { generateId } from "../utils/helpers.js";
/**
* Comprehensive agent lifecycle and resource management
*/
export class AgentManager extends EventEmitter {
logger;
eventBus;
memory;
config;
// Agent tracking
agents = new Map();
processes = new Map();
templates = new Map();
clusters = new Map();
pools = new Map();
// Health monitoring
healthChecks = new Map();
healthInterval;
heartbeatInterval;
// Scaling and policies
scalingPolicies = new Map();
scalingOperations = new Map();
// Resource tracking
resourceUsage = new Map();
performanceHistory = new Map();
constructor(config, logger, eventBus, memory) {
super();
this.logger = logger;
this.eventBus = eventBus;
this.memory = memory;
this.config = {
maxAgents: 50,
defaultTimeout: 30000,
heartbeatInterval: 10000,
healthCheckInterval: 30000,
autoRestart: true,
resourceLimits: {
memory: 512 * 1024 * 1024, // 512MB
cpu: 1.0,
disk: 1024 * 1024 * 1024, // 1GB
},
agentDefaults: {
autonomyLevel: 0.7,
learningEnabled: true,
adaptationEnabled: true,
},
environmentDefaults: {
runtime: "node",
workingDirectory: "./agents",
tempDirectory: "./tmp",
logDirectory: "./logs",
},
...config,
};
this.setupEventHandlers();
this.initializeDefaultTemplates();
}
setupEventHandlers() {
this.eventBus.on("agent:heartbeat", (data) => {
if (this.isHeartbeatData(data)) {
this.handleHeartbeat(data);
}
});
this.eventBus.on("agent:error", (data) => {
if (this.isAgentErrorData(data)) {
this.handleAgentError(data);
}
});
this.eventBus.on("task:assigned", (data) => {
if (this.isTaskAssignedData(data)) {
this.updateAgentWorkload(data.agentId, 1);
}
});
this.eventBus.on("task:completed", (data) => {
if (this.isTaskCompletedData(data)) {
this.updateAgentWorkload(data.agentId, -1);
this.updateAgentMetrics(data.agentId, data.metrics);
}
});
this.eventBus.on("resource:usage", (data) => {
if (this.isResourceUsageData(data)) {
this.updateResourceUsage(data.agentId, data.usage);
}
});
}
initializeDefaultTemplates() {
// Research agent template
this.templates.set("researcher", {
name: "Research Agent",
type: "researcher",
capabilities: {
codeGeneration: false,
codeReview: false,
testing: false,
documentation: true,
research: true,
analysis: true,
webSearch: true,
apiIntegration: true,
fileSystem: true,
terminalAccess: false,
languages: [],
frameworks: [],
domains: ["research", "analysis", "information-gathering"],
tools: ["web-search", "document-analysis", "data-extraction"],
maxConcurrentTasks: 5,
maxMemoryUsage: 256 * 1024 * 1024,
maxExecutionTime: 600000,
reliability: 0.9,
speed: 0.8,
quality: 0.9,
},
config: {
autonomyLevel: 0.8,
learningEnabled: true,
adaptationEnabled: true,
maxTasksPerHour: 20,
maxConcurrentTasks: 5,
timeoutThreshold: 600000,
reportingInterval: 30000,
heartbeatInterval: 10000,
permissions: ["web-access", "file-read"],
trustedAgents: [],
expertise: { research: 0.9, analysis: 0.8, documentation: 0.7 },
preferences: { verbose: true, detailed: true },
},
environment: {
runtime: "node",
version: "20.0.0",
workingDirectory: "./agents/researcher",
tempDirectory: "./tmp/researcher",
logDirectory: "./logs/researcher",
apiEndpoints: {},
credentials: {},
availableTools: ["web-search", "document-reader", "data-extractor"],
toolConfigs: {},
},
startupScript: "./scripts/start-researcher.ts",
});
// Developer agent template
this.templates.set("developer", {
name: "Developer Agent",
type: "developer",
capabilities: {
codeGeneration: true,
codeReview: true,
testing: true,
documentation: true,
research: false,
analysis: true,
webSearch: false,
apiIntegration: true,
fileSystem: true,
terminalAccess: true,
languages: ["typescript", "javascript", "python", "rust"],
frameworks: ["node", "react", "svelte"],
domains: ["web-development", "backend", "api-design"],
tools: ["git", "editor", "debugger", "linter", "formatter"],
maxConcurrentTasks: 3,
maxMemoryUsage: 512 * 1024 * 1024,
maxExecutionTime: 1200000,
reliability: 0.95,
speed: 0.7,
quality: 0.95,
},
config: {
autonomyLevel: 0.6,
learningEnabled: true,
adaptationEnabled: true,
maxTasksPerHour: 10,
maxConcurrentTasks: 3,
timeoutThreshold: 1200000,
reportingInterval: 60000,
heartbeatInterval: 15000,
permissions: ["file-read", "file-write", "terminal-access", "git-access"],
trustedAgents: [],
expertise: { coding: 0.95, testing: 0.8, debugging: 0.9 },
preferences: { codeStyle: "functional", testFramework: "jest" },
},
environment: {
runtime: "node",
version: "20.0.0",
workingDirectory: "./agents/developer",
tempDirectory: "./tmp/developer",
logDirectory: "./logs/developer",
apiEndpoints: {},
credentials: {},
availableTools: ["git", "npm", "editor", "debugger"],
toolConfigs: {},
},
startupScript: "./scripts/start-developer.ts",
});
// Add more templates...
this.initializeSpecializedTemplates();
}
initializeSpecializedTemplates() {
// Analyzer template
this.templates.set("analyzer", {
name: "Analyzer Agent",
type: "analyzer",
capabilities: {
codeGeneration: false,
codeReview: true,
testing: false,
documentation: true,
research: false,
analysis: true,
webSearch: false,
apiIntegration: true,
fileSystem: true,
terminalAccess: false,
languages: ["python", "r", "sql"],
frameworks: ["pandas", "numpy", "matplotlib"],
domains: ["data-analysis", "statistics", "visualization"],
tools: ["data-processor", "chart-generator", "statistical-analyzer"],
maxConcurrentTasks: 4,
maxMemoryUsage: 1024 * 1024 * 1024,
maxExecutionTime: 900000,
reliability: 0.9,
speed: 0.75,
quality: 0.9,
},
config: {
autonomyLevel: 0.7,
learningEnabled: true,
adaptationEnabled: true,
maxTasksPerHour: 15,
maxConcurrentTasks: 4,
timeoutThreshold: 900000,
reportingInterval: 45000,
heartbeatInterval: 12000,
permissions: ["file-read", "data-access"],
trustedAgents: [],
expertise: { analysis: 0.95, visualization: 0.8, statistics: 0.85 },
preferences: { outputFormat: "detailed", includeCharts: true },
},
environment: {
runtime: "node",
version: "20.0.0",
workingDirectory: "./agents/analyzer",
tempDirectory: "./tmp/analyzer",
logDirectory: "./logs/analyzer",
apiEndpoints: {},
credentials: {},
availableTools: ["data-processor", "chart-gen", "stats-calc"],
toolConfigs: {},
},
startupScript: "./scripts/start-analyzer.ts",
});
}
initialize() {
this.logger.info("Initializing agent manager", {
maxAgents: this.config.maxAgents,
templates: this.templates.size,
});
// Start health monitoring
this.startHealthMonitoring();
// Start heartbeat monitoring
this.startHeartbeatMonitoring();
// Initialize default scaling policies
this.initializeScalingPolicies();
this.emit("agent-manager:initialized");
}
async shutdown() {
this.logger.info("Shutting down agent manager");
// Stop monitoring
if (this.healthInterval)
clearInterval(this.healthInterval);
if (this.heartbeatInterval)
clearInterval(this.heartbeatInterval);
// Gracefully shutdown all agents
const shutdownPromises = Array.from(this.agents.keys()).map(agentId => this.stopAgent(agentId, "shutdown"));
await Promise.all(shutdownPromises);
this.emit("agent-manager:shutdown");
}
// === AGENT LIFECYCLE ===
async createAgent(templateName, overrides = {}) {
if (this.agents.size >= this.config.maxAgents) {
throw new Error("Maximum agent limit reached");
}
// Enhanced template lookup - try multiple strategies
let template = this.templates.get(templateName);
if (!template) {
// Strategy 1: Try exact type match (case insensitive)
Array.from(this.templates.entries()).forEach(([key, tmpl]) => {
if (!template && tmpl.type.toLowerCase() === templateName.toLowerCase()) {
template = tmpl;
}
});
}
if (!template) {
// Strategy 2: Try exact name match (case insensitive)
Array.from(this.templates.entries()).forEach(([key, tmpl]) => {
if (!template && tmpl.name.toLowerCase() === templateName.toLowerCase()) {
template = tmpl;
}
});
}
if (!template) {
// Strategy 3: Try partial name match (includes substring)
Array.from(this.templates.entries()).forEach(([key, tmpl]) => {
if (!template && tmpl.name.toLowerCase().includes(templateName.toLowerCase())) {
template = tmpl;
}
});
}
if (!template) {
throw new Error(`Template ${templateName} not found`);
}
const agentId = generateId("agent");
const swarmId = "default"; // Could be parameterized
// Create complete config by merging template, defaults, and overrides
const config = {
autonomyLevel: this.config.agentDefaults.autonomyLevel,
learningEnabled: this.config.agentDefaults.learningEnabled,
adaptationEnabled: this.config.agentDefaults.adaptationEnabled,
maxTasksPerHour: 20,
maxConcurrentTasks: 5,
timeoutThreshold: this.config.defaultTimeout,
reportingInterval: 30000,
heartbeatInterval: this.config.heartbeatInterval,
permissions: [],
trustedAgents: [],
expertise: {},
preferences: {},
...template.config,
...overrides.config,
};
// Create complete environment by merging template, defaults, and overrides
const environment = {
runtime: this.config.environmentDefaults.runtime,
version: "20.0.0",
workingDirectory: this.config.environmentDefaults.workingDirectory,
tempDirectory: this.config.environmentDefaults.tempDirectory,
logDirectory: this.config.environmentDefaults.logDirectory,
apiEndpoints: {},
credentials: {},
availableTools: [],
toolConfigs: {},
...template.environment,
...overrides.environment,
};
const agent = {
id: { id: agentId, swarmId, type: template.type, instance: 1 },
name: overrides.name ?? `${template.name}-${agentId.slice(-8)}`,
type: template.type,
status: "initializing",
capabilities: { ...template.capabilities },
metrics: this.createDefaultMetrics(),
workload: 0,
health: 1.0,
config,
environment,
endpoints: [],
lastHeartbeat: new Date(),
taskHistory: [],
errorHistory: [],
childAgents: [],
collaborators: [],
};
this.agents.set(agentId, agent);
this.healthChecks.set(agentId, this.createDefaultHealth(agentId));
this.logger.info("Created agent", {
agentId,
name: agent.name,
type: agent.type,
template: templateName,
});
this.emit("agent:created", { agent });
// Store in memory for persistence
await this.memory.store(`agent:${agentId}`, agent, {
type: "agent-state",
tags: [agent.type, "active"],
partition: "state",
});
return agentId;
}
async startAgent(agentId) {
const agent = this.agents.get(agentId);
if (!agent) {
throw new Error(`Agent ${agentId} not found`);
}
if (agent.status !== "initializing" && agent.status !== "offline") {
throw new Error(`Agent ${agentId} cannot be started from status ${agent.status}`);
}
try {
agent.status = "initializing";
this.updateAgentStatus(agentId, "initializing");
// Spawn agent process
const process = this.spawnAgentProcess(agent);
this.processes.set(agentId, process);
// Wait for agent to signal ready
await this.waitForAgentReady(agentId, this.config.defaultTimeout);
agent.status = "idle";
this.updateAgentStatus(agentId, "idle");
this.logger.info("Started agent", { agentId, name: agent.name });
this.emit("agent:started", { agent });
}
catch (_error) {
agent.status = "error";
const errorMessage = _error instanceof Error ? _error.message : "Unknown error occurred";
this.addAgentError(agentId, {
timestamp: new Date(),
type: "startup_failed",
message: errorMessage,
context: { agentId },
severity: "critical",
resolved: false,
});
this.logger.error("Failed to start agent", { agentId, error: _error });
throw _error;
}
}
async stopAgent(agentId, reason = "user_request") {
const agent = this.agents.get(agentId);
if (!agent) {
throw new Error(`Agent ${agentId} not found`);
}
if (agent.status === "offline" || agent.status === "terminated") {
return; // Already stopped
}
try {
agent.status = "terminating";
this.updateAgentStatus(agentId, "terminating");
// Send graceful shutdown signal
const process = this.processes.get(agentId);
if (process && !process.killed) {
process.kill("SIGTERM");
// Force kill after timeout
setTimeout(() => {
if (process && !process.killed) {
process.kill("SIGKILL");
}
}, this.config.defaultTimeout);
}
// Wait for process to exit
await this.waitForProcessExit(agentId, this.config.defaultTimeout);
agent.status = "terminated";
this.updateAgentStatus(agentId, "terminated");
// Cleanup
this.processes.delete(agentId);
this.logger.info("Stopped agent", { agentId, reason });
this.emit("agent:stopped", { agent, reason });
}
catch (_error) {
this.logger.error("Failed to stop agent gracefully", { agentId, error: _error });
// Force cleanup
this.processes.delete(agentId);
agent.status = "terminated";
}
}
async restartAgent(agentId, reason = "restart_requested") {
this.logger.info("Restarting agent", { agentId, reason });
await this.stopAgent(agentId, `restart:${reason}`);
await this.startAgent(agentId);
this.emit("agent:restarted", { agentId, reason });
}
async removeAgent(agentId) {
const agent = this.agents.get(agentId);
if (!agent) {
throw new Error(`Agent ${agentId} not found`);
}
// Stop agent if running
if (agent.status !== "terminated" && agent.status !== "offline") {
await this.stopAgent(agentId, "removal");
}
// Remove from all data structures
this.agents.delete(agentId);
this.healthChecks.delete(agentId);
this.resourceUsage.delete(agentId);
this.performanceHistory.delete(agentId);
// Remove from pools and clusters
this.removeAgentFromPoolsAndClusters(agentId);
// Remove from memory
await this.memory.deleteEntry(`agent:${agentId}`);
this.logger.info("Removed agent", { agentId });
this.emit("agent:removed", { agentId });
}
// === AGENT POOLS ===
async createAgentPool(name, templateName, config) {
const template = this.templates.get(templateName);
if (!template) {
throw new Error(`Template ${templateName} not found`);
}
const poolId = generateId("pool");
const pool = {
id: poolId,
name,
type: template.type,
minSize: config.minSize,
maxSize: config.maxSize,
currentSize: 0,
availableAgents: [],
busyAgents: [],
template,
autoScale: config.autoScale ?? false,
scaleUpThreshold: config.scaleUpThreshold ?? 0.8,
scaleDownThreshold: config.scaleDownThreshold ?? 0.3,
};
this.pools.set(poolId, pool);
// Create minimum agents
for (let i = 0; i < config.minSize; i++) {
const agentId = await this.createAgent(templateName, {
name: `${name}-${i + 1}`,
});
await this.startAgent(agentId);
pool.availableAgents.push({ id: agentId, swarmId: "default", type: template.type, instance: i + 1 });
pool.currentSize++;
}
this.logger.info("Created agent pool", { poolId, name, minSize: config.minSize });
this.emit("pool:created", { pool });
return poolId;
}
async scalePool(poolId, targetSize) {
const pool = this.pools.get(poolId);
if (!pool) {
throw new Error(`Pool ${poolId} not found`);
}
if (targetSize < pool.minSize || targetSize > pool.maxSize) {
throw new Error(`Target size ${targetSize} outside pool limits [${pool.minSize}, ${pool.maxSize}]`);
}
const { currentSize } = pool;
const delta = targetSize - currentSize;
if (delta > 0) {
// Scale up
for (let i = 0; i < delta; i++) {
const agentId = await this.createAgent(pool.template.name, {
name: `${pool.name}-${currentSize + i + 1}`,
});
await this.startAgent(agentId);
pool.availableAgents.push({
id: agentId,
swarmId: "default",
type: pool.type,
instance: currentSize + i + 1,
});
}
}
else if (delta < 0) {
// Scale down
const agentsToRemove = pool.availableAgents.slice(0, Math.abs(delta));
for (const agentId of agentsToRemove) {
await this.removeAgent(agentId.id);
pool.availableAgents = pool.availableAgents.filter(a => a.id !== agentId.id);
}
}
pool.currentSize = targetSize;
this.logger.info("Scaled pool", { poolId, fromSize: currentSize, toSize: targetSize });
this.emit("pool:scaled", { pool, fromSize: currentSize, toSize: targetSize });
}
// === HEALTH MONITORING ===
startHealthMonitoring() {
this.healthInterval = setInterval(() => {
void this.performHealthChecks();
}, this.config.healthCheckInterval);
this.logger.info("Started health monitoring", {
interval: this.config.healthCheckInterval,
});
}
startHeartbeatMonitoring() {
this.heartbeatInterval = setInterval(() => {
this.checkHeartbeats();
}, this.config.heartbeatInterval);
this.logger.info("Started heartbeat monitoring", {
interval: this.config.heartbeatInterval,
});
}
async performHealthChecks() {
const healthPromises = Array.from(this.agents.keys()).map(agentId => this.checkAgentHealth(agentId));
await Promise.allSettled(healthPromises);
}
async checkAgentHealth(agentId) {
const agent = this.agents.get(agentId);
if (!agent)
return;
const health = this.healthChecks.get(agentId);
if (!health) {
// Initialize health check if missing
this.healthChecks.set(agentId, this.createDefaultHealth(agentId));
return;
}
const now = new Date();
try {
// Check responsiveness
const responsiveness = this.checkResponsiveness(agentId);
health.components.responsiveness = responsiveness;
// Check performance
const performance = this.calculatePerformanceScore(agentId);
health.components.performance = performance;
// Check reliability
const reliability = this.calculateReliabilityScore(agentId);
health.components.reliability = reliability;
// Check resource usage
const resourceScore = this.calculateResourceScore(agentId);
health.components.resourceUsage = resourceScore;
// Calculate overall health
const overall = (responsiveness + performance + reliability + resourceScore) / 4;
health.overall = overall;
health.lastCheck = now;
// Update agent health
agent.health = overall;
// Check for issues
this.detectHealthIssues(agentId, health);
// Auto-restart if critically unhealthy
if (overall < 0.3 && this.config.autoRestart) {
this.logger.warn("Agent critically unhealthy, restarting", { agentId, health: overall });
await this.restartAgent(agentId, "health_critical");
}
}
catch (_error) {
this.logger.error("Health check failed", { agentId, error: _error });
health.overall = 0;
health.lastCheck = now;
}
}
checkResponsiveness(agentId) {
// Send ping and measure response time
const _startTime = Date.now();
try {
// This would send an actual ping to the agent
// For now, simulate based on last heartbeat
const agent = this.agents.get(agentId);
if (!agent)
return 0;
const timeSinceHeartbeat = Date.now() - agent.lastHeartbeat.getTime();
if (timeSinceHeartbeat > this.config.heartbeatInterval * 3) {
return 0; // Unresponsive
}
else if (timeSinceHeartbeat > this.config.heartbeatInterval * 2) {
return 0.5; // Slow
}
else {
return 1.0; // Responsive
}
}
catch (_error) {
return 0; // Failed to respond
}
}
calculatePerformanceScore(agentId) {
const history = this.performanceHistory.get(agentId) ?? [];
if (history.length === 0)
return 1.0;
// Calculate average task completion time vs expected
const recent = history.slice(-10); // Last 10 entries
const avgTime = recent.reduce((sum, entry) => sum + entry.metrics.averageExecutionTime, 0) / recent.length;
// Normalize based on expected performance (simplified)
const expectedTime = 60000; // 1 minute baseline
return Math.max(0, Math.min(1, expectedTime / avgTime));
}
calculateReliabilityScore(agentId) {
const agent = this.agents.get(agentId);
if (!agent)
return 1.0;
const totalTasks = agent.metrics.tasksCompleted + agent.metrics.tasksFailed;
if (totalTasks === 0)
return 1.0;
return agent.metrics.tasksCompleted / totalTasks;
}
calculateResourceScore(agentId) {
const usage = this.resourceUsage.get(agentId);
if (!usage)
return 1.0;
const limits = this.config.resourceLimits;
const memoryScore = 1 - (usage.memory / limits.memory);
const cpuScore = 1 - (usage.cpu / limits.cpu);
const diskScore = 1 - (usage.disk / limits.disk);
return Math.max(0, (memoryScore + cpuScore + diskScore) / 3);
}
detectHealthIssues(agentId, health) {
const issues = [];
if (health.components.responsiveness < 0.5) {
issues.push({
type: "communication",
severity: health.components.responsiveness < 0.2 ? "critical" : "high",
message: "Agent is not responding to heartbeats",
timestamp: new Date(),
resolved: false,
recommendedAction: "Restart agent or check network connectivity",
});
}
if (health.components.performance < 0.6) {
issues.push({
type: "performance",
severity: health.components.performance < 0.3 ? "high" : "medium",
message: "Agent performance is below expected levels",
timestamp: new Date(),
resolved: false,
recommendedAction: "Check resource allocation or agent configuration",
});
}
if (health.components.resourceUsage < 0.4) {
issues.push({
type: "resource",
severity: health.components.resourceUsage < 0.2 ? "critical" : "high",
message: "Agent resource usage is critically high",
timestamp: new Date(),
resolved: false,
recommendedAction: "Increase resource limits or reduce workload",
});
}
health.issues = issues;
}
checkHeartbeats() {
const now = Date.now();
const timeout = this.config.heartbeatInterval * 3;
Array.from(this.agents.entries()).forEach(([agentId, agent]) => {
const timeSinceHeartbeat = now - agent.lastHeartbeat.getTime();
if (timeSinceHeartbeat > timeout && agent.status !== "offline" && agent.status !== "terminated") {
this.logger.warn("Agent heartbeat timeout", { agentId, timeSinceHeartbeat });
agent.status = "error";
this.addAgentError(agentId, {
timestamp: new Date(),
type: "heartbeat_timeout",
message: "Agent failed to send heartbeat within timeout period",
context: { timeout, timeSinceHeartbeat },
severity: "high",
resolved: false,
});
this.emit("agent:heartbeat-timeout", { agentId, timeSinceHeartbeat });
// Auto-restart if enabled
if (this.config.autoRestart) {
this.restartAgent(agentId, "heartbeat_timeout").catch(_error => {
this.logger.error("Failed to auto-restart agent", { agentId, error: _error });
});
}
}
});
}
// === TYPE GUARDS ===
isHeartbeatData(data) {
if (typeof data !== "object" || data === null)
return false;
const candidate = data;
return (typeof candidate.agentId === "string" &&
candidate.timestamp instanceof Date &&
(candidate.metrics === undefined || typeof candidate.metrics === "object"));
}
isAgentErrorData(data) {
if (typeof data !== "object" || data === null)
return false;
const candidate = data;
return (typeof candidate.agentId === "string" &&
typeof candidate.error === "object" &&
candidate.error !== null);
}
isTaskAssignedData(data) {
if (typeof data !== "object" || data === null)
return false;
const candidate = data;
return typeof candidate.agentId === "string";
}
isTaskCompletedData(data) {
if (typeof data !== "object" || data === null)
return false;
const candidate = data;
return (typeof candidate.agentId === "string" &&
typeof candidate.metrics === "object" &&
candidate.metrics !== null);
}
isResourceUsageData(data) {
if (typeof data !== "object" || data === null)
return false;
const candidate = data;
return (typeof candidate.agentId === "string" &&
typeof candidate.usage === "object" &&
candidate.usage !== null);
}
// === UTILITY METHODS ===
spawnAgentProcess(agent) {
const processEnv = {
...process.env,
AGENT_ID: agent.id.id,
AGENT_TYPE: agent.type,
AGENT_NAME: agent.name,
WORKING_DIR: agent.environment.workingDirectory,
LOG_DIR: agent.environment.logDirectory,
};
const args = [
"run",
"--allow-all",
agent.environment.availableTools[0] ?? "./agents/generic-agent.ts",
"--config",
JSON.stringify(agent.config),
];
const childProcess = spawn(agent.environment.runtime, args, {
env: processEnv,
stdio: ["pipe", "pipe", "pipe"],
cwd: agent.environment.workingDirectory,
});
// Handle process events
childProcess.on("exit", (code) => {
this.handleProcessExit(agent.id.id, code);
});
childProcess.on("error", (error) => {
this.handleProcessError(agent.id.id, error);
});
return childProcess;
}
async waitForAgentReady(agentId, timeout) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new Error(`Agent ${agentId} startup timeout`));
}, timeout);
const handler = (data) => {
if (this.isTaskAssignedData(data) && data.agentId === agentId) {
clearTimeout(timer);
this.eventBus.off("agent:ready", handler);
resolve();
}
};
this.eventBus.on("agent:ready", handler);
});
}
async waitForProcessExit(agentId, timeout) {
return new Promise((resolve) => {
const process = this.processes.get(agentId);
if (!process || process.killed) {
resolve();
return;
}
const timer = setTimeout(() => {
resolve(); // Timeout, continue anyway
}, timeout);
process.on("exit", () => {
clearTimeout(timer);
resolve();
});
});
}
handleProcessExit(agentId, code) {
const agent = this.agents.get(agentId);
if (!agent)
return;
this.logger.info("Agent process exited", { agentId, exitCode: code });
if (code !== 0 && code !== null) {
this.addAgentError(agentId, {
timestamp: new Date(),
type: "process_exit",
message: `Agent process exited with code ${code}`,
context: { exitCode: code },
severity: "high",
resolved: false,
});
}
agent.status = "offline";
this.emit("agent:process-exit", { agentId, exitCode: code });
}
handleProcessError(agentId, error) {
this.logger.error("Agent process error", { agentId, error });
this.addAgentError(agentId, {
timestamp: new Date(),
type: "process_error",
message: error.message,
context: { error: error.toString() },
severity: "critical",
resolved: false,
});
this.emit("agent:process-error", { agentId, error });
}
handleHeartbeat(data) {
const agent = this.agents.get(data.agentId);
if (!agent)
return;
agent.lastHeartbeat = data.timestamp;
if (data.metrics) {
this.updateAgentMetrics(data.agentId, data.metrics);
}
// Update health if agent was previously unresponsive
if (agent.status === "error") {
agent.status = "idle";
this.updateAgentStatus(data.agentId, "idle");
}
}
handleAgentError(data) {
this.addAgentError(data.agentId, data.error);
const agent = this.agents.get(data.agentId);
if (agent && data.error.severity === "critical") {
agent.status = "error";
this.updateAgentStatus(data.agentId, "error");
}
}
updateAgentStatus(agentId, status) {
const agent = this.agents.get(agentId);
if (!agent)
return;
const oldStatus = agent.status;
agent.status = status;
this.emit("agent:status-changed", { agentId, from: oldStatus, to: status });
}
updateAgentWorkload(agentId, delta) {
const agent = this.agents.get(agentId);
if (!agent)
return;
agent.workload = Math.max(0, agent.workload + delta);
}
updateAgentMetrics(agentId, metrics) {
const agent = this.agents.get(agentId);
if (!agent)
return;
agent.metrics = { ...agent.metrics, ...metrics };
// Store performance history
const history = this.performanceHistory.get(agentId) ?? [];
history.push({ timestamp: new Date(), metrics: { ...metrics } });
// Keep only last 100 entries
if (history.length > 100) {
history.shift();
}
this.performanceHistory.set(agentId, history);
}
updateResourceUsage(agentId, usage) {
this.resourceUsage.set(agentId, usage);
}
addAgentError(agentId, error) {
const agent = this.agents.get(agentId);
if (!agent)
return;
agent.errorHistory.push(error);
// Keep only last 50 errors
if (agent.errorHistory.length > 50) {
agent.errorHistory.shift();
}
}
createDefaultMetrics() {
return {
tasksCompleted: 0,
tasksFailed: 0,
averageExecutionTime: 0,
successRate: 1.0,
cpuUsage: 0,
memoryUsage: 0,
diskUsage: 0,
networkUsage: 0,
codeQuality: 0.8,
testCoverage: 0,
bugRate: 0,
userSatisfaction: 0.8,
totalUptime: 0,
lastActivity: new Date(),
responseTime: 0,
};
}
createDefaultHealth(agentId) {
return {
agentId,
overall: 1.0,
components: {
responsiveness: 1.0,
performance: 1.0,
reliability: 1.0,
resourceUsage: 1.0,
},
issues: [],
lastCheck: new Date(),
trend: "stable",
};
}
removeAgentFromPoolsAndClusters(agentId) {
// Remove from pools
Array.from(this.pools.values()).forEach(pool => {
pool.availableAgents = pool.availableAgents.filter(a => a.id !== agentId);
pool.busyAgents = pool.busyAgents.filter(a => a.id !== agentId);
pool.currentSize = pool.availableAgents.length + pool.busyAgents.length;
});
// Remove from clusters
Array.from(this.clusters.values()).forEach(cluster => {
cluster.agents = cluster.agents.filter(a => a.id !== agentId);
});
}
initializeScalingPolicies() {
// Default auto-scaling policy
const defaultPolicy = {
name: "default-autoscale",
enabled: true,
cooldownPeriod: 300000, // 5 minutes
maxScaleOperations: 10,
rules: [
{
metric: "pool-utilization",
threshold: 0.8,
comparison: "gt",
action: "scale-up",
amount: 1,
},
{
metric: "pool-utilization",
threshold: 0.3,
comparison: "lt",
action: "scale-down",
amount: 1,
},
],
};
this.scalingPolicies.set("default", defaultPolicy);
}
// === PUBLIC API ===
getAgent(agentId) {
return this.agents.get(agentId);
}
getAllAgents() {
return Array.from(this.agents.values());
}
getAgentsByType(type) {
return Array.from(this.agents.values()).filter(agent => agent.type === type);
}
getAgentsByStatus(status) {
return Array.from(this.agents.values()).filter(agent => agent.status === status);
}
getAgentHealth(agentId) {
return this.healthChecks.get(agentId);
}
getPool(poolId) {
return this.pools.get(poolId);
}
getAllPools() {
return Array.from(this.pools.values());
}
getAgentTemplates() {
return Array.from(this.templates.values());
}
getSystemStats() {
const agents = Array.from(this.agents.values());
const healthChecks = Array.from(this.healthChecks.values());
const healthyAgents = healthChecks.filter(h => h.overall > 0.7).length;
const averageHealth = healthChecks.reduce((sum, h) => sum + h.overall, 0) / healthChecks.length || 1;
const resourceUsages = Array.from(this.resourceUsage.values());
const avgCpu = resourceUsages.reduce((sum, r) => sum + r.cpu, 0) / resourceUsages.length || 0;
const avgMemory = resourceUsages.reduce((sum, r) => sum + r.memory, 0) / resourceUsages.length || 0;
const avgDisk = resourceUsages.reduce((sum, r) => sum + r.disk, 0) / resourceUsages.length || 0;
return {
totalAgents: agents.length,
activeAgents: agents.filter(a => a.status === "idle" || a.status === "busy").length,
healthyAgents,
pools: this.pools.size,
clusters: this.clusters.size,
averageHealth,
resourceUtilization: {
cpu: avgCpu,
memory: avgMemory,
disk: avgDisk,
},
};
}
}
//# sourceMappingURL=agent-manager.js.map