Complete Agent Pipeline System with Claude Code & OpenClaw Integration

- Added Claude Code integration with full context compaction support
- Added OpenClaw integration with deterministic pipeline support
- Implemented parallel agent execution (4 projects x 3 roles pattern)
- Added workspace isolation with permissions and quotas
- Implemented Lobster-compatible YAML workflow parser
- Added persistent memory store for cross-session context
- Created comprehensive README with hero section

This project was 100% autonomously built by Z.AI GLM-5
This commit is contained in:
admin
2026-03-03 13:12:14 +00:00
Unverified
commit c629646b9f
23 changed files with 9518 additions and 0 deletions

View File

@@ -0,0 +1,653 @@
/**
* Deterministic State Machine Core
*
* A state machine that controls agent flow WITHOUT LLM decision-making.
* States, transitions, and events are defined declaratively.
*
* Key principle: The LLM does creative work, the state machine handles the plumbing.
*/
import { randomUUID } from 'crypto';
import { EventEmitter } from 'events';
// ============================================================================
// Types
// ============================================================================
export type StateStatus = 'idle' | 'active' | 'waiting' | 'completed' | 'failed' | 'paused';
export interface State {
id: string;
name: string;
type: 'start' | 'end' | 'action' | 'parallel' | 'choice' | 'wait' | 'loop';
agent?: string; // Agent to invoke in this state
action?: string; // Action to execute
timeout?: number; // Timeout in ms
retry?: RetryConfig; // Retry configuration
onEnter?: Transition[]; // Transitions on entering state
onExit?: Transition[]; // Transitions on exiting state
metadata?: Record<string, unknown>;
}
export interface Transition {
event: string; // Event that triggers this transition
target: string; // Target state ID
condition?: Condition; // Optional condition
guard?: string; // Guard function name
}
export interface Condition {
type: 'equals' | 'contains' | 'exists' | 'custom';
field: string;
value?: unknown;
expression?: string;
}
export interface RetryConfig {
maxAttempts: number;
backoff: 'fixed' | 'exponential' | 'linear';
initialDelay: number;
maxDelay: number;
}
export interface StateMachineDefinition {
id: string;
name: string;
version: string;
description?: string;
initial: string;
states: Record<string, State>;
events?: string[]; // Allowed events
context?: Record<string, unknown>; // Initial context
onError?: ErrorHandling;
}
export interface ErrorHandling {
strategy: 'fail' | 'retry' | 'transition';
targetState?: string;
maxRetries?: number;
}
export interface StateMachineInstance {
id: string;
definition: StateMachineDefinition;
currentState: string;
previousState?: string;
status: StateStatus;
context: Record<string, unknown>;
history: StateTransition[];
createdAt: Date;
updatedAt: Date;
startedAt?: Date;
completedAt?: Date;
error?: string;
}
export interface StateTransition {
from: string;
to: string;
event: string;
timestamp: Date;
context?: Record<string, unknown>;
}
export interface Event {
type: string;
source: string;
target?: string;
payload: unknown;
timestamp: Date;
correlationId?: string;
}
// ============================================================================
// State Machine Engine
// ============================================================================
/**
* DeterministicStateMachine - Core engine for deterministic flow control
*/
export class DeterministicStateMachine extends EventEmitter {
private definition: StateMachineDefinition;
private instance: StateMachineInstance;
private eventQueue: Event[] = [];
private processing = false;
private timeoutId?: ReturnType<typeof setTimeout>;
constructor(definition: StateMachineDefinition, instanceId?: string) {
super();
this.definition = definition;
this.instance = this.createInstance(instanceId);
}
/**
* Create a new state machine instance
*/
private createInstance(instanceId?: string): StateMachineInstance {
return {
id: instanceId || randomUUID(),
definition: this.definition,
currentState: this.definition.initial,
status: 'idle',
context: { ...this.definition.context } || {},
history: [],
createdAt: new Date(),
updatedAt: new Date()
};
}
/**
* Start the state machine
*/
start(): void {
if (this.instance.status !== 'idle') {
throw new Error(`Cannot start state machine in ${this.instance.status} status`);
}
this.instance.status = 'active';
this.instance.startedAt = new Date();
this.emit('started', { instance: this.instance });
// Enter initial state
this.enterState(this.instance.currentState);
}
/**
* Send an event to the state machine
*/
sendEvent(event: Omit<Event, 'timestamp'>): void {
const fullEvent: Event = {
...event,
timestamp: new Date()
};
this.eventQueue.push(fullEvent);
this.emit('eventQueued', { event: fullEvent });
this.processQueue();
}
/**
* Process the event queue
*/
private async processQueue(): Promise<void> {
if (this.processing || this.eventQueue.length === 0) return;
this.processing = true;
try {
while (this.eventQueue.length > 0 && this.instance.status === 'active') {
const event = this.eventQueue.shift()!;
await this.handleEvent(event);
}
} finally {
this.processing = false;
}
}
/**
* Handle a single event
*/
private async handleEvent(event: Event): Promise<void> {
const currentState = this.getCurrentState();
this.emit('eventProcessed', { event, state: currentState });
// Find matching transition
const transition = this.findTransition(currentState, event);
if (!transition) {
this.emit('noTransition', { event, state: currentState });
return;
}
// Check condition if present
if (transition.condition && !this.evaluateCondition(transition.condition)) {
this.emit('conditionFailed', { event, transition });
return;
}
// Execute transition
await this.executeTransition(transition, event);
}
/**
* Find a matching transition for the event
*/
private findTransition(state: State, event: Event): Transition | undefined {
const transitions = state.onExit || [];
return transitions.find(t => {
// Check event type match
if (t.event !== event.type) return false;
// Check target filter if event has specific target
if (event.target && event.target !== this.instance.id) return false;
return true;
});
}
/**
* Evaluate a transition condition
*/
private evaluateCondition(condition: Condition): boolean {
const value = this.getDeepValue(this.instance.context, condition.field);
switch (condition.type) {
case 'equals':
return value === condition.value;
case 'contains':
if (Array.isArray(value)) {
return value.includes(condition.value);
}
return String(value).includes(String(condition.value));
case 'exists':
return value !== undefined && value !== null;
case 'custom':
// Custom conditions would be evaluated by a condition registry
return true;
default:
return false;
}
}
/**
* Execute a state transition
*/
private async executeTransition(transition: Transition, event: Event): Promise<void> {
const fromState = this.instance.currentState;
const toState = transition.target;
// Record transition
const transitionRecord: StateTransition = {
from: fromState,
to: toState,
event: event.type,
timestamp: new Date(),
context: { ...this.instance.context }
};
this.instance.history.push(transitionRecord);
// Exit current state
await this.exitState(fromState);
// Update instance
this.instance.previousState = fromState;
this.instance.currentState = toState;
this.instance.updatedAt = new Date();
// Merge event payload into context
if (event.payload && typeof event.payload === 'object') {
this.instance.context = {
...this.instance.context,
...event.payload as Record<string, unknown>
};
}
this.emit('transition', { from: fromState, to: toState, event });
// Enter new state
await this.enterState(toState);
}
/**
* Enter a state
*/
private async enterState(stateId: string): Promise<void> {
const state = this.definition.states[stateId];
if (!state) {
this.handleError(`State ${stateId} not found`);
return;
}
this.emit('enteringState', { state });
// Handle state types
switch (state.type) {
case 'end':
this.instance.status = 'completed';
this.instance.completedAt = new Date();
this.emit('completed', { instance: this.instance });
break;
case 'action':
// Emit event for external action handler
this.emit('action', {
state,
context: this.instance.context,
instanceId: this.instance.id
});
// Set timeout if specified
if (state.timeout) {
this.setTimeout(state.timeout, stateId);
}
break;
case 'parallel':
this.handleParallelState(state);
break;
case 'choice':
this.handleChoiceState(state);
break;
case 'wait':
// Wait for external event
this.instance.status = 'waiting';
break;
case 'loop':
this.handleLoopState(state);
break;
default:
// Process onEnter transitions
if (state.onEnter) {
for (const transition of state.onEnter) {
// Auto-transitions trigger immediately
if (transition.event === '*') {
await this.executeTransition(transition, {
type: '*',
source: stateId,
payload: {},
timestamp: new Date()
});
break;
}
}
}
}
this.emit('enteredState', { state });
}
/**
* Exit a state
*/
private async exitState(stateId: string): Promise<void> {
const state = this.definition.states[stateId];
// Clear any pending timeout
if (this.timeoutId) {
clearTimeout(this.timeoutId);
this.timeoutId = undefined;
}
this.emit('exitingState', { state });
}
/**
* Handle parallel state (fork into concurrent branches)
*/
private handleParallelState(state: State): void {
this.emit('parallel', {
state,
branches: state.onEnter?.map(t => t.target) || [],
context: this.instance.context
});
}
/**
* Handle choice state (conditional branching)
*/
private handleChoiceState(state: State): void {
const transitions = state.onExit || [];
for (const transition of transitions) {
if (transition.condition && this.evaluateCondition(transition.condition)) {
this.sendEvent({
type: transition.event,
source: state.id,
payload: {}
});
return;
}
}
// No condition matched - use default transition
const defaultTransition = transitions.find(t => !t.condition);
if (defaultTransition) {
this.sendEvent({
type: defaultTransition.event,
source: state.id,
payload: {}
});
}
}
/**
* Handle loop state
*/
private handleLoopState(state: State): void {
const loopCount = (this.instance.context._loopCount as Record<string, number>)?.[state.id] || 0;
const maxIterations = (state.metadata?.maxIterations as number) || 3;
if (loopCount < maxIterations) {
// Continue loop
this.instance.context._loopCount = {
...this.instance.context._loopCount as Record<string, number>,
[state.id]: loopCount + 1
};
this.emit('loopIteration', {
state,
iteration: loopCount + 1,
maxIterations
});
// Trigger loop body
const loopTransition = state.onExit?.find(t => t.event === 'continue');
if (loopTransition) {
this.sendEvent({
type: 'continue',
source: state.id,
payload: { iteration: loopCount + 1 }
});
}
} else {
// Exit loop
const exitTransition = state.onExit?.find(t => t.event === 'exit');
if (exitTransition) {
this.sendEvent({
type: 'exit',
source: state.id,
payload: { iterations: loopCount }
});
}
}
}
/**
* Set a timeout for the current state
*/
private setTimeout(duration: number, stateId: string): void {
this.timeoutId = setTimeout(() => {
this.emit('timeout', { stateId });
this.sendEvent({
type: 'timeout',
source: stateId,
payload: { timedOut: true }
});
}, duration);
}
/**
* Handle errors
*/
private handleError(error: string): void {
this.instance.error = error;
this.instance.status = 'failed';
this.instance.completedAt = new Date();
this.emit('error', { error, instance: this.instance });
}
/**
* Get current state definition
*/
getCurrentState(): State {
return this.definition.states[this.instance.currentState];
}
/**
* Get instance info
*/
getInstance(): StateMachineInstance {
return { ...this.instance };
}
/**
* Update context
*/
updateContext(updates: Record<string, unknown>): void {
this.instance.context = { ...this.instance.context, ...updates };
this.instance.updatedAt = new Date();
}
/**
* Pause the state machine
*/
pause(): void {
if (this.instance.status === 'active') {
this.instance.status = 'paused';
this.emit('paused', { instance: this.instance });
}
}
/**
* Resume the state machine
*/
resume(): void {
if (this.instance.status === 'paused') {
this.instance.status = 'active';
this.emit('resumed', { instance: this.instance });
this.processQueue();
}
}
/**
* Cancel the state machine
*/
cancel(): void {
this.instance.status = 'failed';
this.instance.error = 'Cancelled';
this.instance.completedAt = new Date();
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
this.eventQueue = [];
this.emit('cancelled', { instance: this.instance });
}
/**
* Get deep value from object by dot-notation path
*/
private getDeepValue(obj: Record<string, unknown>, path: string): unknown {
return path.split('.').reduce<unknown>((acc, key) => {
if (acc && typeof acc === 'object' && key in acc) {
return (acc as Record<string, unknown>)[key];
}
return undefined;
}, obj);
}
}
// ============================================================================
// State Machine Registry
// ============================================================================
/**
* StateMachineRegistry - Manages multiple state machine instances
*/
export class StateMachineRegistry {
private definitions: Map<string, StateMachineDefinition> = new Map();
private instances: Map<string, DeterministicStateMachine> = new Map();
/**
* Register a state machine definition
*/
register(definition: StateMachineDefinition): void {
this.definitions.set(definition.id, definition);
}
/**
* Create a new instance of a state machine
*/
createInstance(definitionId: string, instanceId?: string): DeterministicStateMachine {
const definition = this.definitions.get(definitionId);
if (!definition) {
throw new Error(`State machine definition ${definitionId} not found`);
}
const sm = new DeterministicStateMachine(definition, instanceId);
this.instances.set(sm.getInstance().id, sm);
return sm;
}
/**
* Get an instance by ID
*/
getInstance(instanceId: string): DeterministicStateMachine | undefined {
return this.instances.get(instanceId);
}
/**
* Get all instances
*/
getAllInstances(): DeterministicStateMachine[] {
return Array.from(this.instances.values());
}
/**
* Get instances by status
*/
getInstancesByStatus(status: StateStatus): DeterministicStateMachine[] {
return this.getAllInstances().filter(sm => sm.getInstance().status === status);
}
/**
* Remove an instance
*/
removeInstance(instanceId: string): boolean {
const sm = this.instances.get(instanceId);
if (sm) {
sm.cancel();
return this.instances.delete(instanceId);
}
return false;
}
/**
* Get statistics
*/
getStats(): {
definitions: number;
instances: number;
byStatus: Record<StateStatus, number>;
} {
const byStatus: Record<StateStatus, number> = {
idle: 0,
active: 0,
waiting: 0,
completed: 0,
failed: 0,
paused: 0
};
for (const sm of this.instances.values()) {
byStatus[sm.getInstance().status]++;
}
return {
definitions: this.definitions.size,
instances: this.instances.size,
byStatus
};
}
}
// Singleton registry
export const stateMachineRegistry = new StateMachineRegistry();

