claude-mycelium 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/.agent-meta/tasks/_active.json +4 -0
  2. package/.agent-meta/tasks/task_0657b028-05a0-4b0c-b0b9-a4eae3d66cd9.json +168 -0
  3. package/README.md +6 -0
  4. package/dist/agent/task-worker.d.ts +11 -0
  5. package/dist/agent/task-worker.d.ts.map +1 -0
  6. package/dist/agent/task-worker.js +173 -0
  7. package/dist/agent/task-worker.js.map +1 -0
  8. package/dist/cli/gradients.d.ts.map +1 -1
  9. package/dist/cli/gradients.js +1 -0
  10. package/dist/cli/gradients.js.map +1 -1
  11. package/dist/cli/grow.d.ts +17 -0
  12. package/dist/cli/grow.d.ts.map +1 -0
  13. package/dist/cli/grow.js +373 -0
  14. package/dist/cli/grow.js.map +1 -0
  15. package/dist/cli/index.d.ts.map +1 -1
  16. package/dist/cli/index.js +2 -0
  17. package/dist/cli/index.js.map +1 -1
  18. package/dist/core/agent-executor.d.ts +4 -1
  19. package/dist/core/agent-executor.d.ts.map +1 -1
  20. package/dist/core/agent-executor.js +10 -2
  21. package/dist/core/agent-executor.js.map +1 -1
  22. package/dist/task/agent-coordinator.d.ts +40 -0
  23. package/dist/task/agent-coordinator.d.ts.map +1 -0
  24. package/dist/task/agent-coordinator.js +168 -0
  25. package/dist/task/agent-coordinator.js.map +1 -0
  26. package/dist/task/executor.d.ts +9 -2
  27. package/dist/task/executor.d.ts.map +1 -1
  28. package/dist/task/executor.js +64 -31
  29. package/dist/task/executor.js.map +1 -1
  30. package/docs/ROADMAP.md +139 -59
  31. package/package.json +10 -2
  32. package/src/agent/task-worker.ts +196 -0
  33. package/src/cli/gradients.ts +2 -0
  34. package/src/cli/grow.ts +416 -0
  35. package/src/cli/index.ts +2 -0
  36. package/src/core/agent-executor.ts +17 -4
  37. package/src/task/agent-coordinator.ts +220 -0
  38. package/src/task/executor.ts +71 -66
  39. package/tests/trace/trace-event.test.ts +62 -20
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Task Agent Coordinator
3
+ *
4
+ * Coordinates multi-agent execution of task steps using the mycelium
5
+ * process spawning system (Phase 3) for true RALPH-style parallelization.
6
+ *
7
+ * This replaces the simple Promise.all() approach with real agent processes
8
+ * that use file locks, inhibitors, and the full executeAgent() cycle.
9
+ */
10
+
11
+ import { fork, ChildProcess } from 'child_process';
12
+ import { Task, TaskStep, Mode } from '../types/index.js';
13
+ import { logDebug, logError, logInfo } from '../utils/logger.js';
14
+ import { randomUUID } from 'crypto';
15
+ import * as path from 'path';
16
+ import { fileURLToPath } from 'url';
17
+ import { dirname } from 'path';
18
+
19
+ const __filename = fileURLToPath(import.meta.url);
20
+ const __dirname = dirname(__filename);
21
+
22
+ export interface StepAssignment {
23
+ stepOrder: number;
24
+ targetFile: string;
25
+ mode: Mode;
26
+ description: string;
27
+ taskId: string;
28
+ }
29
+
30
+ export interface AgentResult {
31
+ stepOrder: number;
32
+ success: boolean;
33
+ traceId?: string;
34
+ error?: string;
35
+ exitCode: number | null;
36
+ }
37
+
38
+ /**
39
+ * Spawn an agent for a specific task step
40
+ * Each agent runs as an independent process via child_process.fork()
41
+ */
42
+ export function spawnAgentForStep(
43
+ task: Task,
44
+ step: TaskStep
45
+ ): Promise<AgentResult> {
46
+ return new Promise((resolve) => {
47
+ const agentId = `agent-${randomUUID().substring(0, 8)}`;
48
+
49
+ logDebug('Spawning agent for task step', {
50
+ agentId,
51
+ taskId: task.id,
52
+ stepOrder: step.order,
53
+ targetFile: step.target_file,
54
+ mode: step.mode,
55
+ });
56
+
57
+ // Path to the compiled task worker file
58
+ const workerPath = path.join(__dirname, '../agent/task-worker.js');
59
+
60
+ // Spawn agent process with task context
61
+ const child = fork(workerPath, [], {
62
+ env: {
63
+ ...process.env,
64
+ AGENT_ID: agentId,
65
+ TASK_ID: task.id,
66
+ STEP_ORDER: String(step.order),
67
+ TARGET_FILE: step.target_file,
68
+ MODE: step.mode,
69
+ STEP_DESCRIPTION: step.description,
70
+ },
71
+ stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
72
+ });
73
+
74
+ let result: AgentResult = {
75
+ stepOrder: step.order,
76
+ success: false,
77
+ exitCode: null,
78
+ };
79
+
80
+ // Set timeout (5 minutes)
81
+ const timeout = setTimeout(() => {
82
+ logError('Agent timeout', new Error('Agent timed out'), {
83
+ agentId,
84
+ stepOrder: step.order,
85
+ });
86
+ child.kill('SIGTERM');
87
+ result.error = 'Agent timed out after 5 minutes';
88
+ result.exitCode = -1;
89
+ resolve(result);
90
+ }, 300_000);
91
+
92
+ // Handle IPC messages from agent
93
+ child.on('message', (message: any) => {
94
+ if (message.type === 'result') {
95
+ logDebug('Agent result received', {
96
+ agentId,
97
+ stepOrder: step.order,
98
+ success: message.success,
99
+ });
100
+
101
+ result = {
102
+ stepOrder: step.order,
103
+ success: message.success,
104
+ traceId: message.traceId,
105
+ error: message.error,
106
+ exitCode: 0,
107
+ };
108
+ } else if (message.type === 'progress') {
109
+ logInfo('Agent progress', {
110
+ agentId,
111
+ stepOrder: step.order,
112
+ message: message.message,
113
+ });
114
+ }
115
+ });
116
+
117
+ // Handle agent exit
118
+ child.on('exit', (code, signal) => {
119
+ clearTimeout(timeout);
120
+
121
+ result.exitCode = code;
122
+
123
+ logInfo('Agent exited', {
124
+ agentId,
125
+ stepOrder: step.order,
126
+ code,
127
+ signal,
128
+ success: result.success,
129
+ });
130
+
131
+ // If we didn't receive a result message, mark as failure
132
+ if (!result.success && !result.error) {
133
+ result.error = `Agent exited with code ${code}`;
134
+ }
135
+
136
+ resolve(result);
137
+ });
138
+
139
+ // Handle errors
140
+ child.on('error', (error) => {
141
+ clearTimeout(timeout);
142
+ logError('Agent error', error, {
143
+ agentId,
144
+ stepOrder: step.order,
145
+ });
146
+
147
+ result.error = error.message;
148
+ result.exitCode = -1;
149
+ resolve(result);
150
+ });
151
+
152
+ // Forward stderr for debugging
153
+ if (child.stderr) {
154
+ child.stderr.on('data', (data) => {
155
+ logDebug('Agent stderr', {
156
+ agentId,
157
+ stepOrder: step.order,
158
+ output: data.toString(),
159
+ });
160
+ });
161
+ }
162
+ });
163
+ }
164
+
165
+ /**
166
+ * Execute a wave of task steps in parallel using agent processes
167
+ * This is the true mycelium multi-agent coordination
168
+ */
169
+ export async function executeWaveWithAgents(
170
+ task: Task,
171
+ wave: TaskStep[]
172
+ ): Promise<AgentResult[]> {
173
+ logInfo('Executing wave with agent processes', {
174
+ taskId: task.id,
175
+ waveSize: wave.length,
176
+ steps: wave.map(s => s.order),
177
+ });
178
+
179
+ // Spawn all agents in parallel
180
+ const agentPromises = wave.map(step => spawnAgentForStep(task, step));
181
+
182
+ // Wait for all agents to complete
183
+ const results = await Promise.all(agentPromises);
184
+
185
+ logInfo('Wave execution complete', {
186
+ taskId: task.id,
187
+ total: results.length,
188
+ successful: results.filter(r => r.success).length,
189
+ failed: results.filter(r => !r.success).length,
190
+ });
191
+
192
+ return results;
193
+ }
194
+
195
+ /**
196
+ * Get parallelizable steps grouped by dependency waves
197
+ * Same logic as before, but now we spawn agents for each wave
198
+ */
199
+ export function getParallelizableSteps(steps: TaskStep[]): TaskStep[][] {
200
+ const waves: TaskStep[][] = [];
201
+ const completed = new Set<number>();
202
+
203
+ while (completed.size < steps.length) {
204
+ // Find steps whose dependencies are all completed
205
+ const ready = steps.filter(
206
+ step =>
207
+ !completed.has(step.order) &&
208
+ step.depends_on.every(dep => completed.has(dep))
209
+ );
210
+
211
+ if (ready.length === 0) {
212
+ throw new Error('Circular dependency detected in task steps');
213
+ }
214
+
215
+ waves.push(ready);
216
+ ready.forEach(step => completed.add(step.order));
217
+ }
218
+
219
+ return waves;
220
+ }
@@ -2,23 +2,33 @@
2
2
  * Task Execution Implementation
