refactor: simplify ralph wizard from 9 agents to 2

Remove execution layer that never actually controlled execution:
- execution-bridge.ts (model param was ignored)
- group-scheduler.ts (task-tool mode never used)
- model-selector.ts (recommendations were display-only)
- context-manager.ts (never invoked)
- execution-limits.ts

Remove redundant agent prompts (overlapping outputs):
- requirements-analyst, architecture-planner, risk-analyst
- testing-specialist, verification (merged into planner.ts)
- execution-optimizer (output was ignored)
- final-review (scores were cosmetic)

Simplify plan-orchestrator.ts from 2400 LOC to 520 LOC:
- Before: 9 agents, 6 phases, ~40-60 minutes
- After: 2 agents (research + planner), ~18 minutes

Remove /api/execution/* endpoints and related server code.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-01-30 12:15:09 +01:00
co-authored by Claude Opus 4.5
parent 0020ae5a3e
commit a92826b66d
18 changed files with 329 additions and 5468 deletions
-342
View File
@@ -34,13 +34,6 @@ import { v4 as uuidv4 } from 'uuid';
import { createRequire } from 'node:module';
import { RunSummaryTracker } from '../run-summary.js';
import { PlanOrchestrator, type DetailedPlanResult } from '../plan-orchestrator.js';
import {
ExecutionBridge,
getExecutionBridge,
type PlanItem as ExecutionPlanItem,
type AgentSpawner,
} from '../execution-bridge.js';
import { type ModelConfig } from '../model-selector.js';
// Load version from package.json
const require = createRequire(import.meta.url);
@@ -314,8 +307,6 @@ export class WebServer extends EventEmitter {
private pendingRespawnStarts: Map<string, NodeJS.Timeout> = new Map();
// Active plan orchestrators (for cancellation via API)
private activePlanOrchestrators: Map<string, PlanOrchestrator> = new Map();
// Execution bridge for parallel task execution
private executionBridge: ExecutionBridge;
// Grace period before starting restored respawn controllers (2 minutes)
private static readonly RESPAWN_RESTORE_GRACE_PERIOD_MS = 2 * 60 * 1000;
@@ -346,10 +337,6 @@ export class WebServer extends EventEmitter {
this.broadcast('screen:statsUpdated', screens);
});
// Initialize execution bridge with model config from settings
this.executionBridge = getExecutionBridge(this.loadModelConfig());
this.setupExecutionBridgeListeners();
// Set up subagent watcher listeners
this.setupSubagentWatcherListeners();
@@ -3045,148 +3032,6 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
return this.getSystemStats();
});
// ========== Execution Bridge Endpoints ==========
// Get execution status
this.app.get('/api/execution/status', async () => {
return {
success: true,
data: {
status: this.executionBridge.status,
progress: this.executionBridge.getProgress(),
},
};
});
// Get execution schedule (the current plan)
this.app.get('/api/execution/schedule', async () => {
const schedule = this.executionBridge['_scheduler'].schedule;
return { success: true, data: schedule };
});
// Load a plan for execution
// Accepts either ExecutionPlanItem format (id, title, description) or
// PlanOrchestrator PlanItem format (id, content) and converts as needed
this.app.post('/api/execution/load', async (req) => {
const { items, workingDir } = req.body as {
items: Array<{
id?: string;
title?: string;
description?: string;
content?: string;
parallelGroup?: number;
agentType?: string;
recommendedModel?: string;
requiresFreshContext?: boolean;
estimatedTokens?: number;
inputFiles?: string[];
outputFiles?: string[];
dependencies?: string[];
}>;
workingDir?: string;
};
if (!items || !Array.isArray(items)) {
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'items array is required');
}
try {
if (workingDir) {
this.executionBridge.setWorkingDir(workingDir);
}
// Convert to ExecutionPlanItem format
const execItems: ExecutionPlanItem[] = items.map((item, idx) => ({
id: item.id || `task-${idx}`,
title: item.title || item.content?.slice(0, 50) || `Task ${idx + 1}`,
description: item.description || item.content || '',
parallelGroup: item.parallelGroup,
agentType: item.agentType,
recommendedModel: item.recommendedModel,
requiresFreshContext: item.requiresFreshContext,
estimatedTokens: item.estimatedTokens,
inputFiles: item.inputFiles,
outputFiles: item.outputFiles,
dependencies: item.dependencies,
}));
const schedule = this.executionBridge.loadPlan(execItems);
return { success: true, data: schedule };
} catch (err) {
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, getErrorMessage(err));
}
});
// Start execution
this.app.post('/api/execution/start', async () => {
try {
await this.executionBridge.start();
return { success: true };
} catch (err) {
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, getErrorMessage(err));
}
});
// Pause execution
this.app.post('/api/execution/pause', async () => {
this.executionBridge.pause();
return { success: true };
});
// Resume execution
this.app.post('/api/execution/resume', async () => {
this.executionBridge.resume();
return { success: true };
});
// Cancel execution
this.app.post('/api/execution/cancel', async (req) => {
const { reason } = (req.body as { reason?: string }) || {};
await this.executionBridge.cancel(reason || 'Cancelled via API');
return { success: true };
});
// Reset execution bridge
this.app.post('/api/execution/reset', async () => {
this.executionBridge.reset();
return { success: true };
});
// Get execution history
this.app.get('/api/execution/history', async () => {
return { success: true, data: this.executionBridge.getHistory() };
});
// Get model configuration
this.app.get('/api/execution/model-config', async () => {
return { success: true, data: this.executionBridge.getModelConfig() };
});
// Update model configuration
this.app.put('/api/execution/model-config', async (req) => {
const config = req.body as Partial<ModelConfig>;
try {
this.executionBridge.updateModelConfig(config);
const fullConfig = this.executionBridge.getModelConfig();
this.saveModelConfig(fullConfig);
this.broadcast('execution:modelConfigUpdated', fullConfig);
return { success: true, data: fullConfig };
} catch (err) {
return createErrorResponse(ApiErrorCode.OPERATION_FAILED, getErrorMessage(err));
}
});
// Mark task complete (external signal)
this.app.post('/api/execution/tasks/:taskId/complete', async (req) => {
const { taskId } = req.params as { taskId: string };
this.executionBridge.markTaskComplete(taskId);
return { success: true };
});
// Mark task failed (external signal)
this.app.post('/api/execution/tasks/:taskId/failed', async (req) => {
const { taskId } = req.params as { taskId: string };
const { error } = (req.body as { error?: string }) || {};
this.executionBridge.markTaskFailed(taskId, error || 'Failed via API');
return { success: true };
});
// ========== Subagent Monitoring (Claude Code Background Agents) ==========
// List all known subagents
@@ -3910,193 +3755,6 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
});
}
/**
* Load model configuration from settings file.
*/
private loadModelConfig(): Partial<ModelConfig> {
const settingsPath = join(homedir(), '.claudeman', 'settings.json');
try {
if (existsSync(settingsPath)) {
const content = readFileSync(settingsPath, 'utf-8');
const settings = JSON.parse(content);
return settings.modelConfig || {};
}
} catch (err) {
console.error('Failed to load model config:', getErrorMessage(err));
}
return {};
}
/**
* Save model configuration to settings file.
*/
private saveModelConfig(config: ModelConfig): void {
const settingsPath = join(homedir(), '.claudeman', 'settings.json');
try {
const dir = dirname(settingsPath);
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
let settings: Record<string, unknown> = {};
if (existsSync(settingsPath)) {
settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
}
settings.modelConfig = config;
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
} catch (err) {
console.error('Failed to save model config:', getErrorMessage(err));
}
}
/**
* Set up event listeners for execution bridge.
* Broadcasts execution progress to SSE clients.
*/
private setupExecutionBridgeListeners(): void {
// Create agent spawner interface
const spawner: AgentSpawner = {
spawnAgentWithModel: async (taskId, workingDir, prompt, _model, _options) => {
// Note: model and options are reserved for future model selection integration
const globalNice = this.getGlobalNiceConfig();
const session = new Session({
workingDir,
screenManager: this.screenManager,
useScreen: true,
mode: 'claude',
name: `exec:${taskId}`,
niceConfig: globalNice,
});
this.sessions.set(session.id, session);
this.store.incrementSessionsCreated();
this.setupSessionListeners(session);
await session.startInteractive();
this.broadcast('session:created', session.toDetailedState());
this.broadcast('session:interactive', { id: session.id });
this.persistSessionState(session);
// Configure ralph tracker for completion detection
session.ralphTracker.enable();
// Set up completion listener for execution bridge
session.on('ralphCompletionDetected', () => {
this.executionBridge.markTaskComplete(taskId);
});
// Send the prompt
setTimeout(() => {
session.writeViaScreen(prompt + '\r');
}, 2000);
return { sessionId: session.id };
},
useTaskTool: async (_sessionId, _taskId, _prompt, _model) => {
// Task tool mode - not yet implemented, falls back to session mode
throw new Error('Task tool mode not yet implemented');
},
isTaskComplete: (taskId) => {
// Check if task is marked complete in scheduler
const schedule = this.executionBridge['_scheduler'].schedule;
if (!schedule) return false;
for (const group of schedule.groups) {
const task = group.tasks.find(t => t.id === taskId);
if (task) return task.status === 'completed';
}
return false;
},
getTaskResult: (taskId) => {
const schedule = this.executionBridge['_scheduler'].schedule;
if (!schedule) return null;
for (const group of schedule.groups) {
const task = group.tasks.find(t => t.id === taskId);
if (task) {
if (task.status === 'completed') {
return { success: true };
} else if (task.status === 'failed') {
return { success: false, error: task.error };
}
}
}
return null;
},
};
this.executionBridge.setAgentSpawner(spawner);
// Set up session writer for context management
this.executionBridge.setSessionWriter({
writeToSession: (sessionId, text) => {
const session = this.sessions.get(sessionId);
if (session) {
session.writeViaScreen(text);
}
},
createSession: async (workingDir, name) => {
const globalNice = this.getGlobalNiceConfig();
const session = new Session({
workingDir,
screenManager: this.screenManager,
useScreen: true,
mode: 'claude',
name: name || 'exec-context',
niceConfig: globalNice,
});
this.sessions.set(session.id, session);
this.store.incrementSessionsCreated();
this.setupSessionListeners(session);
await session.startInteractive();
this.broadcast('session:created', session.toDetailedState());
this.persistSessionState(session);
return { sessionId: session.id };
},
});
// Forward execution bridge events as SSE broadcasts
this.executionBridge.on('planLoaded', (schedule) => {
this.broadcast('execution:planLoaded', { schedule });
});
this.executionBridge.on('started', () => {
this.broadcast('execution:started', {});
});
this.executionBridge.on('paused', () => {
this.broadcast('execution:paused', {});
});
this.executionBridge.on('resumed', () => {
this.broadcast('execution:resumed', {});
});
this.executionBridge.on('completed', (data) => {
this.broadcast('execution:completed', data);
});
this.executionBridge.on('cancelled', (reason) => {
this.broadcast('execution:cancelled', { reason });
});
this.executionBridge.on('groupStarted', (data) => {
this.broadcast('execution:groupStarted', data);
});
this.executionBridge.on('groupCompleted', (data) => {
this.broadcast('execution:groupCompleted', data);
});
this.executionBridge.on('taskAssigned', (data) => {
this.broadcast('execution:taskAssigned', data);
});
this.executionBridge.on('taskCompleted', (data) => {
this.broadcast('execution:taskCompleted', data);
});
this.executionBridge.on('taskFailed', (data) => {
this.broadcast('execution:taskFailed', data);
});
this.executionBridge.on('freshContext', (data) => {
this.broadcast('execution:freshContext', data);
});
this.executionBridge.on('modelSelected', (data) => {
this.broadcast('execution:modelSelected', data);
});
this.executionBridge.on('progress', (progress) => {
this.broadcast('execution:progress', progress);
});
}
private setupTimedRespawn(sessionId: string, durationMinutes: number): void {
// Clear existing timer if any
const existing = this.respawnTimers.get(sessionId);