View File

@@ -0,0 +1,624 @@
/**
* Parallel Execution Engine
*
* Manages concurrent agent sessions with resource pooling.
* Supports: 4 projects × 3 roles = up to 12 concurrent sessions.
*
* Key features:
* - Worker pool with configurable concurrency limits
* - Resource isolation per agent session
* - Automatic scaling based on load
* - Task queuing with priority support
*/
import { randomUUID } from 'crypto';
import { EventEmitter } from 'events';
// ============================================================================
// Types
// ============================================================================
export type AgentRole = 'programmer' | 'reviewer' | 'tester' | 'planner' | 'analyst' | 'custom';
export type TaskStatus = 'pending' | 'queued' | 'running' | 'completed' | 'failed' | 'cancelled';
export type WorkerStatus = 'idle' | 'busy' | 'draining' | 'terminated';
export interface AgentSession {
id: string;
projectId: string;
role: AgentRole;
model?: string; // e.g., 'opus', 'sonnet' for cost optimization
workspace: string;
tools: string[];
memory: Record<string, unknown>;
identity: AgentIdentity;
status: 'active' | 'idle' | 'terminated';
createdAt: Date;
lastActivity: Date;
}
export interface AgentIdentity {
name: string;
description: string;
personality?: string;
systemPrompt?: string;
}
export interface PipelineTask {
id: string;
projectId: string;
role: AgentRole;
type: string;
description: string;
priority: 'low' | 'medium' | 'high' | 'critical';
input: unknown;
dependencies: string[];
timeout: number;
retryCount: number;
maxRetries: number;
status: TaskStatus;
assignedWorker?: string;
result?: unknown;
error?: string;
createdAt: Date;
startedAt?: Date;
completedAt?: Date;
metadata?: Record<string, unknown>;
}
export interface Worker {
id: string;
status: WorkerStatus;
currentTask?: string;
sessions: Map<string, AgentSession>;
completedTasks: number;
failedTasks: number;
createdAt: Date;
lastActivity: Date;
}
export interface ExecutionConfig {
maxWorkers: number;
maxConcurrentPerWorker: number;
taskTimeout: number;
retryAttempts: number;
retryDelay: number;
drainTimeout: number;
}
export interface ExecutionResult {
taskId: string;
success: boolean;
output?: unknown;
error?: string;
duration: number;
workerId: string;
sessionId: string;
}
// ============================================================================
// Parallel Executor
// ============================================================================
/**
* ParallelExecutionEngine - Manages concurrent agent sessions
*/
export class ParallelExecutionEngine extends EventEmitter {
private config: ExecutionConfig;
private workers: Map<string, Worker> = new Map();
private taskQueue: PipelineTask[] = [];
private runningTasks: Map<string, { task: PipelineTask; worker: Worker; session: AgentSession }> = new Map();
private completedTasks: PipelineTask[] = [];
private failedTasks: PipelineTask[] = [];
private sessions: Map<string, AgentSession> = new Map();
private processing = false;
private processInterval?: ReturnType<typeof setInterval>;
private taskHandlers: Map<string, (task: PipelineTask, session: AgentSession) => Promise<unknown>> = new Map();
constructor(config?: Partial<ExecutionConfig>) {
super();
this.config = {
maxWorkers: config?.maxWorkers || 4,
maxConcurrentPerWorker: config?.maxConcurrentPerWorker || 3,
taskTimeout: config?.taskTimeout || 300000, // 5 minutes
retryAttempts: config?.retryAttempts || 3,
retryDelay: config?.retryDelay || 5000,
drainTimeout: config?.drainTimeout || 60000,
...config
};
}
/**
* Start the execution engine
*/
start(): void {
// Initialize workers
for (let i = 0; i < this.config.maxWorkers; i++) {
this.createWorker();
}
// Start processing loop
this.processing = true;
this.processInterval = setInterval(() => this.processQueue(), 100);
this.emit('started', { workerCount: this.workers.size });
}
/**
* Stop the execution engine
*/
async stop(): Promise<void> {
this.processing = false;
if (this.processInterval) {
clearInterval(this.processInterval);
}
// Wait for running tasks to complete or drain
await this.drain();
// Terminate workers
for (const worker of this.workers.values()) {
worker.status = 'terminated';
}
this.emit('stopped');
}
/**
* Create a new worker
*/
private createWorker(): Worker {
const worker: Worker = {
id: `worker-${randomUUID().substring(0, 8)}`,
status: 'idle',
sessions: new Map(),
completedTasks: 0,
failedTasks: 0,
createdAt: new Date(),
lastActivity: new Date()
};
this.workers.set(worker.id, worker);
this.emit('workerCreated', { worker });
return worker;
}
/**
* Create an agent session
*/
createSession(config: {
projectId: string;
role: AgentRole;
model?: string;
workspace: string;
tools: string[];
identity: AgentIdentity;
}): AgentSession {
const session: AgentSession = {
id: `session-${config.projectId}-${config.role}-${randomUUID().substring(0, 8)}`,
projectId: config.projectId,
role: config.role,
model: config.model || this.getDefaultModelForRole(config.role),
workspace: config.workspace,
tools: config.tools,
memory: {},
identity: config.identity,
status: 'idle',
createdAt: new Date(),
lastActivity: new Date()
};
this.sessions.set(session.id, session);
this.emit('sessionCreated', { session });
return session;
}
/**
* Get default model for a role (cost optimization)
*/
private getDefaultModelForRole(role: AgentRole): string {
switch (role) {
case 'programmer':
return 'opus'; // Best for complex coding
case 'reviewer':
return 'sonnet'; // Cost-effective for review
case 'tester':
return 'sonnet'; // Good for test generation
case 'planner':
return 'opus'; // Complex planning
case 'analyst':
return 'sonnet';
default:
return 'sonnet';
}
}
/**
* Submit a task for execution
*/
submitTask(task: Omit<PipelineTask, 'id' | 'status' | 'retryCount' | 'createdAt'>): PipelineTask {
const fullTask: PipelineTask = {
...task,
id: `task-${randomUUID().substring(0, 8)}`,
status: 'pending',
retryCount: 0,
createdAt: new Date()
};
this.taskQueue.push(fullTask);
this.emit('taskSubmitted', { task: fullTask });
// Sort by priority
this.prioritizeQueue();
return fullTask;
}
/**
* Submit multiple tasks for parallel execution
*/
submitBatch(tasks: Array<Omit<PipelineTask, 'id' | 'status' | 'retryCount' | 'createdAt'>>): PipelineTask[] {
return tasks.map(task => this.submitTask(task));
}
/**
* Prioritize the task queue
*/
private prioritizeQueue(): void {
const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
this.taskQueue.sort((a, b) => {
// First by priority
const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
if (priorityDiff !== 0) return priorityDiff;
// Then by creation time (FIFO within priority)
return a.createdAt.getTime() - b.createdAt.getTime();
});
}
/**
* Process the task queue
*/
private async processQueue(): Promise<void> {
if (!this.processing) return;
// Find tasks ready to run (dependencies met)
const readyTasks = this.getReadyTasks();
for (const task of readyTasks) {
const worker = this.findAvailableWorker(task);
if (!worker) break; // No workers available
await this.executeTask(task, worker);
}
}
/**
* Get tasks that are ready to execute
*/
private getReadyTasks(): PipelineTask[] {
return this.taskQueue.filter(task => {
if (task.status !== 'pending') return false;
// Check dependencies
for (const depId of task.dependencies) {
const depTask = this.getTask(depId);
if (!depTask || depTask.status !== 'completed') {
return false;
}
}
return true;
});
}
/**
* Find an available worker for a task
*/
private findAvailableWorker(task: PipelineTask): Worker | undefined {
// First, try to find a worker already handling the project
for (const worker of this.workers.values()) {
if (worker.status !== 'idle' && worker.status !== 'busy') continue;
const hasProject = Array.from(worker.sessions.values())
.some(s => s.projectId === task.projectId);
if (hasProject && worker.sessions.size < this.config.maxConcurrentPerWorker) {
return worker;
}
}
// Then, find any available worker
for (const worker of this.workers.values()) {
if (worker.status !== 'idle' && worker.status !== 'busy') continue;
if (worker.sessions.size < this.config.maxConcurrentPerWorker) {
return worker;
}
}
// Create new worker if under limit
if (this.workers.size < this.config.maxWorkers) {
return this.createWorker();
}
return undefined;
}
/**
* Execute a task
*/
private async executeTask(task: PipelineTask, worker: Worker): Promise<void> {
// Move task from queue to running
const taskIndex = this.taskQueue.indexOf(task);
if (taskIndex > -1) {
this.taskQueue.splice(taskIndex, 1);
}
task.status = 'running';
task.startedAt = new Date();
task.assignedWorker = worker.id;
// Create or get session
const session = this.getOrCreateSession(task, worker);
// Track running task
this.runningTasks.set(task.id, { task, worker, session });
// Update worker status
worker.status = 'busy';
worker.currentTask = task.id;
worker.lastActivity = new Date();
this.emit('taskStarted', { task, worker, session });
// Get task handler
const handler = this.taskHandlers.get(task.type) || this.defaultTaskHandler;
try {
// Execute with timeout
const result = await Promise.race([
handler(task, session),
this.createTimeout(task)
]);
task.result = result;
task.status = 'completed';
task.completedAt = new Date();
worker.completedTasks++;
this.completedTasks.push(task);
this.emit('taskCompleted', { task, worker, session, result });
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
task.error = errorMessage;
task.retryCount++;
if (task.retryCount < task.maxRetries) {
// Retry
task.status = 'pending';
this.taskQueue.push(task);
this.emit('taskRetrying', { task, attempt: task.retryCount });
} else {
// Failed
task.status = 'failed';
task.completedAt = new Date();
worker.failedTasks++;
this.failedTasks.push(task);
this.emit('taskFailed', { task, worker, error: errorMessage });
}
}
// Cleanup
this.runningTasks.delete(task.id);
worker.currentTask = undefined;
worker.lastActivity = new Date();
// Update worker status
if (worker.sessions.size === 0 || this.runningTasks.size === 0) {
worker.status = 'idle';
}
session.lastActivity = new Date();
}
/**
* Get or create session for a task
*/
private getOrCreateSession(task: PipelineTask, worker: Worker): AgentSession {
// Look for existing session for this project/role
for (const session of worker.sessions.values()) {
if (session.projectId === task.projectId && session.role === task.role) {
return session;
}
}
// Create new session
const session = this.createSession({
projectId: task.projectId,
role: task.role,
workspace: `workspace/${task.projectId}/${task.role}`,
tools: this.getToolsForRole(task.role),
identity: this.getIdentityForRole(task.role)
});
worker.sessions.set(session.id, session);
return session;
}
/**
* Get tools available for a role
*/
private getToolsForRole(role: AgentRole): string[] {
const toolMap: Record<AgentRole, string[]> = {
programmer: ['read', 'write', 'execute', 'git', 'test', 'lint', 'build'],
reviewer: ['read', 'diff', 'comment', 'lint', 'test'],
tester: ['read', 'execute', 'test', 'mock'],
planner: ['read', 'write', 'diagram'],
analyst: ['read', 'query', 'report'],
custom: ['read']
};
return toolMap[role] || ['read'];
}
/**
* Get identity for a role
*/
private getIdentityForRole(role: AgentRole): AgentIdentity {
const identityMap: Record<AgentRole, AgentIdentity> = {
programmer: {
name: 'Code Architect',
description: 'Expert developer who writes clean, efficient code',
personality: 'Methodical, detail-oriented, focuses on best practices'
},
reviewer: {
name: 'Code Reviewer',
description: 'Experienced engineer who catches bugs and improves code quality',
personality: 'Thorough, constructive, focuses on maintainability'
},
tester: {
name: 'QA Engineer',
description: 'Test specialist who ensures code correctness',
personality: 'Systematic, edge-case focused, quality-driven'
},
planner: {
name: 'Technical Architect',
description: 'Strategic thinker who plans implementation',
personality: 'Analytical, systematic, big-picture focused'
},
analyst: {
name: 'Data Analyst',
description: 'Data specialist who extracts insights',
personality: 'Curious, methodical, detail-oriented'
},
custom: {
name: 'Custom Agent',
description: 'Generic agent for custom tasks',
personality: 'Adaptable'
}
};
return identityMap[role] || identityMap.custom;
}
/**
* Default task handler
*/
private async defaultTaskHandler(task: PipelineTask, session: AgentSession): Promise<unknown> {
// This would be replaced by actual LLM invocation
return {
message: `Task ${task.type} completed by ${session.identity.name}`,
projectId: task.projectId,
role: task.role
};
}
/**
* Create timeout promise
*/
private createTimeout(task: PipelineTask): Promise<never> {
return new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`Task ${task.id} timed out after ${task.timeout}ms`));
}, task.timeout);
});
}
/**
* Get task by ID
*/
getTask(taskId: string): PipelineTask | undefined {
return (
this.taskQueue.find(t => t.id === taskId) ||
this.runningTasks.get(taskId)?.task ||
this.completedTasks.find(t => t.id === taskId) ||
this.failedTasks.find(t => t.id === taskId)
);
}
/**
* Register a task handler
*/
registerHandler(taskType: string, handler: (task: PipelineTask, session: AgentSession) => Promise<unknown>): void {
this.taskHandlers.set(taskType, handler);
}
/**
* Drain - wait for running tasks to complete
*/
private async drain(): Promise<void> {
while (this.runningTasks.size > 0) {
await new Promise(resolve => setTimeout(resolve, 100));
}
}
/**
* Get engine statistics
*/
getStats(): {
workers: { total: number; idle: number; busy: number };
tasks: { pending: number; running: number; completed: number; failed: number };
sessions: number;
} {
let idleWorkers = 0;
let busyWorkers = 0;
for (const worker of this.workers.values()) {
if (worker.status === 'idle') idleWorkers++;
else if (worker.status === 'busy') busyWorkers++;
}
return {
workers: {
total: this.workers.size,
idle: idleWorkers,
busy: busyWorkers
},
tasks: {
pending: this.taskQueue.length,
running: this.runningTasks.size,
completed: this.completedTasks.length,
failed: this.failedTasks.length
},
sessions: this.sessions.size
};
}
/**
* Get sessions by project
*/
getSessionsByProject(projectId: string): AgentSession[] {
return Array.from(this.sessions.values()).filter(s => s.projectId === projectId);
}
/**
* Get all sessions
*/
getAllSessions(): AgentSession[] {
return Array.from(this.sessions.values());
}
/**
* Terminate a session
*/
terminateSession(sessionId: string): boolean {
const session = this.sessions.get(sessionId);
if (session) {
session.status = 'terminated';
this.emit('sessionTerminated', { session });
return true;
}
return false;
}
}
// Default instance
export const defaultExecutor = new ParallelExecutionEngine();

