Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
965
vendor/ruvector/examples/edge-net/pkg/agents.js
vendored
Normal file
965
vendor/ruvector/examples/edge-net/pkg/agents.js
vendored
Normal file
@@ -0,0 +1,965 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Edge-Net Agent System
|
||||
*
|
||||
* Distributed AI agent execution across the Edge-Net collective.
|
||||
* Spawn agents, create worker pools, and orchestrate multi-agent workflows.
|
||||
*/
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import { randomBytes, createHash } from 'crypto';
|
||||
|
||||
// Agent types and their capabilities
|
||||
export const AGENT_TYPES = {
|
||||
researcher: {
|
||||
name: 'Researcher',
|
||||
capabilities: ['search', 'analyze', 'summarize', 'extract'],
|
||||
baseRuv: 10,
|
||||
description: 'Analyzes and researches information',
|
||||
},
|
||||
coder: {
|
||||
name: 'Coder',
|
||||
capabilities: ['code', 'refactor', 'debug', 'test'],
|
||||
baseRuv: 15,
|
||||
description: 'Writes and improves code',
|
||||
},
|
||||
reviewer: {
|
||||
name: 'Reviewer',
|
||||
capabilities: ['review', 'audit', 'validate', 'suggest'],
|
||||
baseRuv: 12,
|
||||
description: 'Reviews code and provides feedback',
|
||||
},
|
||||
tester: {
|
||||
name: 'Tester',
|
||||
capabilities: ['test', 'benchmark', 'validate', 'report'],
|
||||
baseRuv: 10,
|
||||
description: 'Tests and validates implementations',
|
||||
},
|
||||
analyst: {
|
||||
name: 'Analyst',
|
||||
capabilities: ['analyze', 'metrics', 'report', 'visualize'],
|
||||
baseRuv: 8,
|
||||
description: 'Analyzes data and generates reports',
|
||||
},
|
||||
optimizer: {
|
||||
name: 'Optimizer',
|
||||
capabilities: ['optimize', 'profile', 'benchmark', 'improve'],
|
||||
baseRuv: 15,
|
||||
description: 'Optimizes performance and efficiency',
|
||||
},
|
||||
coordinator: {
|
||||
name: 'Coordinator',
|
||||
capabilities: ['orchestrate', 'route', 'schedule', 'monitor'],
|
||||
baseRuv: 20,
|
||||
description: 'Coordinates multi-agent workflows',
|
||||
},
|
||||
embedder: {
|
||||
name: 'Embedder',
|
||||
capabilities: ['embed', 'vectorize', 'similarity', 'search'],
|
||||
baseRuv: 5,
|
||||
description: 'Generates embeddings and vector operations',
|
||||
},
|
||||
};
|
||||
|
||||
// Task status enum
|
||||
export const TaskStatus = {
|
||||
PENDING: 'pending',
|
||||
QUEUED: 'queued',
|
||||
ASSIGNED: 'assigned',
|
||||
RUNNING: 'running',
|
||||
COMPLETED: 'completed',
|
||||
FAILED: 'failed',
|
||||
CANCELLED: 'cancelled',
|
||||
};
|
||||
|
||||
/**
|
||||
* Distributed Agent
|
||||
*
|
||||
* Represents an AI agent running on the Edge-Net network.
|
||||
*/
|
||||
export class DistributedAgent extends EventEmitter {
|
||||
constructor(options) {
|
||||
super();
|
||||
this.id = `agent-${randomBytes(8).toString('hex')}`;
|
||||
this.type = options.type || 'researcher';
|
||||
this.task = options.task;
|
||||
this.config = AGENT_TYPES[this.type] || AGENT_TYPES.researcher;
|
||||
this.maxRuv = options.maxRuv || this.config.baseRuv;
|
||||
this.priority = options.priority || 'medium';
|
||||
this.timeout = options.timeout || 300000; // 5 min default
|
||||
|
||||
this.status = TaskStatus.PENDING;
|
||||
this.assignedNode = null;
|
||||
this.progress = 0;
|
||||
this.result = null;
|
||||
this.error = null;
|
||||
this.startTime = null;
|
||||
this.endTime = null;
|
||||
this.ruvSpent = 0;
|
||||
|
||||
this.subtasks = [];
|
||||
this.logs = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get agent info
|
||||
*/
|
||||
getInfo() {
|
||||
return {
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
task: this.task,
|
||||
status: this.status,
|
||||
progress: this.progress,
|
||||
assignedNode: this.assignedNode,
|
||||
maxRuv: this.maxRuv,
|
||||
ruvSpent: this.ruvSpent,
|
||||
startTime: this.startTime,
|
||||
endTime: this.endTime,
|
||||
duration: this.endTime && this.startTime
|
||||
? this.endTime - this.startTime
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update agent progress
|
||||
*/
|
||||
updateProgress(progress, message) {
|
||||
this.progress = Math.min(100, Math.max(0, progress));
|
||||
this.log(`Progress: ${this.progress}% - ${message}`);
|
||||
this.emit('progress', { progress: this.progress, message });
|
||||
}
|
||||
|
||||
/**
|
||||
* Log message
|
||||
*/
|
||||
log(message) {
|
||||
const entry = {
|
||||
timestamp: Date.now(),
|
||||
message,
|
||||
};
|
||||
this.logs.push(entry);
|
||||
this.emit('log', entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark as completed
|
||||
*/
|
||||
complete(result) {
|
||||
this.status = TaskStatus.COMPLETED;
|
||||
this.result = result;
|
||||
this.progress = 100;
|
||||
this.endTime = Date.now();
|
||||
this.log('Agent completed successfully');
|
||||
this.emit('complete', result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark as failed
|
||||
*/
|
||||
fail(error) {
|
||||
this.status = TaskStatus.FAILED;
|
||||
this.error = error;
|
||||
this.endTime = Date.now();
|
||||
this.log(`Agent failed: ${error}`);
|
||||
this.emit('error', error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the agent
|
||||
*/
|
||||
cancel() {
|
||||
this.status = TaskStatus.CANCELLED;
|
||||
this.endTime = Date.now();
|
||||
this.log('Agent cancelled');
|
||||
this.emit('cancelled');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Agent Spawner
|
||||
*
|
||||
* Spawns and manages distributed agents across the Edge-Net network.
|
||||
*/
|
||||
export class AgentSpawner extends EventEmitter {
|
||||
constructor(networkManager, options = {}) {
|
||||
super();
|
||||
this.network = networkManager;
|
||||
this.agents = new Map();
|
||||
this.pendingQueue = [];
|
||||
this.maxConcurrent = options.maxConcurrent || 10;
|
||||
this.defaultTimeout = options.defaultTimeout || 300000;
|
||||
|
||||
// Agent routing table (learned from outcomes)
|
||||
this.routingTable = new Map();
|
||||
|
||||
// Stats
|
||||
this.stats = {
|
||||
totalSpawned: 0,
|
||||
completed: 0,
|
||||
failed: 0,
|
||||
totalRuvSpent: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn a new agent on the network
|
||||
*/
|
||||
async spawn(options) {
|
||||
const agent = new DistributedAgent({
|
||||
...options,
|
||||
timeout: options.timeout || this.defaultTimeout,
|
||||
});
|
||||
|
||||
this.agents.set(agent.id, agent);
|
||||
this.stats.totalSpawned++;
|
||||
|
||||
agent.log(`Agent spawned: ${agent.type} - ${agent.task}`);
|
||||
agent.status = TaskStatus.QUEUED;
|
||||
|
||||
// Find best node for this agent type
|
||||
const targetNode = await this.findBestNode(agent);
|
||||
|
||||
if (targetNode) {
|
||||
await this.assignToNode(agent, targetNode);
|
||||
} else {
|
||||
// Queue for later assignment
|
||||
this.pendingQueue.push(agent);
|
||||
agent.log('Queued - waiting for available node');
|
||||
}
|
||||
|
||||
this.emit('agent-spawned', agent);
|
||||
return agent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the best node for an agent based on capabilities and load
|
||||
*/
|
||||
async findBestNode(agent) {
|
||||
if (!this.network) return null;
|
||||
|
||||
const peers = this.network.getPeerList ?
|
||||
this.network.getPeerList() :
|
||||
Array.from(this.network.peers?.values() || []);
|
||||
|
||||
if (peers.length === 0) return null;
|
||||
|
||||
// Score each peer based on:
|
||||
// 1. Capability match
|
||||
// 2. Current load
|
||||
// 3. Historical performance
|
||||
// 4. Latency
|
||||
const scoredPeers = peers.map(peer => {
|
||||
let score = 50; // Base score
|
||||
|
||||
// Check capabilities
|
||||
const peerCaps = peer.capabilities || [];
|
||||
const requiredCaps = agent.config.capabilities;
|
||||
const capMatch = requiredCaps.filter(c => peerCaps.includes(c)).length;
|
||||
score += capMatch * 10;
|
||||
|
||||
// Check load (lower is better)
|
||||
const load = peer.load || 0;
|
||||
score -= load * 20;
|
||||
|
||||
// Check historical performance
|
||||
const history = this.routingTable.get(`${peer.piKey || peer.id}-${agent.type}`);
|
||||
if (history) {
|
||||
score += history.successRate * 30;
|
||||
score -= history.avgLatency / 1000; // Penalize high latency
|
||||
}
|
||||
|
||||
return { peer, score };
|
||||
});
|
||||
|
||||
// Sort by score (highest first)
|
||||
scoredPeers.sort((a, b) => b.score - a.score);
|
||||
|
||||
return scoredPeers[0]?.peer || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign agent to a specific node
|
||||
*/
|
||||
async assignToNode(agent, node) {
|
||||
agent.status = TaskStatus.ASSIGNED;
|
||||
agent.assignedNode = node.piKey || node.id;
|
||||
agent.startTime = Date.now();
|
||||
agent.log(`Assigned to node: ${agent.assignedNode.slice(0, 12)}...`);
|
||||
|
||||
// Send task to node via network
|
||||
if (this.network?.sendToPeer) {
|
||||
await this.network.sendToPeer(agent.assignedNode, {
|
||||
type: 'agent_task',
|
||||
agentId: agent.id,
|
||||
agentType: agent.type,
|
||||
task: agent.task,
|
||||
maxRuv: agent.maxRuv,
|
||||
timeout: agent.timeout,
|
||||
});
|
||||
}
|
||||
|
||||
agent.status = TaskStatus.RUNNING;
|
||||
this.emit('agent-assigned', { agent, node });
|
||||
|
||||
// Set timeout
|
||||
setTimeout(() => {
|
||||
if (agent.status === TaskStatus.RUNNING) {
|
||||
agent.fail('Timeout exceeded');
|
||||
}
|
||||
}, agent.timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle task result from network
|
||||
*/
|
||||
handleResult(agentId, result) {
|
||||
const agent = this.agents.get(agentId);
|
||||
if (!agent) return;
|
||||
|
||||
if (result.success) {
|
||||
agent.complete(result.data);
|
||||
this.stats.completed++;
|
||||
this.updateRoutingTable(agent, true, result.latency);
|
||||
} else {
|
||||
agent.fail(result.error);
|
||||
this.stats.failed++;
|
||||
this.updateRoutingTable(agent, false, result.latency);
|
||||
}
|
||||
|
||||
agent.ruvSpent = result.ruvSpent || agent.config.baseRuv;
|
||||
this.stats.totalRuvSpent += agent.ruvSpent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update routing table with outcome
|
||||
*/
|
||||
updateRoutingTable(agent, success, latency) {
|
||||
const key = `${agent.assignedNode}-${agent.type}`;
|
||||
const existing = this.routingTable.get(key) || {
|
||||
attempts: 0,
|
||||
successes: 0,
|
||||
totalLatency: 0,
|
||||
};
|
||||
|
||||
existing.attempts++;
|
||||
if (success) existing.successes++;
|
||||
existing.totalLatency += latency || 0;
|
||||
existing.successRate = existing.successes / existing.attempts;
|
||||
existing.avgLatency = existing.totalLatency / existing.attempts;
|
||||
|
||||
this.routingTable.set(key, existing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get agent by ID
|
||||
*/
|
||||
getAgent(agentId) {
|
||||
return this.agents.get(agentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all agents
|
||||
*/
|
||||
listAgents(filter = {}) {
|
||||
let agents = Array.from(this.agents.values());
|
||||
|
||||
if (filter.status) {
|
||||
agents = agents.filter(a => a.status === filter.status);
|
||||
}
|
||||
if (filter.type) {
|
||||
agents = agents.filter(a => a.type === filter.type);
|
||||
}
|
||||
|
||||
return agents.map(a => a.getInfo());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get spawner stats
|
||||
*/
|
||||
getStats() {
|
||||
return {
|
||||
...this.stats,
|
||||
activeAgents: Array.from(this.agents.values())
|
||||
.filter(a => a.status === TaskStatus.RUNNING).length,
|
||||
queuedAgents: this.pendingQueue.length,
|
||||
successRate: this.stats.completed /
|
||||
(this.stats.completed + this.stats.failed) || 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Worker Pool
|
||||
*
|
||||
* Manages a pool of distributed workers for parallel task execution.
|
||||
*/
|
||||
export class WorkerPool extends EventEmitter {
|
||||
constructor(networkManager, options = {}) {
|
||||
super();
|
||||
this.id = `pool-${randomBytes(6).toString('hex')}`;
|
||||
this.network = networkManager;
|
||||
this.size = options.size || 5;
|
||||
this.capabilities = options.capabilities || ['compute', 'embed'];
|
||||
this.maxTasksPerWorker = options.maxTasksPerWorker || 10;
|
||||
|
||||
this.workers = new Map();
|
||||
this.taskQueue = [];
|
||||
this.activeTasks = new Map();
|
||||
this.results = new Map();
|
||||
|
||||
this.status = 'initializing';
|
||||
this.stats = {
|
||||
tasksCompleted: 0,
|
||||
tasksFailed: 0,
|
||||
totalProcessingTime: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the worker pool
|
||||
*/
|
||||
async initialize() {
|
||||
this.status = 'recruiting';
|
||||
this.emit('status', 'Recruiting workers...');
|
||||
|
||||
// Find available workers from network
|
||||
const peers = this.network?.getPeerList?.() ||
|
||||
Array.from(this.network?.peers?.values() || []);
|
||||
|
||||
// Filter peers by capabilities
|
||||
const eligiblePeers = peers.filter(peer => {
|
||||
const peerCaps = peer.capabilities || [];
|
||||
return this.capabilities.some(c => peerCaps.includes(c));
|
||||
});
|
||||
|
||||
// Recruit up to pool size
|
||||
const recruited = eligiblePeers.slice(0, this.size);
|
||||
|
||||
for (const peer of recruited) {
|
||||
this.workers.set(peer.piKey || peer.id, {
|
||||
id: peer.piKey || peer.id,
|
||||
peer,
|
||||
status: 'idle',
|
||||
currentTasks: 0,
|
||||
completedTasks: 0,
|
||||
lastSeen: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
// If not enough real workers, create virtual workers for local execution
|
||||
while (this.workers.size < this.size) {
|
||||
const virtualId = `virtual-${randomBytes(4).toString('hex')}`;
|
||||
this.workers.set(virtualId, {
|
||||
id: virtualId,
|
||||
peer: null,
|
||||
status: 'idle',
|
||||
currentTasks: 0,
|
||||
completedTasks: 0,
|
||||
isVirtual: true,
|
||||
});
|
||||
}
|
||||
|
||||
this.status = 'ready';
|
||||
this.emit('ready', {
|
||||
poolId: this.id,
|
||||
workers: this.workers.size,
|
||||
realWorkers: Array.from(this.workers.values())
|
||||
.filter(w => !w.isVirtual).length,
|
||||
});
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute tasks in parallel across workers
|
||||
*/
|
||||
async execute(options) {
|
||||
const {
|
||||
task,
|
||||
data,
|
||||
strategy = 'parallel',
|
||||
chunkSize = null,
|
||||
} = options;
|
||||
|
||||
const batchId = `batch-${randomBytes(6).toString('hex')}`;
|
||||
const startTime = Date.now();
|
||||
|
||||
// Split data into chunks for workers
|
||||
let chunks;
|
||||
if (Array.isArray(data)) {
|
||||
const size = chunkSize || Math.ceil(data.length / this.workers.size);
|
||||
chunks = [];
|
||||
for (let i = 0; i < data.length; i += size) {
|
||||
chunks.push(data.slice(i, i + size));
|
||||
}
|
||||
} else {
|
||||
chunks = [data];
|
||||
}
|
||||
|
||||
this.emit('batch-start', { batchId, chunks: chunks.length });
|
||||
|
||||
// Assign chunks to workers
|
||||
const promises = chunks.map((chunk, index) =>
|
||||
this.assignTask({
|
||||
batchId,
|
||||
index,
|
||||
task,
|
||||
data: chunk,
|
||||
})
|
||||
);
|
||||
|
||||
// Wait for all or handle based on strategy
|
||||
let results;
|
||||
if (strategy === 'parallel') {
|
||||
results = await Promise.all(promises);
|
||||
} else if (strategy === 'race') {
|
||||
results = [await Promise.race(promises)];
|
||||
} else {
|
||||
// Sequential
|
||||
results = [];
|
||||
for (const promise of promises) {
|
||||
results.push(await promise);
|
||||
}
|
||||
}
|
||||
|
||||
const endTime = Date.now();
|
||||
this.stats.totalProcessingTime += endTime - startTime;
|
||||
|
||||
this.emit('batch-complete', {
|
||||
batchId,
|
||||
duration: endTime - startTime,
|
||||
results: results.length,
|
||||
});
|
||||
|
||||
// Flatten results if array
|
||||
return Array.isArray(data) ? results.flat() : results[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a single task to an available worker
|
||||
*/
|
||||
async assignTask(taskInfo) {
|
||||
const taskId = `task-${randomBytes(6).toString('hex')}`;
|
||||
|
||||
// Find idle worker
|
||||
const worker = this.findIdleWorker();
|
||||
if (!worker) {
|
||||
// Queue task
|
||||
return new Promise((resolve, reject) => {
|
||||
this.taskQueue.push({ taskInfo, resolve, reject });
|
||||
});
|
||||
}
|
||||
|
||||
worker.status = 'busy';
|
||||
worker.currentTasks++;
|
||||
|
||||
this.activeTasks.set(taskId, {
|
||||
...taskInfo,
|
||||
workerId: worker.id,
|
||||
startTime: Date.now(),
|
||||
});
|
||||
|
||||
try {
|
||||
// Execute on worker
|
||||
const result = await this.executeOnWorker(worker, taskInfo);
|
||||
|
||||
worker.completedTasks++;
|
||||
this.stats.tasksCompleted++;
|
||||
this.results.set(taskId, result);
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.stats.tasksFailed++;
|
||||
throw error;
|
||||
} finally {
|
||||
worker.currentTasks--;
|
||||
if (worker.currentTasks === 0) {
|
||||
worker.status = 'idle';
|
||||
}
|
||||
this.activeTasks.delete(taskId);
|
||||
|
||||
// Process queued task if any
|
||||
if (this.taskQueue.length > 0) {
|
||||
const { taskInfo, resolve, reject } = this.taskQueue.shift();
|
||||
this.assignTask(taskInfo).then(resolve).catch(reject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an idle worker
|
||||
*/
|
||||
findIdleWorker() {
|
||||
for (const worker of this.workers.values()) {
|
||||
if (worker.status === 'idle' ||
|
||||
worker.currentTasks < this.maxTasksPerWorker) {
|
||||
return worker;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute task on a specific worker
|
||||
*/
|
||||
async executeOnWorker(worker, taskInfo) {
|
||||
if (worker.isVirtual) {
|
||||
// Local execution for virtual workers
|
||||
return this.executeLocally(taskInfo);
|
||||
}
|
||||
|
||||
// Send to remote worker via network
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('Worker timeout'));
|
||||
}, 60000);
|
||||
|
||||
// Send task
|
||||
if (this.network?.sendToPeer) {
|
||||
this.network.sendToPeer(worker.id, {
|
||||
type: 'worker_task',
|
||||
poolId: this.id,
|
||||
task: taskInfo.task,
|
||||
data: taskInfo.data,
|
||||
});
|
||||
}
|
||||
|
||||
// Listen for result
|
||||
const handler = (msg) => {
|
||||
if (msg.poolId === this.id && msg.batchId === taskInfo.batchId) {
|
||||
clearTimeout(timeout);
|
||||
this.network?.off?.('worker_result', handler);
|
||||
resolve(msg.result);
|
||||
}
|
||||
};
|
||||
|
||||
this.network?.on?.('worker_result', handler);
|
||||
|
||||
// Fallback to local if no response
|
||||
setTimeout(() => {
|
||||
clearTimeout(timeout);
|
||||
this.executeLocally(taskInfo).then(resolve).catch(reject);
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute task locally (for virtual workers or fallback)
|
||||
*/
|
||||
async executeLocally(taskInfo) {
|
||||
const { task, data } = taskInfo;
|
||||
|
||||
// Simple local execution based on task type
|
||||
switch (task) {
|
||||
case 'embed':
|
||||
// Simulate embedding
|
||||
return Array.isArray(data)
|
||||
? data.map(() => new Array(384).fill(0).map(() => Math.random()))
|
||||
: new Array(384).fill(0).map(() => Math.random());
|
||||
|
||||
case 'process':
|
||||
return Array.isArray(data)
|
||||
? data.map(item => ({ processed: true, item }))
|
||||
: { processed: true, data };
|
||||
|
||||
case 'analyze':
|
||||
return {
|
||||
analyzed: true,
|
||||
itemCount: Array.isArray(data) ? data.length : 1,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
default:
|
||||
return { task, data, executed: true };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pool status
|
||||
*/
|
||||
getStatus() {
|
||||
const workers = Array.from(this.workers.values());
|
||||
return {
|
||||
poolId: this.id,
|
||||
status: this.status,
|
||||
totalWorkers: workers.length,
|
||||
idleWorkers: workers.filter(w => w.status === 'idle').length,
|
||||
busyWorkers: workers.filter(w => w.status === 'busy').length,
|
||||
virtualWorkers: workers.filter(w => w.isVirtual).length,
|
||||
queuedTasks: this.taskQueue.length,
|
||||
activeTasks: this.activeTasks.size,
|
||||
stats: this.stats,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the pool
|
||||
*/
|
||||
async shutdown() {
|
||||
this.status = 'shutting_down';
|
||||
|
||||
// Wait for active tasks
|
||||
while (this.activeTasks.size > 0) {
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
}
|
||||
|
||||
// Clear workers
|
||||
this.workers.clear();
|
||||
this.status = 'shutdown';
|
||||
this.emit('shutdown');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Task Orchestrator
|
||||
*
|
||||
* Orchestrates multi-agent workflows and complex task pipelines.
|
||||
*/
|
||||
export class TaskOrchestrator extends EventEmitter {
|
||||
constructor(agentSpawner, workerPool, options = {}) {
|
||||
super();
|
||||
this.spawner = agentSpawner;
|
||||
this.pool = workerPool;
|
||||
this.workflows = new Map();
|
||||
this.maxConcurrentWorkflows = options.maxConcurrentWorkflows || 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a workflow
|
||||
*/
|
||||
createWorkflow(name, steps) {
|
||||
const workflow = {
|
||||
id: `wf-${randomBytes(6).toString('hex')}`,
|
||||
name,
|
||||
steps,
|
||||
status: 'created',
|
||||
currentStep: 0,
|
||||
results: [],
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
};
|
||||
|
||||
this.workflows.set(workflow.id, workflow);
|
||||
return workflow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a workflow
|
||||
*/
|
||||
async executeWorkflow(workflowId, input = {}) {
|
||||
const workflow = this.workflows.get(workflowId);
|
||||
if (!workflow) throw new Error('Workflow not found');
|
||||
|
||||
workflow.status = 'running';
|
||||
workflow.startTime = Date.now();
|
||||
workflow.input = input;
|
||||
|
||||
this.emit('workflow-start', { workflowId, name: workflow.name });
|
||||
|
||||
try {
|
||||
let context = { ...input };
|
||||
|
||||
for (let i = 0; i < workflow.steps.length; i++) {
|
||||
workflow.currentStep = i;
|
||||
const step = workflow.steps[i];
|
||||
|
||||
this.emit('step-start', {
|
||||
workflowId,
|
||||
step: i,
|
||||
type: step.type,
|
||||
name: step.name,
|
||||
});
|
||||
|
||||
const result = await this.executeStep(step, context);
|
||||
workflow.results.push(result);
|
||||
|
||||
// Pass result to next step
|
||||
context = { ...context, [step.name || `step${i}`]: result };
|
||||
|
||||
this.emit('step-complete', {
|
||||
workflowId,
|
||||
step: i,
|
||||
result,
|
||||
});
|
||||
}
|
||||
|
||||
workflow.status = 'completed';
|
||||
workflow.endTime = Date.now();
|
||||
|
||||
this.emit('workflow-complete', {
|
||||
workflowId,
|
||||
duration: workflow.endTime - workflow.startTime,
|
||||
results: workflow.results,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
results: workflow.results,
|
||||
context,
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
workflow.status = 'failed';
|
||||
workflow.endTime = Date.now();
|
||||
workflow.error = error.message;
|
||||
|
||||
this.emit('workflow-failed', { workflowId, error: error.message });
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
failedStep: workflow.currentStep,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a single workflow step
|
||||
*/
|
||||
async executeStep(step, context) {
|
||||
switch (step.type) {
|
||||
case 'agent':
|
||||
return this.executeAgentStep(step, context);
|
||||
|
||||
case 'parallel':
|
||||
return this.executeParallelStep(step, context);
|
||||
|
||||
case 'pool':
|
||||
return this.executePoolStep(step, context);
|
||||
|
||||
case 'condition':
|
||||
return this.executeConditionStep(step, context);
|
||||
|
||||
case 'transform':
|
||||
return this.executeTransformStep(step, context);
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown step type: ${step.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an agent step
|
||||
*/
|
||||
async executeAgentStep(step, context) {
|
||||
const task = typeof step.task === 'function'
|
||||
? step.task(context)
|
||||
: step.task;
|
||||
|
||||
const agent = await this.spawner.spawn({
|
||||
type: step.agentType || 'researcher',
|
||||
task,
|
||||
maxRuv: step.maxRuv,
|
||||
priority: step.priority,
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
agent.on('complete', resolve);
|
||||
agent.on('error', reject);
|
||||
|
||||
// Simulate completion for now
|
||||
setTimeout(() => {
|
||||
agent.complete({
|
||||
task,
|
||||
result: `Completed: ${task}`,
|
||||
context,
|
||||
});
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute parallel agents
|
||||
*/
|
||||
async executeParallelStep(step, context) {
|
||||
const promises = step.agents.map(agentConfig =>
|
||||
this.executeAgentStep(agentConfig, context)
|
||||
);
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute worker pool step
|
||||
*/
|
||||
async executePoolStep(step, context) {
|
||||
const data = typeof step.data === 'function'
|
||||
? step.data(context)
|
||||
: step.data || context.data;
|
||||
|
||||
return this.pool.execute({
|
||||
task: step.task,
|
||||
data,
|
||||
strategy: step.strategy || 'parallel',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute conditional step
|
||||
*/
|
||||
async executeConditionStep(step, context) {
|
||||
const condition = typeof step.condition === 'function'
|
||||
? step.condition(context)
|
||||
: step.condition;
|
||||
|
||||
if (condition) {
|
||||
return this.executeStep(step.then, context);
|
||||
} else if (step.else) {
|
||||
return this.executeStep(step.else, context);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute transform step
|
||||
*/
|
||||
async executeTransformStep(step, context) {
|
||||
return step.transform(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get workflow status
|
||||
*/
|
||||
getWorkflowStatus(workflowId) {
|
||||
const workflow = this.workflows.get(workflowId);
|
||||
if (!workflow) return null;
|
||||
|
||||
return {
|
||||
id: workflow.id,
|
||||
name: workflow.name,
|
||||
status: workflow.status,
|
||||
currentStep: workflow.currentStep,
|
||||
totalSteps: workflow.steps.length,
|
||||
progress: (workflow.currentStep / workflow.steps.length) * 100,
|
||||
startTime: workflow.startTime,
|
||||
endTime: workflow.endTime,
|
||||
duration: workflow.endTime && workflow.startTime
|
||||
? workflow.endTime - workflow.startTime
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* List all workflows
|
||||
*/
|
||||
listWorkflows() {
|
||||
return Array.from(this.workflows.values()).map(w => ({
|
||||
id: w.id,
|
||||
name: w.name,
|
||||
status: w.status,
|
||||
steps: w.steps.length,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// Export default instances
|
||||
export default {
|
||||
AGENT_TYPES,
|
||||
TaskStatus,
|
||||
DistributedAgent,
|
||||
AgentSpawner,
|
||||
WorkerPool,
|
||||
TaskOrchestrator,
|
||||
};
|
||||
Reference in New Issue
Block a user