claude-flow-multilang
Version:
Revolutionary multilingual AI orchestration framework with cultural awareness and DDD architecture
219 lines (175 loc) • 6.14 kB
text/typescript
/**
* Work stealing algorithm for load balancing between agents
*/
import type { Task, AgentProfile } from '../utils/types.js';
import type { IEventBus } from '../core/event-bus.js';
import type { ILogger } from '../core/logger.js';
export interface WorkStealingConfig {
enabled: boolean;
stealThreshold: number; // Min difference in task count to trigger stealing
maxStealBatch: number; // Max tasks to steal at once
stealInterval: number; // How often to check for steal opportunities (ms)
}
export interface AgentWorkload {
agentId: string;
taskCount: number;
avgTaskDuration: number;
cpuUsage: number;
memoryUsage: number;
priority: number;
capabilities: string[];
}
/**
* Work stealing coordinator for load balancing
*/
export class WorkStealingCoordinator {
private workloads = new Map<string, AgentWorkload>();
private stealInterval?: ReturnType<typeof setInterval>;
private taskDurations = new Map<string, number[]>(); // agentId -> task durations
constructor(
private config: WorkStealingConfig,
private eventBus: IEventBus,
private logger: ILogger,
) {}
async initialize(): Promise<void> {
if (!this.config.enabled) {
this.logger.info('Work stealing is disabled');
return;
}
this.logger.info('Initializing work stealing coordinator');
// Start periodic steal checks
this.stealInterval = setInterval(() => this.checkAndSteal(), this.config.stealInterval);
}
async shutdown(): Promise<void> {
if (this.stealInterval) {
clearInterval(this.stealInterval);
}
this.workloads.clear();
this.taskDurations.clear();
}
updateAgentWorkload(agentId: string, workload: Partial<AgentWorkload>): void {
const existing = this.workloads.get(agentId) || {
agentId,
taskCount: 0,
avgTaskDuration: 0,
cpuUsage: 0,
memoryUsage: 0,
priority: 0,
capabilities: [],
};
this.workloads.set(agentId, { ...existing, ...workload });
}
recordTaskDuration(agentId: string, duration: number): void {
if (!this.taskDurations.has(agentId)) {
this.taskDurations.set(agentId, []);
}
const durations = this.taskDurations.get(agentId)!;
durations.push(duration);
// Keep only last 100 durations
if (durations.length > 100) {
durations.shift();
}
// Update average duration
const avg = durations.reduce((sum, d) => sum + d, 0) / durations.length;
this.updateAgentWorkload(agentId, { avgTaskDuration: avg });
}
async checkAndSteal(): Promise<void> {
const workloads = Array.from(this.workloads.values());
if (workloads.length < 2) {
return; // Need at least 2 agents
}
// Sort by task count (ascending)
workloads.sort((a, b) => a.taskCount - b.taskCount);
const minLoaded = workloads[0];
const maxLoaded = workloads[workloads.length - 1];
// Check if stealing is warranted
const difference = maxLoaded.taskCount - minLoaded.taskCount;
if (difference < this.config.stealThreshold) {
return; // Not enough imbalance
}
// Calculate how many tasks to steal
const tasksToSteal = Math.min(Math.floor(difference / 2), this.config.maxStealBatch);
this.logger.info('Initiating work stealing', {
from: maxLoaded.agentId,
to: minLoaded.agentId,
tasksToSteal,
difference,
});
// Emit steal request event
this.eventBus.emit('workstealing:request', {
sourceAgent: maxLoaded.agentId,
targetAgent: minLoaded.agentId,
taskCount: tasksToSteal,
});
}
/**
* Find the best agent for a task based on capabilities and load
*/
findBestAgent(task: Task, agents: AgentProfile[]): string | null {
const candidates: Array<{
agentId: string;
score: number;
}> = [];
for (const agent of agents) {
const workload = this.workloads.get(agent.id);
if (!workload) {
continue;
}
// Calculate score based on multiple factors
let score = 100;
// Factor 1: Task count (lower is better)
score -= workload.taskCount * 10;
// Factor 2: CPU usage (lower is better)
score -= workload.cpuUsage * 0.5;
// Factor 3: Memory usage (lower is better)
score -= workload.memoryUsage * 0.3;
// Factor 4: Agent priority (higher is better)
score += agent.priority * 5;
// Factor 5: Capability match
const taskType = task.type;
if (agent.capabilities.includes(taskType)) {
score += 20; // Bonus for capability match
}
// Factor 6: Average task duration (predictive load)
const predictedLoad = workload.avgTaskDuration * workload.taskCount;
score -= predictedLoad / 1000; // Convert to seconds
candidates.push({ agentId: agent.id, score });
}
if (candidates.length === 0) {
return null;
}
// Sort by score (descending) and return best
candidates.sort((a, b) => b.score - a.score);
this.logger.debug('Agent selection scores', {
taskId: task.id,
candidates: candidates.slice(0, 5), // Top 5
});
return candidates[0].agentId;
}
getWorkloadStats(): Record<string, unknown> {
const stats: Record<string, unknown> = {
totalAgents: this.workloads.size,
workloads: {},
};
let totalTasks = 0;
let minTasks = Infinity;
let maxTasks = 0;
for (const [agentId, workload] of this.workloads) {
totalTasks += workload.taskCount;
minTasks = Math.min(minTasks, workload.taskCount);
maxTasks = Math.max(maxTasks, workload.taskCount);
(stats.workloads as Record<string, unknown>)[agentId] = {
taskCount: workload.taskCount,
avgTaskDuration: workload.avgTaskDuration,
cpuUsage: workload.cpuUsage,
memoryUsage: workload.memoryUsage,
};
}
stats.totalTasks = totalTasks;
stats.avgTasksPerAgent = totalTasks / this.workloads.size;
stats.minTasks = minTasks === Infinity ? 0 : minTasks;
stats.maxTasks = maxTasks;
stats.imbalance = maxTasks - (minTasks === Infinity ? 0 : minTasks);
return stats;
}
}