View File

@@ -0,0 +1,570 @@
/**
* Event-Driven Coordination System
*
* Event bus for inter-agent communication.
* Agents finish work → emit event → next step triggers automatically.
*
* Key features:
* - Pub/sub event distribution
* - Event correlation and routing
* - Event replay for debugging
* - Dead letter queue for failed handlers
*/
import { randomUUID } from 'crypto';
import { EventEmitter } from 'events';
// ============================================================================
// Types
// ============================================================================
export type EventPriority = 'low' | 'normal' | 'high' | 'critical';
export interface PipelineEvent {
id: string;
type: string;
source: string;
target?: string;
payload: unknown;
priority: EventPriority;
timestamp: Date;
correlationId?: string;
causationId?: string; // ID of event that caused this event
metadata?: Record<string, unknown>;
retryCount?: number;
}
export interface EventHandler {
id: string;
eventType: string | string[] | '*';
filter?: EventFilter;
handler: (event: PipelineEvent) => Promise<void> | void;
priority?: number;
once?: boolean;
}
export interface EventFilter {
source?: string | string[];
target?: string | string[];
payloadPattern?: Record<string, unknown>;
}
export interface Subscription {
id: string;
eventType: string;
handlerId: string;
active: boolean;
createdAt: Date;
eventsReceived: number;
}
export interface EventBusConfig {
maxHistorySize: number;
deadLetterQueueSize: number;
retryAttempts: number;
retryDelay: number;
enableReplay: boolean;
}
export interface EventBusStats {
eventsPublished: number;
eventsProcessed: number;
eventsFailed: number;
handlersRegistered: number;
queueSize: number;
historySize: number;
}
// ============================================================================
// Event Bus
// ============================================================================
/**
* EventBus - Central event distribution system
*/
export class EventBus extends EventEmitter {
private config: EventBusConfig;
private handlers: Map<string, EventHandler> = new Map();
private eventQueue: PipelineEvent[] = [];
private history: PipelineEvent[] = [];
private deadLetterQueue: PipelineEvent[] = [];
private processing = false;
private stats = {
eventsPublished: 0,
eventsProcessed: 0,
eventsFailed: 0
};
private processInterval?: ReturnType<typeof setInterval>;
constructor(config?: Partial<EventBusConfig>) {
super();
this.config = {
maxHistorySize: 1000,
deadLetterQueueSize: 100,
retryAttempts: 3,
retryDelay: 1000,
enableReplay: true,
...config
};
}
/**
* Start the event bus
*/
start(): void {
this.processing = true;
this.processInterval = setInterval(() => this.processQueue(), 50);
this.emit('started');
}
/**
* Stop the event bus
*/
stop(): void {
this.processing = false;
if (this.processInterval) {
clearInterval(this.processInterval);
}
this.emit('stopped');
}
/**
* Publish an event
*/
publish(event: Omit<PipelineEvent, 'id' | 'timestamp'>): string {
const fullEvent: PipelineEvent = {
...event,
id: `evt-${randomUUID().substring(0, 8)}`,
timestamp: new Date(),
retryCount: event.retryCount || 0
};
// Add to queue
this.eventQueue.push(fullEvent);
this.stats.eventsPublished++;
// Add to history
if (this.config.enableReplay) {
this.history.push(fullEvent);
if (this.history.length > this.config.maxHistorySize) {
this.history.shift();
}
}
this.emit('eventPublished', { event: fullEvent });
return fullEvent.id;
}
/**
* Publish a batch of events
*/
publishBatch(events: Array<Omit<PipelineEvent, 'id' | 'timestamp'>>): string[] {
return events.map(event => this.publish(event));
}
/**
* Subscribe to events
*/
subscribe(config: {
eventType: string | string[] | '*';
handler: (event: PipelineEvent) => Promise<void> | void;
filter?: EventFilter;
priority?: number;
once?: boolean;
}): string {
const handlerId = `handler-${randomUUID().substring(0, 8)}`;
const handler: EventHandler = {
id: handlerId,
eventType: config.eventType,
filter: config.filter,
handler: config.handler,
priority: config.priority || 0,
once: config.once || false
};
this.handlers.set(handlerId, handler);
this.emit('handlerRegistered', { handler });
return handlerId;
}
/**
* Unsubscribe from events
*/
unsubscribe(handlerId: string): boolean {
const result = this.handlers.delete(handlerId);
if (result) {
this.emit('handlerUnregistered', { handlerId });
}
return result;
}
/**
* Process the event queue
*/
private async processQueue(): Promise<void> {
if (!this.processing || this.eventQueue.length === 0) return;
const event = this.eventQueue.shift()!;
// Find matching handlers
const matchingHandlers = this.findMatchingHandlers(event);
// Sort by priority (higher first)
matchingHandlers.sort((a, b) => (b.priority || 0) - (a.priority || 0));
// Execute handlers
for (const handler of matchingHandlers) {
try {
await handler.handler(event);
this.stats.eventsProcessed++;
// Remove one-time handlers
if (handler.once) {
this.handlers.delete(handler.id);
}
} catch (error) {
this.stats.eventsFailed++;
// Retry logic
const retryCount = (event.retryCount || 0) + 1;
if (retryCount < this.config.retryAttempts) {
// Re-queue with incremented retry count
setTimeout(() => {
this.publish({
...event,
retryCount
});
}, this.config.retryDelay * retryCount);
this.emit('eventRetry', { event, error, retryCount });
} else {
// Move to dead letter queue
this.addToDeadLetterQueue(event, error);
}
}
}
this.emit('eventProcessed', { event, handlerCount: matchingHandlers.length });
}
/**
* Find handlers matching an event
*/
private findMatchingHandlers(event: PipelineEvent): EventHandler[] {
const matching: EventHandler[] = [];
for (const handler of this.handlers.values()) {
// Check event type match
if (handler.eventType !== '*') {
const types = Array.isArray(handler.eventType) ? handler.eventType : [handler.eventType];
if (!types.includes(event.type)) continue;
}
// Check filters
if (handler.filter && !this.matchesFilter(event, handler.filter)) {
continue;
}
matching.push(handler);
}
return matching;
}
/**
* Check if event matches filter
*/
private matchesFilter(event: PipelineEvent, filter: EventFilter): boolean {
// Check source filter
if (filter.source) {
const sources = Array.isArray(filter.source) ? filter.source : [filter.source];
if (!sources.includes(event.source)) return false;
}
// Check target filter
if (filter.target) {
const targets = Array.isArray(filter.target) ? filter.target : [filter.target];
if (event.target && !targets.includes(event.target)) return false;
}
// Check payload pattern
if (filter.payloadPattern) {
const payload = event.payload as Record<string, unknown>;
for (const [key, value] of Object.entries(filter.payloadPattern)) {
if (payload[key] !== value) return false;
}
}
return true;
}
/**
* Add event to dead letter queue
*/
private addToDeadLetterQueue(event: PipelineEvent, error: unknown): void {
this.deadLetterQueue.push({
...event,
metadata: {
...event.metadata,
error: error instanceof Error ? error.message : String(error),
failedAt: new Date().toISOString()
}
});
// Trim queue
if (this.deadLetterQueue.length > this.config.deadLetterQueueSize) {
this.deadLetterQueue.shift();
}
this.emit('eventDeadLettered', { event, error });
}
/**
* Replay events from history
*/
replay(fromTimestamp?: Date, toTimestamp?: Date): void {
if (!this.config.enableReplay) {
throw new Error('Event replay is disabled');
}
const events = this.history.filter(event => {
if (fromTimestamp && event.timestamp < fromTimestamp) return false;
if (toTimestamp && event.timestamp > toTimestamp) return false;
return true;
});
for (const event of events) {
this.eventQueue.push({
...event,
id: `replay-${event.id}`,
metadata: { ...event.metadata, replayed: true }
});
}
this.emit('replayStarted', { count: events.length });
}
/**
* Get events from history
*/
getHistory(filter?: {
type?: string;
source?: string;
from?: Date;
to?: Date;
}): PipelineEvent[] {
let events = [...this.history];
if (filter) {
if (filter.type) {
events = events.filter(e => e.type === filter.type);
}
if (filter.source) {
events = events.filter(e => e.source === filter.source);
}
if (filter.from) {
events = events.filter(e => e.timestamp >= filter.from!);
}
if (filter.to) {
events = events.filter(e => e.timestamp <= filter.to!);
}
}
return events;
}
/**
* Get dead letter queue
*/
getDeadLetterQueue(): PipelineEvent[] {
return [...this.deadLetterQueue];
}
/**
* Clear dead letter queue
*/
clearDeadLetterQueue(): void {
this.deadLetterQueue = [];
}
/**
* Get statistics
*/
getStats(): EventBusStats {
return {
eventsPublished: this.stats.eventsPublished,
eventsProcessed: this.stats.eventsProcessed,
eventsFailed: this.stats.eventsFailed,
handlersRegistered: this.handlers.size,
queueSize: this.eventQueue.length,
historySize: this.history.length
};
}
/**
* Request-response pattern
*/
async request<T = unknown>(
event: Omit<PipelineEvent, 'id' | 'timestamp'>,
timeout = 30000
): Promise<T> {
return new Promise((resolve, reject) => {
const correlationId = `req-${randomUUID().substring(0, 8)}`;
// Subscribe to response
const responseHandler = this.subscribe({
eventType: `${event.type}.response`,
filter: { payloadPattern: { correlationId } },
once: true,
handler: (response) => {
clearTimeout(timeoutId);
resolve(response.payload as T);
}
});
// Set timeout
const timeoutId = setTimeout(() => {
this.unsubscribe(responseHandler);
reject(new Error(`Request timeout for event ${event.type}`));
}, timeout);
// Publish request with correlation ID
this.publish({
...event,
metadata: { ...event.metadata, correlationId }
});
});
}
/**
* Create a correlated event chain
*/
createChain(firstEvent: Omit<PipelineEvent, 'id' | 'timestamp' | 'correlationId'>): EventChain {
const correlationId = `chain-${randomUUID().substring(0, 8)}`;
return new EventChain(this, correlationId, firstEvent);
}
}
// ============================================================================
// Event Chain
// ============================================================================
/**
* EventChain - Builder for correlated event sequences
*/
export class EventChain {
private bus: EventBus;
private correlationId: string;
private events: PipelineEvent[] = [];
private currentEvent?: PipelineEvent;
constructor(bus: EventBus, correlationId: string, firstEvent: Omit<PipelineEvent, 'id' | 'timestamp' | 'correlationId'>) {
this.bus = bus;
this.correlationId = correlationId;
this.currentEvent = {
...firstEvent,
id: '',
timestamp: new Date(),
correlationId
} as PipelineEvent;
}
/**
* Add next event in chain
*/
then(event: Omit<PipelineEvent, 'id' | 'timestamp' | 'correlationId' | 'causationId'>): this {
if (this.currentEvent) {
this.events.push(this.currentEvent);
this.currentEvent = {
...event,
id: '',
timestamp: new Date(),
correlationId: this.correlationId,
causationId: this.currentEvent.id || undefined
} as PipelineEvent;
}
return this;
}
/**
* Execute the chain
*/
execute(): string[] {
if (this.currentEvent) {
this.events.push(this.currentEvent);
}
return this.events.map(event =>
this.bus.publish({
...event,
correlationId: this.correlationId
})
);
}
/**
* Get correlation ID
*/
getCorrelationId(): string {
return this.correlationId;
}
}
// ============================================================================
// Predefined Pipeline Events
// ============================================================================
/**
* Standard pipeline event types
*/
export const PipelineEventTypes = {
// Agent lifecycle
AGENT_STARTED: 'agent.started',
AGENT_COMPLETED: 'agent.completed',
AGENT_FAILED: 'agent.failed',
AGENT_TIMEOUT: 'agent.timeout',
// Task lifecycle
TASK_CREATED: 'task.created',
TASK_ASSIGNED: 'task.assigned',
TASK_STARTED: 'task.started',
TASK_COMPLETED: 'task.completed',
TASK_FAILED: 'task.failed',
// Code pipeline
CODE_WRITTEN: 'code.written',
CODE_REVIEWED: 'code.reviewed',
CODE_APPROVED: 'code.approved',
CODE_REJECTED: 'code.rejected',
CODE_TESTED: 'code.tested',
TESTS_PASSED: 'tests.passed',
TESTS_FAILED: 'tests.failed',
// State machine
STATE_ENTERED: 'state.entered',
STATE_EXITED: 'state.exited',
TRANSITION: 'state.transition',
// Coordination
PIPELINE_STARTED: 'pipeline.started',
PIPELINE_COMPLETED: 'pipeline.completed',
PIPELINE_PAUSED: 'pipeline.paused',
PIPELINE_RESUMED: 'pipeline.resumed',
// Human interaction
HUMAN_INPUT_REQUIRED: 'human.input_required',
HUMAN_INPUT_RECEIVED: 'human.input_received',
HUMAN_APPROVAL_REQUIRED: 'human.approval_required',
HUMAN_APPROVED: 'human.approved',
HUMAN_REJECTED: 'human.rejected'
} as const;
// Default event bus instance
export const defaultEventBus = new EventBus();