3
3
  * Per second-spec §12.2: Task Step Execution
4
4
  *
5
- * Executes task steps in dependency order, tracking progress
6
- * and handling create mode appropriately.
5
+ * Executes task steps using the mycelium multi-agent system.
6
+ * Each step spawns an independent agent process that uses file locks,
7
+ * inhibitors, and the full RALPH-style executeAgent() cycle.
8
+ *
9
+ * This is TRUE multi-agent coordination as per ADR-004.
7
10
  */
8
11
 
9
- import { Task, TaskStep, Mode, ChangeSet, CostRecord } from '../types/index.js';
10
- import { callLLM, calculateCost } from '../llm/anthropic-client.js';
11
- import { logDebug, logError } from '../utils/logger.js';
12
- import { fileExists, readFile, writeFile } from '../utils/file-utils.js';
13
- import { ciProvider } from '../utils/ci-provider.js';
14
- import { createTraceEvent, recordTraceEvent } from '../trace/index.js';
15
- import { calculateGradient } from '../core/gradient.js';
12
+ import { Task, TaskStep } from '../types/index.js';
13
+ import { logDebug, logError, logInfo } from '../utils/logger.js';
14
+ import {
15
+ executeWaveWithAgents,
16
+ getParallelizableSteps,
17
+ type AgentResult,
18
+ } from './agent-coordinator.js';
16
19
 