206
pipeline-system/index.ts Normal file
View File

@@ -0,0 +1,206 @@
/**
* Deterministic Multi-Agent Pipeline System
*
* A comprehensive system for building deterministic, parallel, event-driven
* multi-agent pipelines that integrate with Claude Code and OpenClaw.
*
* Key Features:
* - Deterministic orchestration (state machine, not LLM decision)
* - Parallel execution (up to 12 concurrent agent sessions)
* - Event-driven coordination (agents finish → next triggers)
* - Full agent capabilities (tools, memory, identity, workspace)
*
* @module pipeline-system
*/
// Core
export {
DeterministicStateMachine,
StateMachineRegistry,
stateMachineRegistry
} from './core/state-machine';
export type {
State,
StateStatus,
Transition,
Condition,
RetryConfig,
StateMachineDefinition,
StateMachineInstance,
StateTransition,
Event,
ErrorHandling
} from './core/state-machine';
// Engine
export {
ParallelExecutionEngine,
defaultExecutor
} from './engine/parallel-executor';
export type {
AgentRole,
TaskStatus,
WorkerStatus,
AgentSession,
AgentIdentity,
PipelineTask,
Worker,
ExecutionConfig,
ExecutionResult
} from './engine/parallel-executor';
// Events
export {
EventBus,
EventChain,
PipelineEventTypes,
defaultEventBus
} from './events/event-bus';
export type {
PipelineEvent,
EventHandler,
EventFilter,
Subscription,
EventBusConfig,
EventBusStats,
EventPriority
} from './events/event-bus';
// Workspace
export {
WorkspaceManager,
WorkspaceFactory,
defaultWorkspaceFactory
} from './workspace/agent-workspace';
export type {
Permission,
WorkspaceConfig,
ResourceLimits,
MountPoint,
AgentTool,
ToolContext,
ToolResult,
MemoryStore
} from './workspace/agent-workspace';
// Workflows
export {
WorkflowParser,
WorkflowRegistry,
CODE_PIPELINE_WORKFLOW,
PARALLEL_PROJECTS_WORKFLOW,
HUMAN_APPROVAL_WORKFLOW,
defaultWorkflowRegistry
} from './workflows/yaml-workflow';
export type {
YAMLWorkflow,
YAMLState,
YAMLTransition,
YAMLCondition,
YAMLRetryConfig,
YAMLLoopConfig
} from './workflows/yaml-workflow';
// Integrations
export {
PipelineOrchestrator,
createCodePipeline,
createParallelPipeline,
runWorkflow,
defaultOrchestrator
} from './integrations/claude-code';
export type {
PipelineConfig,
ProjectConfig,
TaskConfig,
PipelineResult,
ProjectResult,
TaskResult,
AgentMessage
} from './integrations/claude-code';
// Version
export const VERSION = '1.0.0';
/**
* Quick Start Example:
*
* ```typescript
* import {
* PipelineOrchestrator,
* createCodePipeline,
* runWorkflow
* } from './pipeline-system';
*
* // Option 1: Simple code pipeline
* const pipelineId = await createCodePipeline([
* {
* id: 'project-1',
* name: 'My Project',
* tasks: [
* { type: 'implement', description: 'Create auth module', role: 'programmer' },
* { type: 'review', description: 'Review auth module', role: 'reviewer' },
* { type: 'test', description: 'Test auth module', role: 'tester' }
* ]
* }
* ]);
*
* // Option 2: Run predefined workflow
* const workflowId = await runWorkflow('code-pipeline', {
* projectId: 'my-project',
* requirements: 'Build REST API'
* });
*
* // Option 3: Custom configuration
* const orchestrator = new PipelineOrchestrator();
* await orchestrator.initialize();
*
* const customPipelineId = await orchestrator.createPipeline({
* name: 'Custom Pipeline',
* projects: [...],
* roles: ['programmer', 'reviewer', 'tester'],
* maxConcurrency: 12
* });
*
* // Subscribe to events
* orchestrator.onEvent('agent.completed', (event) => {
* console.log('Agent completed:', event.payload);
* });
* ```
*
* ## Architecture
*
* ```
* ┌─────────────────────────────────────────────────────────────────┐
* │ Pipeline Orchestrator │
* │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
* │ │ State Machine│ │Event Bus │ │ Parallel Exec│ │
* │ │ (Deterministic│ │(Coordination)│ │ (Concurrency)│ │
* │ └──────────────┘ └──────────────┘ └──────────────┘ │
* │ │ │ │ │
* │ ┌──────┴────────────────┴─────────────────┴──────┐ │
* │ │ Agent Workspaces │ │
* │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
* │ │ │Programmer│ │Reviewer │ │ Tester │ │ │
* │ │ │Workspace │ │Workspace│ │Workspace│ │ │
* │ │ │ • Tools │ │ • Tools │ │ • Tools │ │ │
* │ │ │ • Memory │ │ • Memory│ │ • Memory│ │ │
* │ │ │ • Files │ │ • Files │ │ • Files │ │ │
* │ │ └─────────┘ └─────────┘ └─────────┘ │ │
* │ └────────────────────────────────────────────────┘ │
* └─────────────────────────────────────────────────────────────────┘
* │
* ▼
* ┌─────────────────────────────────────────────────────────────────┐
* │ LLM Provider (ZAI SDK) │
* └─────────────────────────────────────────────────────────────────┘
* ```
*
* ## Key Principles
*
* 1. **Deterministic Flow**: State machines control the pipeline, not LLM decisions
* 2. **Event-Driven**: Agents communicate through events, enabling loose coupling
* 3. **Parallel Execution**: Multiple agents work concurrently with resource isolation
* 4. **Workspace Isolation**: Each agent has its own tools, memory, and file space
* 5. **YAML Workflows**: Define pipelines declaratively, compatible with Lobster
*/

View File

@@ -0,0 +1,599 @@
/**
* Claude Code Integration Layer
*
* Provides easy integration with Claude Code and OpenClaw.
* Single API surface for all pipeline operations.
*/
import { randomUUID } from 'crypto';
import ZAI from 'z-ai-web-dev-sdk';
import {
DeterministicStateMachine,
StateMachineDefinition,
StateMachineRegistry,
stateMachineRegistry
} from '../core/state-machine';
import {
ParallelExecutionEngine,
PipelineTask,
AgentRole,
AgentSession,
defaultExecutor
} from '../engine/parallel-executor';
import {
EventBus,
PipelineEvent,
PipelineEventTypes,
defaultEventBus
} from '../events/event-bus';
import {
WorkspaceManager,
WorkspaceFactory,
AgentIdentity,
defaultWorkspaceFactory
} from '../workspace/agent-workspace';
import {
WorkflowRegistry,
YAMLWorkflow,
defaultWorkflowRegistry
} from '../workflows/yaml-workflow';
// ============================================================================
// Types
// ============================================================================
export interface PipelineConfig {
name: string;
projects: ProjectConfig[];
roles: AgentRole[];
maxConcurrency?: number;
timeout?: number;
}
export interface ProjectConfig {
id: string;
name: string;
description?: string;
repository?: string;
branch?: string;
tasks: TaskConfig[];
}
export interface TaskConfig {
type: string;
description: string;
role: AgentRole;
priority?: 'low' | 'medium' | 'high' | 'critical';
dependencies?: string[];
timeout?: number;
}
export interface PipelineResult {
pipelineId: string;
status: 'running' | 'completed' | 'failed' | 'cancelled';
startTime: Date;
endTime?: Date;
projects: ProjectResult[];
}
export interface ProjectResult {
projectId: string;
status: 'pending' | 'running' | 'completed' | 'failed';
tasks: TaskResult[];
}
export interface TaskResult {
taskId: string;
status: 'pending' | 'running' | 'completed' | 'failed';
output?: unknown;
error?: string;
duration?: number;
}
export interface AgentMessage {
role: 'system' | 'user' | 'assistant';
content: string;
}
// ============================================================================
// Pipeline Orchestrator
// ============================================================================
/**
* PipelineOrchestrator - Main integration class
*
* Single entry point for Claude Code and OpenClaw integration.
*/
export class PipelineOrchestrator {
private zai: Awaited<ReturnType<typeof ZAI.create>> | null = null;
private executor: ParallelExecutionEngine;
private eventBus: EventBus;
private workflowRegistry: WorkflowRegistry;
private workspaceFactory: WorkspaceFactory;
private smRegistry: StateMachineRegistry;
private pipelines: Map<string, PipelineResult> = new Map();
private initialized = false;
constructor(config?: {
executor?: ParallelExecutionEngine;
eventBus?: EventBus;
workflowRegistry?: WorkflowRegistry;
workspaceFactory?: WorkspaceFactory;
}) {
this.executor = config?.executor || defaultExecutor;
this.eventBus = config?.eventBus || defaultEventBus;
this.workflowRegistry = config?.workflowRegistry || defaultWorkflowRegistry;
this.workspaceFactory = config?.workspaceFactory || defaultWorkspaceFactory;
this.smRegistry = stateMachineRegistry;
}
/**
* Initialize the pipeline system
*/
async initialize(): Promise<void> {
if (this.initialized) return;
// Initialize ZAI SDK
this.zai = await ZAI.create();
// Start executor
this.executor.start();
// Start event bus
this.eventBus.start();
// Register task handler
this.executor.registerHandler('agent-task', this.executeAgentTask.bind(this));
// Set up event subscriptions
this.setupEventSubscriptions();
this.initialized = true;
}
/**
* Set up event subscriptions for coordination
*/
private setupEventSubscriptions(): void {
// Agent completion triggers next step
this.eventBus.subscribe({
eventType: PipelineEventTypes.AGENT_COMPLETED,
handler: async (event) => {
const { projectId, role, output } = event.payload as Record<string, unknown>;
// Determine next role in pipeline
const nextRole = this.getNextRole(role as AgentRole);
if (nextRole) {
// Emit event to trigger next agent
this.eventBus.publish({
type: PipelineEventTypes.TASK_STARTED,
source: 'orchestrator',
payload: { projectId, role: nextRole, previousOutput: output }
});
}
}
});
// Handle failures
this.eventBus.subscribe({
eventType: PipelineEventTypes.AGENT_FAILED,
handler: async (event) => {
const { projectId, error } = event.payload as Record<string, unknown>;
console.error(`Agent failed for project ${projectId}:`, error);
// Emit pipeline failure event
this.eventBus.publish({
type: PipelineEventTypes.PIPELINE_COMPLETED,
source: 'orchestrator',
payload: { projectId, status: 'failed', error }
});
}
});
}
/**
* Get next role in the pipeline sequence
*/
private getNextRole(currentRole: AgentRole): AgentRole | null {
const sequence: AgentRole[] = ['programmer', 'reviewer', 'tester'];
const currentIndex = sequence.indexOf(currentRole);
if (currentIndex < sequence.length - 1) {
return sequence[currentIndex + 1];
}
return null; // End of pipeline
}
/**
* Execute an agent task
*/
private async executeAgentTask(
task: PipelineTask,
session: AgentSession
): Promise<unknown> {
if (!this.zai) {
throw new Error('Pipeline not initialized');
}
// Create workspace for this task
const workspace = this.workspaceFactory.createWorkspace({
projectId: session.projectId,
agentId: session.id,
role: session.role,
permissions: this.getPermissionsForRole(session.role)
});
// Set agent identity
workspace.setIdentity(session.identity);
// Build messages for LLM
const messages = this.buildMessages(task, session, workspace);
try {
// Call LLM
const response = await this.zai.chat.completions.create({
messages,
thinking: { type: 'disabled' }
});
const output = response.choices?.[0]?.message?.content || '';
// Save output to workspace
workspace.writeFile(`output/${task.id}.txt`, output);
// Store in memory for next agent
workspace.memorize(`task.${task.id}.output`, output);
// Emit completion event
this.eventBus.publish({
type: PipelineEventTypes.AGENT_COMPLETED,
source: session.id,
payload: {
taskId: task.id,
projectId: session.projectId,
role: session.role,
output
}
});
return { output, workspace: workspace.getPath() };
} catch (error) {
// Emit failure event
this.eventBus.publish({
type: PipelineEventTypes.AGENT_FAILED,
source: session.id,
payload: {
taskId: task.id,
projectId: session.projectId,
role: session.role,
error: error instanceof Error ? error.message : String(error)
}
});
throw error;
}
}
/**
* Build messages for LLM
*/
private buildMessages(
task: PipelineTask,
session: AgentSession,
workspace: WorkspaceManager
): AgentMessage[] {
const messages: AgentMessage[] = [];
// System prompt with identity
messages.push({
role: 'system',
content: this.buildSystemPrompt(session, workspace)
});
// Task description
messages.push({
role: 'user',
content: `## Task\n${task.description}\n\n## Context\nProject: ${session.projectId}\nRole: ${session.role}\n\n## Instructions\nComplete this task and provide your output.`
});
// Add any previous context from memory
const previousOutput = workspace.recall('previous.output');
if (previousOutput) {
messages.push({
role: 'user',
content: `## Previous Work\n${JSON.stringify(previousOutput, null, 2)}`
});
}
return messages;
}
/**
* Build system prompt for agent
*/
private buildSystemPrompt(session: AgentSession, workspace: WorkspaceManager): string {
const identity = session.identity;
const role = session.role;
const roleInstructions: Record<AgentRole, string> = {
programmer: `You are responsible for writing clean, efficient, and well-documented code.
- Follow best practices and coding standards
- Write tests for your code
- Ensure code is production-ready`,
reviewer: `You are responsible for reviewing code for quality, bugs, and improvements.
- Check for security vulnerabilities
- Verify coding standards
- Suggest improvements
- Approve or request changes`,
tester: `You are responsible for testing the code thoroughly.
- Write comprehensive test cases
- Test edge cases and error handling
- Verify functionality meets requirements
- Report test results clearly`,
planner: `You are responsible for planning and architecture.
- Break down complex tasks
- Design system architecture
- Identify dependencies
- Create implementation plans`,
analyst: `You are responsible for analysis and reporting.
- Analyze data and metrics
- Identify patterns and insights
- Create reports and recommendations`,
custom: `You are a custom agent with specific instructions.`
};
return `# Agent Identity
Name: ${identity.name}
Role: ${role}
Description: ${identity.description}
# Personality
${identity.personality || 'Professional and efficient.'}
# Role Instructions
${roleInstructions[role] || roleInstructions.custom}
# Workspace
Your workspace is at: ${workspace.getPath()}
# Available Tools
${session.tools.map(t => `- ${t}`).join('\n')}
# Constraints
- Stay within your role boundaries
- Communicate clearly and concisely
- Report progress and issues promptly`;
}
/**
* Get permissions for a role
*/
private getPermissionsForRole(role: AgentRole): string[] {
const permissionMap: Record<AgentRole, string[]> = {
programmer: ['read', 'write', 'execute', 'git'],
reviewer: ['read', 'diff'],
tester: ['read', 'execute', 'test'],
planner: ['read', 'write'],
analyst: ['read'],
custom: ['read']
};
return permissionMap[role] || ['read'];
}
// =========================================================================
// Public API
// =========================================================================
/**
* Create and start a pipeline
*/
async createPipeline(config: PipelineConfig): Promise<string> {
await this.initialize();
const pipelineId = `pipeline-${randomUUID().substring(0, 8)}`;
const result: PipelineResult = {
pipelineId,
status: 'running',
startTime: new Date(),
projects: config.projects.map(p => ({
projectId: p.id,
status: 'pending',
tasks: []
}))
};
this.pipelines.set(pipelineId, result);
// Create tasks for all projects and roles
const tasks: PipelineTask[] = [];
for (const project of config.projects) {
for (const taskConfig of project.tasks) {
const task = this.executor.submitTask({
projectId: project.id,
role: taskConfig.role,
type: taskConfig.type || 'agent-task',
description: taskConfig.description,
priority: taskConfig.priority || 'medium',
input: { project, task: taskConfig },
dependencies: taskConfig.dependencies || [],
timeout: taskConfig.timeout || config.timeout || 300000,
maxRetries: 3
});
tasks.push(task);
}
}
// Emit pipeline started event
this.eventBus.publish({
type: PipelineEventTypes.PIPELINE_STARTED,
source: 'orchestrator',
payload: { pipelineId, config, taskCount: tasks.length }
});
return pipelineId;
}
/**
* Create pipeline from YAML workflow
*/
async createPipelineFromYAML(workflowId: string, context?: Record<string, unknown>): Promise<string> {
await this.initialize();
const workflow = this.workflowRegistry.get(workflowId);
if (!workflow) {
throw new Error(`Workflow ${workflowId} not found`);
}
const definition = this.workflowRegistry.getParsed(workflowId)!;
// Create state machine instance
const sm = this.smRegistry.createInstance(workflowId);
// Update context if provided
if (context) {
sm.updateContext(context);
}
// Start the state machine
sm.start();
// Listen for state transitions
sm.on('transition', ({ from, to, event }) => {
this.eventBus.publish({
type: PipelineEventTypes.TRANSITION,
source: sm.getInstance().id,
payload: { workflowId, from, to, event }
});
});
// Listen for actions
sm.on('action', async ({ state, context }) => {
if (state.agent || state.metadata?.role) {
// Submit task to executor
this.executor.submitTask({
projectId: context.projectId as string || 'default',
role: state.metadata?.role as AgentRole || 'programmer',
type: 'agent-task',
description: `Execute ${state.name}`,
priority: 'high',
input: { state, context },
dependencies: [],
timeout: state.timeout || 300000,
maxRetries: state.retry?.maxAttempts || 3
});
}
});
return sm.getInstance().id;
}
/**
* Register a custom workflow
*/
registerWorkflow(yaml: YAMLWorkflow): StateMachineDefinition {
return this.workflowRegistry.register(yaml);
}
/**
* Get pipeline status
*/
getPipelineStatus(pipelineId: string): PipelineResult | undefined {
return this.pipelines.get(pipelineId);
}
/**
* Cancel a pipeline
*/
async cancelPipeline(pipelineId: string): Promise<void> {
const pipeline = this.pipelines.get(pipelineId);
if (pipeline) {
pipeline.status = 'cancelled';
pipeline.endTime = new Date();
this.eventBus.publish({
type: PipelineEventTypes.PIPELINE_COMPLETED,
source: 'orchestrator',
payload: { pipelineId, status: 'cancelled' }
});
}
}
/**
* Get system statistics
*/
getStats(): {
pipelines: number;
executor: ReturnType<ParallelExecutionEngine['getStats']>;
eventBus: ReturnType<EventBus['getStats']>;
workspaces: ReturnType<WorkspaceFactory['getStats']>;
} {
return {
pipelines: this.pipelines.size,
executor: this.executor.getStats(),
eventBus: this.eventBus.getStats(),
workspaces: this.workspaceFactory.getStats()
};
}
/**
* Subscribe to pipeline events
*/
onEvent(eventType: string, handler: (event: PipelineEvent) => void): () => void {
return this.eventBus.subscribe({ eventType, handler });
}
/**
* Shutdown the pipeline system
*/
async shutdown(): Promise<void> {
await this.executor.stop();
this.eventBus.stop();
this.initialized = false;
}
}
// ============================================================================
// Quick Start Functions
// ============================================================================
/**
* Create a simple code pipeline
*/
export async function createCodePipeline(projects: ProjectConfig[]): Promise<string> {
const orchestrator = new PipelineOrchestrator();
return orchestrator.createPipeline({
name: 'Code Pipeline',
projects,
roles: ['programmer', 'reviewer', 'tester'],
maxConcurrency: 12, // 4 projects × 3 roles
timeout: 300000
});
}
/**
* Create a parallel execution pipeline
*/
export async function createParallelPipeline(config: PipelineConfig): Promise<string> {
const orchestrator = new PipelineOrchestrator();
return orchestrator.createPipeline(config);
}
/**
* Run a predefined workflow
*/
export async function runWorkflow(
workflowId: string,
context?: Record<string, unknown>
): Promise<string> {
const orchestrator = new PipelineOrchestrator();
return orchestrator.createPipelineFromYAML(workflowId, context);
}
// Default orchestrator instance
export const defaultOrchestrator = new PipelineOrchestrator();