17
20
  /**
18
21
  * Execute a complete task by running all steps in dependency order
22
+ *
23
+ * This spawns independent agent processes for each step, coordinated
24
+ * via file locks. Multiple agents can work in parallel on different
25
+ * files, implementing true RALPH-style multi-agent coordination.
19
26
  */
20
27
  export async function executeTask(task: Task): Promise<Task> {
21
- logDebug('Executing task', { taskId: task.id, steps: task.plan?.steps.length });
28
+ logInfo('Executing task with multi-agent coordination', {
29
+ taskId: task.id,
30
+ steps: task.plan?.steps.length,
31
+ });
22
32
 
23
33
  if (!task.plan) {
24
34
  throw new Error('Cannot execute task without a plan');
@@ -26,83 +36,82 @@ export async function executeTask(task: Task): Promise<Task> {
26
36
 
27
37
  const waves = getParallelizableSteps(task.plan.steps);
28
38
 
39
+ logInfo('Task execution plan', {
40
+ taskId: task.id,
41
+ totalSteps: task.plan.steps.length,
42
+ waves: waves.length,
43
+ waveSizes: waves.map(w => w.length),
44
+ });
45
+
29
46
  for (let waveIndex = 0; waveIndex < waves.length; waveIndex++) {
30
47
  const wave = waves[waveIndex];
31
- logDebug('Executing wave', { wave: waveIndex + 1, steps: wave.length });
48
+ logInfo('Executing wave', {
49
+ taskId: task.id,
50
+ wave: waveIndex + 1,
51
+ totalWaves: waves.length,
52
+ stepsInWave: wave.length,
53
+ });
32
54
 
33
- // Execute all steps in this wave (they have no dependencies on each other)
34
- const results = await Promise.all(
35
- wave.map(step => executeStep(task, step))
36
- );
55
+ // Execute all steps in this wave using agent processes
56
+ // Each agent runs in its own process with file locks
57
+ const results: AgentResult[] = await executeWaveWithAgents(task, wave);
37
58
 
38
59
  // Check for failures
39
60
  const failed = results.find(r => !r.success);
40
61
  if (failed) {
41
62
  task.status = 'failed';
42
- task.error = failed.error;
43
- logError('Task step failed', new Error(failed.error), { taskId: task.id });
63
+ task.error = failed.error || 'Agent execution failed';
64
+ logError('Task wave failed', new Error(task.error), {
65
+ taskId: task.id,
66
+ wave: waveIndex + 1,
67
+ failedStep: failed.stepOrder,
68
+ });
44
69
  return task;
45
70
  }
46
71
 
47
72
  // Update completed steps
48
73
  for (let i = 0; i < wave.length; i++) {
49
- wave[i].completed = true;
50
- wave[i].trace_id = results[i].trace_id;
51
- task.steps_completed++;
52
- }
74
+ const result = results.find(r => r.stepOrder === wave[i].order);
75
+ if (result && result.success) {
76
+ wave[i].completed = true;
77
+ wave[i].trace_id = result.traceId;
78
+ task.steps_completed++;
79
+
80
+ // Track file changes
81
+ if (wave[i].mode === 'create') {
82
+ task.files_created.push(wave[i].target_file);
83
+ } else {
84
+ if (!task.files_modified.includes(wave[i].target_file)) {
85
+ task.files_modified.push(wave[i].target_file);
86
+ }
87
+ }
53
88
 
54
- // Track file changes
55
- for (let i = 0; i < wave.length; i++) {
56
- if (wave[i].mode === 'create') {
57
- task.files_created.push(wave[i].target_file);
58
- } else {
59
- if (!task.files_modified.includes(wave[i].target_file)) {
60
- task.files_modified.push(wave[i].target_file);
89
+ if (result.traceId) {
90
+ task.traces.push(result.traceId);
61
91
  }
62
92
  }
63
- const traceId = results[i].trace_id;
64
- if (traceId !== undefined) {
65
- task.traces.push(traceId);
66
- }
67
93
  }
94
+
95
+ logInfo('Wave complete', {
96
+ taskId: task.id,
97
+ wave: waveIndex + 1,
98
+ successCount: results.filter(r => r.success).length,
99
+ failureCount: results.filter(r => !r.success).length,
100
+ });
68
101
  }
69
102
 
70
103
  task.status = 'completed';
71
- logDebug('Task execution completed', { taskId: task.id });
72
- return task;
73
- }
74
-
75
- /**
76
- * Execute a single task step
77
- */
78
- export async function executeStep(
79
- task: Task,
80
- step: TaskStep
81
- ): Promise<{ success: boolean; trace_id?: string; error?: string }> {
82
- logDebug('Executing step', {
104
+ logInfo('Task execution completed', {
83
105
  taskId: task.id,
84
- stepOrder: step.order,
85
- mode: step.mode,
86
- file: step.target_file,
106
+ stepsCompleted: task.steps_completed,
107
+ filesCreated: task.files_created.length,
108
+ filesModified: task.files_modified.length,
87
109
  });
88
110
 
89
- try {
90
- if (step.mode === 'create') {
91
- return await executeCreateStep(task, step);
92
- } else {
93
- return await executeModifyStep(task, step);
94
- }
95
- } catch (error) {
96
- const errorMsg = error instanceof Error ? error.message : 'Unknown error';
97
- const errorObj = error instanceof Error ? error : new Error(String(error));
98
- logError('Step execution failed', errorObj, {
99
- taskId: task.id,
100
- stepOrder: step.order,
101
- });
102
- return { success: false, error: errorMsg };
103
- }
111
+ return task;
104
112
  }
105
113
 
114
+
106
115
  /**
107
116
  * Track progress of task execution
108
117
  */
@@ -124,10 +133,6 @@ export function trackProgress(task: Task): {
124
133
  };
125
134
  }
126
135
 
127
- /**
128
- * Execute a create mode step (new file)
129
- */
130
- async function executeCreateStep(
131
136
  task: Task,
132
137
  step: TaskStep
133
138
  ): Promise<{ success: boolean; trace_id?: string; error?: string }> {
@@ -53,11 +53,21 @@ describe('Trace Event System', () => {
53
53
  gradient_before: 0.8,
54
54
  gradient_after: 0.6,
55
55
  gradient_delta: 0.2,
56
- changes_made: ['fix null check', 'add error handling'],
57
- tokens_used: 1000,
58
- cost_usd: 0.05,
59
- duration_ms: 30000,
60
- success: true,
56
+ metabolic_cost: 0.05,
57
+ efficiency: 4.0,
58
+ ci_passed: true,
59
+ changes: {
60
+ additions: 5,
61
+ deletions: 2,
62
+ files_touched: ['src/test.ts'],
63
+ },
64
+ notes: ['fix null check', 'add error handling'],
65
+ cost: {
66
+ tokens_in: 500,
67
+ tokens_out: 500,
68
+ model: 'claude-sonnet-4',
69
+ estimated_usd: 0.05,
70
+ },
61
71
  };
62
72
 
63
73
  await recordTrace(trace);
@@ -82,11 +92,21 @@ describe('Trace Event System', () => {
82
92
  gradient_before: 0.9,
83
93
  gradient_after: 0.7,
84
94
  gradient_delta: undefined as any,
85
- changes_made: ['extract function'],
86
- tokens_used: 800,
87
- cost_usd: 0.03,
88
- duration_ms: 25000,
89
- success: true,
95
+ metabolic_cost: 0.03,
96
+ efficiency: 6.67,
97
+ ci_passed: true,
98
+ changes: {
99
+ additions: 3,
100
+ deletions: 1,
101
+ files_touched: ['src/auth.ts'],
102
+ },
103
+ notes: ['extract function'],
104
+ cost: {
105
+ tokens_in: 400,
106
+ tokens_out: 400,
107
+ model: 'claude-sonnet-4',
108
+ estimated_usd: 0.03,
109
+ },
90
110
  };
91
111
 
92
112
  await recordTrace(trace);
@@ -102,11 +122,22 @@ describe('Trace Event System', () => {
102
122
  mode: 'debt_payer',
103
123
  gradient_before: 0.5,
104
124
  gradient_after: 0.4,
105
- changes_made: ['fix lint'],
106
- tokens_used: 500,
107
- cost_usd: 0.02,
108
- duration_ms: 15000,
109
- success: true,
125
+ gradient_delta: 0.1,
126
+ metabolic_cost: 0.02,
127
+ efficiency: 5.0,
128
+ ci_passed: true,
129
+ changes: {
130
+ additions: 2,
131
+ deletions: 1,
132
+ files_touched: ['src/test1.ts'],
133
+ },
134
+ notes: ['fix lint'],
135
+ cost: {
136
+ tokens_in: 250,
137
+ tokens_out: 250,
138
+ model: 'claude-sonnet-4',
139
+ estimated_usd: 0.02,
140
+ },
110
141
  });
111
142
 
112
143
  const trace2: TraceEvent = createTraceEvent({
@@ -115,11 +146,22 @@ describe('Trace Event System', () => {
115
146
  mode: 'stabilizer',
116
147
  gradient_before: 0.6,
117
148
  gradient_after: 0.5,
118
- changes_made: ['add tests'],
119
- tokens_used: 600,
120
- cost_usd: 0.025,
121
- duration_ms: 20000,
122
- success: true,
149
+ gradient_delta: 0.1,
150
+ metabolic_cost: 0.025,
151
+ efficiency: 4.0,
152
+ ci_passed: true,
153
+ changes: {
154
+ additions: 5,
155
+ deletions: 0,
156
+ files_touched: ['src/test2.ts'],
157
+ },
158
+ notes: ['add tests'],
159
+ cost: {
160
+ tokens_in: 300,
161
+ tokens_out: 300,
162
+ model: 'claude-sonnet-4',
163
+ estimated_usd: 0.025,
164
+ },
123
165
  });
124
166
 
125
167
  await recordTrace(trace1);