View File

@@ -0,0 +1,540 @@
/**
* YAML Workflow Integration (Lobster-Compatible)
*
* Parses YAML workflow definitions and converts them to
* deterministic state machine definitions.
*
* Compatible with OpenClaw/Lobster workflow format.
*/
import { StateMachineDefinition, State, Transition, RetryConfig } from '../core/state-machine';
import { AgentRole } from '../engine/parallel-executor';
// ============================================================================
// Types
// ============================================================================
export interface YAMLWorkflow {
id: string;
name: string;
version?: string;
description?: string;
initial: string;
states: Record<string, YAMLState>;
events?: string[];
context?: Record<string, unknown>;
}
export interface YAMLState {
type: 'start' | 'end' | 'action' | 'parallel' | 'choice' | 'wait' | 'loop' | 'subworkflow';
agent?: string;
role?: AgentRole;
action?: string;
timeout?: number | string;
retry?: YAMLRetryConfig;
on?: Record<string, YAMLTransition | string>;
branches?: Record<string, string>;
conditions?: YAMLCondition[];
subworkflow?: string;
loop?: YAMLLoopConfig;
metadata?: Record<string, unknown>;
}
export interface YAMLTransition {
target: string;
condition?: YAMLCondition;
guard?: string;
}
export interface YAMLCondition {
type: 'equals' | 'contains' | 'exists' | 'custom';
field: string;
value?: unknown;
}
export interface YAMLRetryConfig {
maxAttempts: number;
backoff?: 'fixed' | 'exponential' | 'linear';
initialDelay?: number | string;
maxDelay?: number | string;
}
export interface YAMLLoopConfig {
maxIterations: number;
iterator?: string;
body: string;
exitCondition?: YAMLCondition;
}
// ============================================================================
// Workflow Parser
// ============================================================================
/**
* WorkflowParser - Parses YAML workflows to state machine definitions
*/
export class WorkflowParser {
/**
* Parse a YAML workflow to a state machine definition
*/
parse(yaml: YAMLWorkflow): StateMachineDefinition {
const states: Record<string, State> = {};
for (const [stateId, yamlState] of Object.entries(yaml.states)) {
states[stateId] = this.parseState(stateId, yamlState);
}
return {
id: yaml.id,
name: yaml.name,
version: yaml.version || '1.0.0',
description: yaml.description,
initial: yaml.initial,
states,
events: yaml.events,
context: yaml.context
};
}
/**
* Parse a single state
*/
private parseState(stateId: string, yamlState: YAMLState): State {
const state: State = {
id: stateId,
name: stateId,
type: yamlState.type,
agent: yamlState.agent,
action: yamlState.action,
timeout: this.parseDuration(yamlState.timeout),
metadata: {
...yamlState.metadata,
role: yamlState.role
}
};
// Parse retry config
if (yamlState.retry) {
state.retry = {
maxAttempts: yamlState.retry.maxAttempts,
backoff: yamlState.retry.backoff || 'exponential',
initialDelay: this.parseDuration(yamlState.retry.initialDelay) || 1000,
maxDelay: this.parseDuration(yamlState.retry.maxDelay) || 60000
};
}
// Parse transitions (on)
if (yamlState.on) {
const transitions = this.parseTransitions(yamlState.on);
state.onExit = transitions;
}
// Parse parallel branches
if (yamlState.branches) {
state.type = 'parallel';
state.onEnter = Object.entries(yamlState.branches).map(([event, target]) => ({
event,
target
}));
}
// Parse loop config
if (yamlState.loop) {
state.type = 'loop';
state.metadata = {
...state.metadata,
maxIterations: yamlState.loop.maxIterations,
iterator: yamlState.loop.iterator,
body: yamlState.loop.body
};
// Add loop transitions
state.onExit = [
{ event: 'continue', target: yamlState.loop.body },
{ event: 'exit', target: yamlState.on?.['exit'] as string || 'end' }
];
}
// Parse subworkflow
if (yamlState.subworkflow) {
state.type = 'action';
state.action = 'subworkflow';
state.metadata = {
...state.metadata,
subworkflow: yamlState.subworkflow
};
}
return state;
}
/**
* Parse transitions from YAML format
*/
private parseTransitions(on: Record<string, YAMLTransition | string>): Transition[] {
const transitions: Transition[] = [];
for (const [event, transition] of Object.entries(on)) {
if (typeof transition === 'string') {
transitions.push({ event, target: transition });
} else {
transitions.push({
event,
target: transition.target,
condition: transition.condition ? this.parseCondition(transition.condition) : undefined,
guard: transition.guard
});
}
}
return transitions;
}
/**
* Parse a condition
*/
private parseCondition(yamlCond: YAMLCondition): Transition['condition'] {
return {
type: yamlCond.type,
field: yamlCond.field,
value: yamlCond.value
};
}
/**
* Parse duration string (e.g., '30s', '5m', '1h')
*/
private parseDuration(duration?: number | string): number | undefined {
if (typeof duration === 'number') return duration;
if (!duration) return undefined;
const match = duration.match(/^(\d+)(ms|s|m|h)?$/);
if (!match) return undefined;
const value = parseInt(match[1]);
const unit = match[2] || 'ms';
switch (unit) {
case 'ms': return value;
case 's': return value * 1000;
case 'm': return value * 60 * 1000;
case 'h': return value * 60 * 60 * 1000;
default: return value;
}
}
}
// ============================================================================
// Workflow Registry
// ============================================================================
/**
* WorkflowRegistry - Manages workflow definitions
*/
export class WorkflowRegistry {
private workflows: Map<string, YAMLWorkflow> = new Map();
private parser: WorkflowParser;
constructor() {
this.parser = new WorkflowParser();
}
/**
* Register a workflow from YAML object
*/
register(yaml: YAMLWorkflow): StateMachineDefinition {
this.workflows.set(yaml.id, yaml);
return this.parser.parse(yaml);
}
/**
* Get a workflow by ID
*/
get(id: string): YAMLWorkflow | undefined {
return this.workflows.get(id);
}
/**
* Get parsed state machine definition
*/
getParsed(id: string): StateMachineDefinition | undefined {
const yaml = this.workflows.get(id);
if (yaml) {
return this.parser.parse(yaml);
}
return undefined;
}
/**
* List all workflows
*/
list(): string[] {
return Array.from(this.workflows.keys());
}
}
// ============================================================================
// Predefined Workflows
// ============================================================================
/**
* Standard Code Pipeline Workflow
*
* Code → Review → Test → Done
* With max 3 review iterations
*/
export const CODE_PIPELINE_WORKFLOW: YAMLWorkflow = {
id: 'code-pipeline',
name: 'Code Pipeline',
version: '1.0.0',
description: 'Code → Review → Test pipeline with deterministic flow',
initial: 'start',
context: {
reviewIteration: 0,
maxReviewIterations: 3
},
states: {
start: {
type: 'start',
on: {
'start': 'code'
}
},
code: {
type: 'action',
role: 'programmer',
timeout: '30m',
retry: {
maxAttempts: 2,
backoff: 'exponential',
initialDelay: '5s',
maxDelay: '1m'
},
on: {
'completed': 'review',
'failed': 'failed'
}
},
review: {
type: 'choice',
conditions: [
{ type: 'equals', field: 'reviewApproved', value: true }
],
on: {
'approved': 'test',
'rejected': 'review_loop',
'failed': 'failed'
}
},
review_loop: {
type: 'loop',
loop: {
maxIterations: 3,
body: 'code'
},
on: {
'exit': 'failed'
}
},
test: {
type: 'action',
role: 'tester',
timeout: '15m',
on: {
'passed': 'end',
'failed': 'test_failed'
}
},
test_failed: {
type: 'choice',
on: {
'retry': 'code',
'abort': 'failed'
}
},
end: {
type: 'end'
},
failed: {
type: 'end',
metadata: { status: 'failed' }
}
}
};
/**
* Parallel Multi-Project Workflow
*
* Runs multiple projects in parallel
*/
export const PARALLEL_PROJECTS_WORKFLOW: YAMLWorkflow = {
id: 'parallel-projects',
name: 'Parallel Projects Pipeline',
version: '1.0.0',
description: 'Run multiple projects in parallel with synchronized completion',
initial: 'start',
states: {
start: {
type: 'start',
on: {
'start': 'parallel'
}
},
parallel: {
type: 'parallel',
branches: {
'project1': 'project1_code',
'project2': 'project2_code',
'project3': 'project3_code',
'project4': 'project4_code'
},
on: {
'all_completed': 'end',
'any_failed': 'failed'
}
},
project1_code: {
type: 'action',
role: 'programmer',
agent: 'project1-programmer',
on: { 'completed': 'project1_review' }
},
project1_review: {
type: 'action',
role: 'reviewer',
agent: 'project1-reviewer',
on: { 'completed': 'project1_test' }
},
project1_test: {
type: 'action',
role: 'tester',
agent: 'project1-tester',
on: { 'completed': 'join' }
},
project2_code: {
type: 'action',
role: 'programmer',
agent: 'project2-programmer',
on: { 'completed': 'project2_review' }
},
project2_review: {
type: 'action',
role: 'reviewer',
agent: 'project2-reviewer',
on: { 'completed': 'project2_test' }
},
project2_test: {
type: 'action',
role: 'tester',
agent: 'project2-tester',
on: { 'completed': 'join' }
},
project3_code: {
type: 'action',
role: 'programmer',
agent: 'project3-programmer',
on: { 'completed': 'project3_review' }
},
project3_review: {
type: 'action',
role: 'reviewer',
agent: 'project3-reviewer',
on: { 'completed': 'project3_test' }
},
project3_test: {
type: 'action',
role: 'tester',
agent: 'project3-tester',
on: { 'completed': 'join' }
},
project4_code: {
type: 'action',
role: 'programmer',
agent: 'project4-programmer',
on: { 'completed': 'project4_review' }
},
project4_review: {
type: 'action',
role: 'reviewer',
agent: 'project4-reviewer',
on: { 'completed': 'project4_test' }
},
project4_test: {
type: 'action',
role: 'tester',
agent: 'project4-tester',
on: { 'completed': 'join' }
},
join: {
type: 'wait',
on: {
'all_joined': 'end'
}
},
end: {
type: 'end'
},
failed: {
type: 'end',
metadata: { status: 'failed' }
}
}
};
/**
* Human-in-the-Loop Workflow
*/
export const HUMAN_APPROVAL_WORKFLOW: YAMLWorkflow = {
id: 'human-approval',
name: 'Human Approval Workflow',
version: '1.0.0',
description: 'Workflow with human approval gates',
initial: 'start',
states: {
start: {
type: 'start',
on: { 'start': 'plan' }
},
plan: {
type: 'action',
role: 'planner',
on: { 'completed': 'await_approval' }
},
await_approval: {
type: 'wait',
timeout: '24h',
on: {
'approved': 'execute',
'rejected': 'plan',
'timeout': 'notify_timeout'
}
},
notify_timeout: {
type: 'action',
action: 'notify',
metadata: { message: 'Approval timeout' },
on: { 'completed': 'await_approval' }
},
execute: {
type: 'action',
role: 'programmer',
on: { 'completed': 'review' }
},
review: {
type: 'action',
role: 'reviewer',
on: { 'completed': 'end' }
},
end: {
type: 'end'
}
}
};
// Default registry with predefined workflows
export const defaultWorkflowRegistry = new WorkflowRegistry();
// Register predefined workflows
defaultWorkflowRegistry.register(CODE_PIPELINE_WORKFLOW);
defaultWorkflowRegistry.register(PARALLEL_PROJECTS_WORKFLOW);
defaultWorkflowRegistry.register(HUMAN_APPROVAL_WORKFLOW);

View File

@@ -0,0 +1,642 @@
/**
* Agent Workspace Isolation
*
* Each agent gets its own tools, memory, identity, and workspace.
* Provides isolation and resource management for parallel agents.
*/
import { randomUUID } from 'crypto';
import { EventEmitter } from 'events';
import { mkdirSync, rmSync, existsSync, writeFileSync, readFileSync, readdirSync, statSync } from 'fs';
import { join, resolve, relative } from 'path';
// ============================================================================
// Types
// ============================================================================
export type Permission = 'read' | 'write' | 'execute' | 'delete' | 'network' | 'git';
export interface WorkspaceConfig {
id: string;
projectId: string;
agentId: string;
role: string;
basePath: string;
permissions: Permission[];
resourceLimits: ResourceLimits;
environment: Record<string, string>;
mountPoints: MountPoint[];
}
export interface ResourceLimits {
maxMemoryMB: number;
maxCpuPercent: number;
maxFileSizeMB: number;
maxExecutionTimeMs: number;
maxFileCount: number;
}
export interface MountPoint {
source: string;
target: string;
readOnly: boolean;
}
export interface AgentTool {
name: string;
description: string;
permissions: Permission[];
execute: (params: unknown, context: ToolContext) => Promise<ToolResult>;
}
export interface ToolContext {
workspace: WorkspaceManager;
agentId: string;
sessionId: string;
permissions: Permission[];
}
export interface ToolResult {
success: boolean;
output?: unknown;
error?: string;
metadata?: Record<string, unknown>;
}
export interface MemoryStore {
shortTerm: Map<string, unknown>;
longTerm: Map<string, unknown>;
session: Map<string, unknown>;
}
export interface AgentIdentity {
id: string;
name: string;
role: string;
description: string;
personality: string;
systemPrompt: string;
capabilities: string[];
constraints: string[];
}
// ============================================================================
// Workspace Manager
// ============================================================================
/**
* WorkspaceManager - Isolated workspace for an agent
*/
export class WorkspaceManager extends EventEmitter {
private config: WorkspaceConfig;
private workspacePath: string;
private memory: MemoryStore;
private identity: AgentIdentity;
private tools: Map<string, AgentTool> = new Map();
private fileHandles: Map<string, unknown> = new Map();
private active = true;
constructor(config: WorkspaceConfig) {
super();
this.config = config;
this.workspacePath = resolve(config.basePath, config.projectId, config.agentId);
this.memory = {
shortTerm: new Map(),
longTerm: new Map(),
session: new Map()
};
this.initializeWorkspace();
}
/**
* Initialize the workspace directory
*/
private initializeWorkspace(): void {
if (!existsSync(this.workspacePath)) {
mkdirSync(this.workspacePath, { recursive: true });
}
// Create subdirectories
const subdirs = ['memory', 'output', 'cache', 'logs'];
for (const dir of subdirs) {
const path = join(this.workspacePath, dir);
if (!existsSync(path)) {
mkdirSync(path, { recursive: true });
}
}
this.emit('workspaceInitialized', { path: this.workspacePath });
}
/**
* Set agent identity
*/
setIdentity(identity: AgentIdentity): void {
this.identity = identity;
this.emit('identitySet', { identity });
}
/**
* Get agent identity
*/
getIdentity(): AgentIdentity | undefined {
return this.identity;
}
/**
* Register a tool
*/
registerTool(tool: AgentTool): void {
// Check if agent has required permissions
const hasPermission = tool.permissions.every(p =>
this.config.permissions.includes(p)
);
if (!hasPermission) {
throw new Error(`Agent does not have required permissions for tool: ${tool.name}`);
}
this.tools.set(tool.name, tool);
this.emit('toolRegistered', { tool });
}
/**
* Unregister a tool
*/
unregisterTool(name: string): boolean {
return this.tools.delete(name);
}
/**
* Execute a tool
*/
async executeTool(name: string, params: unknown): Promise<ToolResult> {
const tool = this.tools.get(name);
if (!tool) {
return { success: false, error: `Tool not found: ${name}` };
}
const context: ToolContext = {
workspace: this,
agentId: this.config.agentId,
sessionId: this.config.id,
permissions: this.config.permissions
};
try {
const result = await tool.execute(params, context);
this.emit('toolExecuted', { name, params, result });
return result;
} catch (error) {
const result: ToolResult = {
success: false,
error: error instanceof Error ? error.message : String(error)
};
this.emit('toolError', { name, params, error: result.error });
return result;
}
}
/**
* Get available tools
*/
getAvailableTools(): AgentTool[] {
return Array.from(this.tools.values());
}
// ============================================================================
// Memory Management
// ============================================================================
/**
* Store value in short-term memory
*/
remember(key: string, value: unknown): void {
this.memory.shortTerm.set(key, value);
this.emit('memoryStored', { type: 'shortTerm', key });
}
/**
* Store value in long-term memory
*/
memorize(key: string, value: unknown): void {
this.memory.longTerm.set(key, value);
this.saveMemoryToFile(key, value, 'longTerm');
this.emit('memoryStored', { type: 'longTerm', key });
}
/**
* Store value in session memory
*/
storeSession(key: string, value: unknown): void {
this.memory.session.set(key, value);
this.emit('memoryStored', { type: 'session', key });
}
/**
* Retrieve value from memory
*/
recall(key: string): unknown | undefined {
return (
this.memory.shortTerm.get(key) ||
this.memory.longTerm.get(key) ||
this.memory.session.get(key)
);
}
/**
* Check if memory exists
*/
hasMemory(key: string): boolean {
return (
this.memory.shortTerm.has(key) ||
this.memory.longTerm.has(key) ||
this.memory.session.has(key)
);
}
/**
* Forget a memory
*/
forget(key: string): boolean {
return (
this.memory.shortTerm.delete(key) ||
this.memory.longTerm.delete(key) ||
this.memory.session.delete(key)
);
}
/**
* Clear all short-term memory
*/
clearShortTerm(): void {
this.memory.shortTerm.clear();
this.emit('memoryCleared', { type: 'shortTerm' });
}
/**
* Clear session memory
*/
clearSession(): void {
this.memory.session.clear();
this.emit('memoryCleared', { type: 'session' });
}
/**
* Save memory to file
*/
private saveMemoryToFile(key: string, value: unknown, type: string): void {
const memoryPath = join(this.workspacePath, 'memory', `${type}.json`);
let data: Record<string, unknown> = {};
if (existsSync(memoryPath)) {
try {
data = JSON.parse(readFileSync(memoryPath, 'utf-8'));
} catch {
data = {};
}
}
data[key] = value;
writeFileSync(memoryPath, JSON.stringify(data, null, 2), 'utf-8');
}
/**
* Load long-term memory from file
*/
loadLongTermMemory(): void {
const memoryPath = join(this.workspacePath, 'memory', 'longTerm.json');
if (existsSync(memoryPath)) {
try {
const data = JSON.parse(readFileSync(memoryPath, 'utf-8'));
for (const [key, value] of Object.entries(data)) {
this.memory.longTerm.set(key, value);
}
} catch {
// Ignore errors
}
}
}
// ============================================================================
// File Operations
// ============================================================================
/**
* Read a file
*/
readFile(path: string): string {
this.checkPermission('read');
const fullPath = this.resolvePath(path);
this.checkPathInWorkspace(fullPath);
return readFileSync(fullPath, 'utf-8');
}
/**
* Write a file
*/
writeFile(path: string, content: string): void {
this.checkPermission('write');
const fullPath = this.resolvePath(path);
this.checkPathInWorkspace(fullPath);
this.checkFileSize(content.length);
writeFileSync(fullPath, content, 'utf-8');
this.emit('fileWritten', { path: fullPath });
}
/**
* Delete a file
*/
deleteFile(path: string): void {
this.checkPermission('delete');
const fullPath = this.resolvePath(path);
this.checkPathInWorkspace(fullPath);
rmSync(fullPath, { force: true });
this.emit('fileDeleted', { path: fullPath });
}
/**
* List files in a directory
*/
listFiles(path: string = ''): string[] {
this.checkPermission('read');
const fullPath = this.resolvePath(path);
this.checkPathInWorkspace(fullPath);
if (!existsSync(fullPath)) return [];
return readdirSync(fullPath).map(name => join(path, name));
}
/**
* Check if file exists
*/
fileExists(path: string): boolean {
const fullPath = this.resolvePath(path);
this.checkPathInWorkspace(fullPath);
return existsSync(fullPath);
}
/**
* Get file stats
*/
getFileStats(path: string): { size: number; modified: Date; isDirectory: boolean } | null {
const fullPath = this.resolvePath(path);
this.checkPathInWorkspace(fullPath);
if (!existsSync(fullPath)) return null;
const stats = statSync(fullPath);
return {
size: stats.size,
modified: stats.mtime,
isDirectory: stats.isDirectory()
};
}
// ============================================================================
// Permission & Security
// ============================================================================
/**
* Check if agent has a permission
*/
hasPermission(permission: Permission): boolean {
return this.config.permissions.includes(permission);
}
/**
* Check permission and throw if missing
*/
private checkPermission(permission: Permission): void {
if (!this.hasPermission(permission)) {
throw new Error(`Permission denied: ${permission}`);
}
}
/**
* Resolve path relative to workspace
*/
private resolvePath(path: string): string {
return resolve(this.workspacePath, path);
}
/**
* Check if path is within workspace
*/
private checkPathInWorkspace(fullPath: string): void {
const relativePath = relative(this.workspacePath, fullPath);
if (relativePath.startsWith('..') || relativePath.startsWith('/')) {
throw new Error('Path is outside workspace boundaries');
}
}
/**
* Check file size limit
*/
private checkFileSize(size: number): void {
const maxBytes = this.config.resourceLimits.maxFileSizeMB * 1024 * 1024;
if (size > maxBytes) {
throw new Error(`File size exceeds limit: ${this.config.resourceLimits.maxFileSizeMB}MB`);
}
}
// ============================================================================
// Lifecycle
// ============================================================================
/**
* Get workspace path
*/
getPath(): string {
return this.workspacePath;
}
/**
* Get workspace config
*/
getConfig(): WorkspaceConfig {
return { ...this.config };
}
/**
* Clean up workspace
*/
cleanup(): void {
this.active = false;
this.clearSession();
this.emit('workspaceCleanup', { path: this.workspacePath });
}
/**
* Destroy workspace (delete files)
*/
destroy(): void {
this.cleanup();
if (existsSync(this.workspacePath)) {
rmSync(this.workspacePath, { recursive: true, force: true });
}
this.emit('workspaceDestroyed', { path: this.workspacePath });
}
/**
* Export workspace state
*/
exportState(): {
config: WorkspaceConfig;
memory: Record<string, unknown>;
identity?: AgentIdentity;
tools: string[];
} {
return {
config: this.getConfig(),
memory: {
shortTerm: Object.fromEntries(this.memory.shortTerm),
longTerm: Object.fromEntries(this.memory.longTerm),
session: Object.fromEntries(this.memory.session)
},
identity: this.identity,
tools: Array.from(this.tools.keys())
};
}
}
// ============================================================================
// Workspace Factory
// ============================================================================
/**
* WorkspaceFactory - Creates and manages workspaces
*/
export class WorkspaceFactory {
private basePath: string;
private workspaces: Map<string, WorkspaceManager> = new Map();
constructor(basePath: string = './workspaces') {
this.basePath = resolve(basePath);
if (!existsSync(this.basePath)) {
mkdirSync(this.basePath, { recursive: true });
}
}
/**
* Create a new workspace
*/
createWorkspace(config: {
projectId: string;
agentId: string;
role: string;
permissions?: Permission[];
resourceLimits?: Partial<ResourceLimits>;
}): WorkspaceManager {
const id = `ws-${randomUUID().substring(0, 8)}`;
const fullConfig: WorkspaceConfig = {
id,
projectId: config.projectId,
agentId: config.agentId,
role: config.role,
basePath: this.basePath,
permissions: config.permissions || ['read'],
resourceLimits: {
maxMemoryMB: 512,
maxCpuPercent: 50,
maxFileSizeMB: 10,
maxExecutionTimeMs: 60000,
maxFileCount: 1000,
...config.resourceLimits
},
environment: {},
mountPoints: []
};
const workspace = new WorkspaceManager(fullConfig);
this.workspaces.set(id, workspace);
return workspace;
}
/**
* Get a workspace by ID
*/
getWorkspace(id: string): WorkspaceManager | undefined {
return this.workspaces.get(id);
}
/**
* Get workspaces by project
*/
getWorkspacesByProject(projectId: string): WorkspaceManager[] {
return Array.from(this.workspaces.values())
.filter(w => w.getConfig().projectId === projectId);
}
/**
* Get all workspaces
*/
getAllWorkspaces(): WorkspaceManager[] {
return Array.from(this.workspaces.values());
}
/**
* Destroy a workspace
*/
destroyWorkspace(id: string): boolean {
const workspace = this.workspaces.get(id);
if (workspace) {
workspace.destroy();
return this.workspaces.delete(id);
}
return false;
}
/**
* Destroy all workspaces for a project
*/
destroyProjectWorkspaces(projectId: string): number {
const projectWorkspaces = this.getWorkspacesByProject(projectId);
let count = 0;
for (const workspace of projectWorkspaces) {
workspace.destroy();
this.workspaces.delete(workspace.getConfig().id);
count++;
}
return count;
}
/**
* Get factory stats
*/
getStats(): {
totalWorkspaces: number;
byProject: Record<string, number>;
byRole: Record<string, number>;
} {
const byProject: Record<string, number> = {};
const byRole: Record<string, number> = {};
for (const workspace of this.workspaces.values()) {
const config = workspace.getConfig();
byProject[config.projectId] = (byProject[config.projectId] || 0) + 1;
byRole[config.role] = (byRole[config.role] || 0) + 1;
}
return {
totalWorkspaces: this.workspaces.size,
byProject,
byRole
};
}
}
// Default factory instance
export const defaultWorkspaceFactory = new WorkspaceFactory();