@chuckssmith/agentloom 0.4.0 → 0.5.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.
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # agentloom
2
2
 
3
- A workflow layer for Claude Code. Better routing, reusable roles, and multi-agent crew coordination — built natively on what Claude Code already provides.
3
+ A workflow layer for Claude Code persistence loops, parallel crews, and typed agent roles, built natively on what Claude Code already provides.
4
4
 
5
5
  ```bash
6
- npm install -g agentloom
6
+ npm install -g @chuckssmith/agentloom
7
7
  loom setup
8
8
  ```
9
9
 
@@ -12,10 +12,11 @@ loom setup
12
12
  ## What this is
13
13
 
14
14
  Claude Code is the execution engine. agentloom adds:
15
+
15
16
  - **`$grind`** — persistence loop that keeps working until a task is verified complete
16
- - **`$crew`** — parallel workers that decompose and execute simultaneously
17
+ - **`$crew`** — parallel workers that decompose and execute simultaneously
17
18
  - **`$architect`** — deep analysis mode before major decisions
18
- - **`loom crew`** — CLI to spawn a crew of workers from your terminal
19
+ - **`loom crew`** — CLI to spawn and monitor a crew from your terminal
19
20
 
20
21
  It does not replace Claude Code. It wraps it.
21
22
 
@@ -24,14 +25,24 @@ It does not replace Claude Code. It wraps it.
24
25
  ## Quick start
25
26
 
26
27
  ```bash
27
- npm install -g agentloom
28
- loom setup # installs skills, validates deps
28
+ npm install -g @chuckssmith/agentloom
29
+ loom setup # installs $grind, $crew, $architect skills + validates deps
29
30
 
30
- # From your terminal:
31
+ # Spawn workers from your terminal:
31
32
  loom crew "audit every API endpoint for security issues"
32
33
  loom crew 2:explore+1:code-reviewer "review the payment flow"
34
+ loom crew --dry-run 3 "migrate the database schema" # preview before launching
35
+
36
+ # Monitor:
37
+ loom watch # live tail all worker logs
38
+ loom status # session overview + stale worker detection
39
+ loom logs w00 # full output for one worker
40
+
41
+ # After workers finish:
42
+ loom collect # synthesize results with Claude
43
+ loom reset --force # clear state for next run
33
44
 
34
- # Or from inside a Claude Code session:
45
+ # Or use inside any Claude Code session:
35
46
  # $grind "port the auth module to the new interface"
36
47
  # $crew "analyze all three data pipeline stages in parallel"
37
48
  ```
@@ -40,49 +51,81 @@ loom crew 2:explore+1:code-reviewer "review the payment flow"
40
51
 
41
52
  ## Skills
42
53
 
43
- Install with `loom setup`. Then use inside any Claude Code session:
54
+ Install with `loom setup`. Use inside any Claude Code session:
44
55
 
45
- | Skill | What it does |
46
- |---|---|
47
- | `$grind` | Persistence loop with mandatory verification gate |
48
- | `$crew` | Parallel workers decomposes task, runs simultaneously, verifies |
49
- | `$architect` | Deep analysis — maps system, finds real problems, recommends approach |
56
+ | Skill | Trigger | What it does |
57
+ |---|---|---|
58
+ | `$grind` | `$grind "<task>"` | Persistence loop plans, executes in parallel, verifies. Won't stop until a code-reviewer subagent returns PASS |
59
+ | `$crew` | `$crew "<task>"` | Decomposes task into independent streams, runs workers simultaneously, verifies result |
60
+ | `$architect` | `$architect "<task>"` | Deep analysis — maps the system, finds real problems, recommends approach before you write code |
50
61
 
51
62
  ---
52
63
 
53
- ## CLI
64
+ ## CLI reference
65
+
66
+ ### Spawning workers
67
+
68
+ ```
69
+ loom crew "<task>" 2 general-purpose workers (default)
70
+ loom crew 3 "<task>" 3 workers
71
+ loom crew 2:explore "<task>" 2 explore-type workers
72
+ loom crew 2:explore+1:code-reviewer "<task>" typed crew
73
+ loom crew --dry-run 3 "<task>" preview decomposed subtasks, no launch
74
+ ```
75
+
76
+ ### Monitoring
54
77
 
55
78
  ```
56
- loom crew [N] "<task>" Spawn N general-purpose workers
57
- loom crew 3 "<task>" Spawn 3 workers
58
- loom crew 2:explore "<task>" Spawn 2 Explore-type workers
59
- loom crew 2:explore+1:code-reviewer Spawn typed crew
60
- loom status Show active session
61
- loom setup Install skills + validate
79
+ loom watch Live tail all worker logs with color-coded output
80
+ loom status Session overview, task counts, stale worker detection
81
+ loom logs Summary of all workers (status + last line)
82
+ loom logs <workerId> Full log + result for one worker (e.g. loom logs w00)
62
83
  ```
63
84
 
64
- ### Worker types
85
+ ### After workers finish
65
86
 
66
- Matches Claude Code's built-in subagent types:
87
+ ```
88
+ loom collect Read worker results + synthesize summary with Claude
89
+ loom collect --no-ai Concatenate results without Claude synthesis
90
+ ```
67
91
 
68
- | Type | Best for |
69
- |---|---|
70
- | `explore` | Read-only research, codebase mapping |
71
- | `plan` | Architecture decisions, approach planning |
72
- | `code-reviewer` | Audits, security reviews, quality checks |
73
- | `frontend-developer` | UI and component work |
74
- | `general-purpose` | General implementation (default) |
92
+ ### Housekeeping
93
+
94
+ ```
95
+ loom setup Install skills to ~/.claude/skills/, validate deps
96
+ loom reset --force Wipe .claude-team/ state
97
+ ```
98
+
99
+ ---
100
+
101
+ ## Worker types
102
+
103
+ Each type gets a role-specific system prompt that shapes its behavior:
104
+
105
+ | Type | Role | Modifies files? |
106
+ |---|---|---|
107
+ | `explore` | Maps code, documents structure and connections | No |
108
+ | `plan` | Reasons about approach, produces ordered action plan | No |
109
+ | `code-reviewer` | Audits for correctness, security, quality; assigns severity | No |
110
+ | `frontend-developer` | UI, components, styling, client-side logic | Yes |
111
+ | `general-purpose` | Does whatever the subtask requires (default) | Yes |
75
112
 
76
113
  ---
77
114
 
78
115
  ## State directory
79
116
 
117
+ Session state lives in `.claude-team/` (gitignored):
118
+
80
119
  ```
81
- .agentloom/
82
- tasks/ Task queue — workers claim atomically
83
- workers/ Worker status and results
84
- context/ Shared context snapshots
85
- session.json Active session metadata
120
+ .claude-team/
121
+ session.json Active session metadata
122
+ context/ Shared context snapshots (workers read + append)
123
+ tasks/ Task queue — workers claim atomically via file rename
124
+ workers/
125
+ w00.log Live stdout from worker 00
126
+ w00-prompt.md Prompt sent to worker 00
127
+ w00-result.md Result summary written by worker 00 on completion
128
+ summary.md Final synthesis from loom collect
86
129
  ```
87
130
 
88
131
  ---
@@ -90,8 +133,8 @@ Matches Claude Code's built-in subagent types:
90
133
  ## Requirements
91
134
 
92
135
  - Node.js 20+
93
- - Claude Code CLI (`claude`)
94
- - tmux (optional — used for crew mode on Mac/Linux; falls back to background processes on WSL/Windows)
136
+ - Claude Code CLI (`claude`) — authenticated
137
+ - tmux (optional — used on Mac/Linux; falls back to background processes on Windows/WSL)
95
138
 
96
139
  ---
97
140
 
package/dist/cli.js CHANGED
@@ -5,6 +5,7 @@ import { status } from './commands/status.js';
5
5
  import { logs } from './commands/logs.js';
6
6
  import { collect } from './commands/collect.js';
7
7
  import { reset } from './commands/reset.js';
8
+ import { watch } from './commands/watch.js';
8
9
  const [, , command, ...args] = process.argv;
9
10
  const usage = `
10
11
  agentloom (loom) — workflow layer for Claude Code
@@ -14,13 +15,21 @@ Usage:
14
15
  loom crew [N] "<task>" Spawn N parallel workers on a task
15
16
  loom crew 2:explore "<task>" Spawn typed workers (explore/plan/code-reviewer)
16
17
  loom crew --dry-run [N] "<task>" Preview decomposed subtasks without launching
17
- loom status Show active crew session
18
+ loom watch Live tail all worker logs (Ctrl+C to stop)
19
+ loom status Show active crew session + stale worker detection
18
20
  loom logs Show worker output summary
19
21
  loom logs <workerId> Show full log for a specific worker
20
22
  loom collect Synthesize worker results into a summary
21
23
  loom collect --no-ai Collect results without Claude synthesis
22
24
  loom reset --force Clear all session state
23
25
 
26
+ Agent types (use with crew):
27
+ explore Read-only research and mapping
28
+ plan Architecture and approach planning
29
+ code-reviewer Audit for correctness, security, quality
30
+ frontend-developer UI and component work
31
+ general-purpose Default — does whatever the subtask requires
32
+
24
33
  Modes (use $grind or $crew inside a Claude Code session):
25
34
  $grind Persistence loop — keeps working until verified complete
26
35
  $crew Parallel workers — decompose and execute simultaneously
@@ -31,8 +40,8 @@ Examples:
31
40
  loom crew 3 "audit every API endpoint for security issues"
32
41
  loom crew 2:explore+1:code-reviewer "review the payment flow"
33
42
  loom crew --dry-run 3 "migrate the database schema"
34
- loom logs
35
- loom logs w00
43
+ loom watch
44
+ loom collect
36
45
  `;
37
46
  switch (command) {
38
47
  case 'setup':
@@ -41,6 +50,9 @@ switch (command) {
41
50
  case 'crew':
42
51
  await crew(args);
43
52
  break;
53
+ case 'watch':
54
+ await watch(args);
55
+ break;
44
56
  case 'status':
45
57
  await status();
46
58
  break;
@@ -13,6 +13,53 @@ const hasTmux = () => {
13
13
  }
14
14
  };
15
15
  const isWSL = () => process.platform === 'linux' && !!process.env.WSL_DISTRO_NAME;
16
+ // Role-specific instructions injected into each worker prompt
17
+ const AGENT_ROLE = {
18
+ 'explore': `Your role is EXPLORER. You are read-only. Do not modify any files.
19
+ - Map out the relevant code, files, and structure
20
+ - Document what exists, how it connects, and what's notable
21
+ - Your output feeds the other workers — be thorough and specific`,
22
+ 'plan': `Your role is PLANNER. You are read-only. Do not modify any files.
23
+ - Reason about the best approach to the subtask
24
+ - Identify risks, dependencies, and open questions
25
+ - Produce a concrete, ordered action plan other workers can execute`,
26
+ 'code-reviewer': `Your role is CODE REVIEWER. You are read-only. Do not modify any files.
27
+ - Audit the relevant code for correctness, security, and quality
28
+ - Flag specific lines, patterns, or logic that are problematic
29
+ - Assign severity (critical / high / medium / low) to each finding`,
30
+ 'frontend-developer': `Your role is FRONTEND DEVELOPER.
31
+ - Focus on UI, components, styling, and client-side logic
32
+ - Follow existing conventions in the codebase
33
+ - Write clean, accessible code`,
34
+ 'general-purpose': `Your role is GENERAL PURPOSE WORKER.
35
+ - Do whatever the subtask requires — research, implementation, or both
36
+ - Use all tools available to you`,
37
+ };
38
+ function buildWorkerPrompt(subtask, contextPath, sessionId, workerId, agentType) {
39
+ const resultFile = join(STATE_DIR, 'workers', `${workerId}-result.md`);
40
+ const roleInstructions = AGENT_ROLE[agentType] ?? AGENT_ROLE['general-purpose'];
41
+ return `You are worker ${workerId} in an agentloom crew session (${sessionId}).
42
+
43
+ ${roleInstructions}
44
+
45
+ ## Your assigned subtask
46
+
47
+ "${subtask}"
48
+
49
+ ## Protocol
50
+
51
+ 1. Read the shared context: ${contextPath}
52
+ 2. Do the work thoroughly using all tools available to you
53
+ 3. Append key findings to the context file so other workers can see them
54
+ 4. When done, write a result summary to: ${resultFile}
55
+ Format: brief markdown — what you did, what you found, any blockers
56
+
57
+ ## Rules
58
+ - Stay focused on your assigned subtask and role
59
+ - Do not stop until your subtask is complete or you have hit a genuine blocker
60
+
61
+ Begin now.`;
62
+ }
16
63
  export async function crew(args) {
17
64
  if (args.length === 0) {
18
65
  console.error('Usage: loom crew [--dry-run] [N] "<task>"');
@@ -49,47 +96,28 @@ export async function crew(args) {
49
96
  console.log(`Tasks: ${tasks.length} created`);
50
97
  console.log(`Context: ${contextPath}\n`);
51
98
  if (hasTmux() && !isWSL()) {
52
- await launchTmux(session.id, totalWorkers, specs, tasks.map(t => t.description), contextPath);
99
+ await launchTmux(session.id, specs, tasks, contextPath);
53
100
  }
54
101
  else {
55
- await launchBackground(session.id, specs, tasks.map(t => t.description), contextPath);
102
+ await launchBackground(session.id, specs, tasks, contextPath);
56
103
  }
57
104
  console.log(`\nWorkers launched. Monitor with:`);
58
105
  console.log(` loom status`);
59
106
  console.log(` loom logs`);
107
+ console.log(` loom crew --watch (live tail)`);
60
108
  console.log(`State dir: ${STATE_DIR}/`);
61
109
  }
62
- function buildWorkerPrompt(subtask, contextPath, sessionId, workerId) {
63
- const resultFile = join(STATE_DIR, 'workers', `${workerId}-result.md`);
64
- return `You are worker ${workerId} in an agentloom crew session (${sessionId}).
65
-
66
- Your assigned subtask: "${subtask}"
67
-
68
- ## Protocol
69
-
70
- 1. Read the shared context: ${contextPath}
71
- 2. Do the work thoroughly using all tools available to you
72
- 3. When done, write a result summary to: ${resultFile}
73
- Format: brief markdown — what you did, what you found, any blockers
74
-
75
- ## Rules
76
- - Focus only on your assigned subtask
77
- - Write findings to the context file (${contextPath}) so other workers can see them
78
- - Do not stop until your subtask is complete or you have hit a genuine blocker
79
-
80
- Begin now.`;
81
- }
82
- async function launchBackground(sessionId, specs, subtasks, contextPath) {
110
+ async function launchBackground(sessionId, specs, tasks, contextPath) {
83
111
  await mkdir(join(STATE_DIR, 'workers'), { recursive: true });
84
112
  let workerIdx = 0;
85
113
  for (const spec of specs) {
86
114
  for (let i = 0; i < spec.count; i++) {
87
115
  const workerId = `w${String(workerIdx).padStart(2, '0')}`;
88
- const subtask = subtasks[workerIdx] ?? subtasks[0] ?? '';
116
+ const subtask = tasks[workerIdx]?.description ?? tasks[0]?.description ?? '';
117
+ const agentType = tasks[workerIdx]?.agentType ?? spec.agentType;
89
118
  workerIdx++;
90
- const prompt = buildWorkerPrompt(subtask, contextPath, sessionId, workerId);
119
+ const prompt = buildWorkerPrompt(subtask, contextPath, sessionId, workerId, agentType);
91
120
  const logFile = join(STATE_DIR, 'workers', `${workerId}.log`);
92
- // Write prompt to disk for inspection
93
121
  await writeFile(join(STATE_DIR, 'workers', `${workerId}-prompt.md`), prompt);
94
122
  const log = await open(logFile, 'w');
95
123
  const child = spawn('claude', ['--print', '--dangerously-skip-permissions', '-p', prompt], {
@@ -99,27 +127,28 @@ async function launchBackground(sessionId, specs, subtasks, contextPath) {
99
127
  });
100
128
  child.on('close', () => log.close());
101
129
  child.unref();
102
- console.log(` ✓ Worker ${workerId} (${spec.agentType}) launched [pid ${child.pid}] → ${logFile}`);
130
+ console.log(` ✓ Worker ${workerId} (${agentType}) launched [pid ${child.pid}] → ${logFile}`);
103
131
  }
104
132
  }
105
133
  }
106
- async function launchTmux(sessionId, count, specs, subtasks, contextPath) {
134
+ async function launchTmux(sessionId, specs, tasks, contextPath) {
107
135
  const tmuxSession = `loom-${sessionId}`;
108
136
  execSync(`tmux new-session -d -s ${tmuxSession} -x 220 -y 50`);
109
137
  let workerIdx = 0;
110
138
  for (const spec of specs) {
111
139
  for (let i = 0; i < spec.count; i++) {
112
140
  const workerId = `w${String(workerIdx).padStart(2, '0')}`;
113
- const subtask = subtasks[workerIdx] ?? subtasks[0] ?? '';
141
+ const subtask = tasks[workerIdx]?.description ?? tasks[0]?.description ?? '';
142
+ const agentType = tasks[workerIdx]?.agentType ?? spec.agentType;
114
143
  workerIdx++;
115
- const prompt = buildWorkerPrompt(subtask, contextPath, sessionId, workerId);
144
+ const prompt = buildWorkerPrompt(subtask, contextPath, sessionId, workerId, agentType);
116
145
  if (workerIdx > 1) {
117
146
  execSync(`tmux split-window -h -t ${tmuxSession}`);
118
147
  execSync(`tmux select-layout -t ${tmuxSession} tiled`);
119
148
  }
120
149
  const cmd = `AGENTLOOM_WORKER_ID=${workerId} AGENTLOOM_SESSION=${sessionId} claude --print --dangerously-skip-permissions -p '${prompt.replace(/'/g, "'\"'\"'")}'; echo '[worker done]'; read`;
121
150
  execSync(`tmux send-keys -t ${tmuxSession} "${cmd}" Enter`);
122
- console.log(` ✓ Worker ${workerId} (${spec.agentType}) launched in tmux pane`);
151
+ console.log(` ✓ Worker ${workerId} (${agentType}) launched in tmux pane`);
123
152
  }
124
153
  }
125
154
  execSync(`tmux attach-session -t ${tmuxSession}`);
@@ -1,6 +1,8 @@
1
- import { readSession, readTasks, readWorkers } from '../state/session.js';
2
- import { existsSync } from 'fs';
3
- import { STATE_DIR } from '../state/session.js';
1
+ import { readSession, readTasks, STATE_DIR } from '../state/session.js';
2
+ import { existsSync, statSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { readdir } from 'fs/promises';
5
+ const STALE_THRESHOLD_MS = 10 * 60 * 1000; // 10 minutes with no log growth = stale
4
6
  export async function status() {
5
7
  if (!existsSync(STATE_DIR)) {
6
8
  console.log('No active session. Run: loom crew "<task>"');
@@ -12,7 +14,6 @@ export async function status() {
12
14
  return;
13
15
  }
14
16
  const tasks = await readTasks();
15
- const workers = await readWorkers();
16
17
  const pending = tasks.filter(t => t.status === 'pending').length;
17
18
  const claimed = tasks.filter(t => t.status === 'claimed').length;
18
19
  const done = tasks.filter(t => t.status === 'done').length;
@@ -22,13 +23,44 @@ export async function status() {
22
23
  console.log(`Task: ${session.description}`);
23
24
  console.log(`Started: ${session.createdAt}`);
24
25
  console.log(`\nTasks: ${pending} pending ${claimed} active ${done} done ${failed} failed`);
25
- console.log(`Workers: ${workers.length} (${session.workerCount} total)`);
26
- if (workers.length > 0) {
27
- console.log('\nWorker status:');
28
- for (const w of workers) {
29
- const task = w.currentTaskId ? tasks.find(t => t.id === w.currentTaskId) : null;
30
- const desc = task ? ` → ${task.description.slice(0, 60)}` : '';
31
- console.log(` [${w.id}] ${w.status}${desc}`);
26
+ // Worker status from log files
27
+ const workersDir = join(STATE_DIR, 'workers');
28
+ if (!existsSync(workersDir))
29
+ return;
30
+ const files = await readdir(workersDir);
31
+ const logFiles = files.filter(f => f.endsWith('.log')).sort();
32
+ if (logFiles.length === 0)
33
+ return;
34
+ console.log(`\nWorkers: ${logFiles.length}`);
35
+ const now = Date.now();
36
+ for (const logFile of logFiles) {
37
+ const workerId = logFile.replace('.log', '');
38
+ const logPath = join(workersDir, logFile);
39
+ const resultPath = join(workersDir, `${workerId}-result.md`);
40
+ const hasResult = existsSync(resultPath);
41
+ if (hasResult) {
42
+ console.log(` [${workerId}] done ✓`);
43
+ continue;
44
+ }
45
+ // Check if log is growing (worker is alive) or stale
46
+ const logStat = statSync(logPath);
47
+ const msSinceWrite = now - logStat.mtimeMs;
48
+ const isStale = msSinceWrite > STALE_THRESHOLD_MS;
49
+ const logSize = logStat.size;
50
+ if (logSize === 0) {
51
+ console.log(` [${workerId}] starting...`);
32
52
  }
53
+ else if (isStale) {
54
+ const mins = Math.round(msSinceWrite / 60000);
55
+ console.log(` [${workerId}] STALE — no activity for ${mins}m (log: ${logPath})`);
56
+ }
57
+ else {
58
+ const secs = Math.round(msSinceWrite / 1000);
59
+ console.log(` [${workerId}] running (last activity ${secs}s ago)`);
60
+ }
61
+ }
62
+ const allDone = logFiles.every(f => existsSync(join(workersDir, f.replace('.log', '-result.md'))));
63
+ if (allDone && logFiles.length > 0) {
64
+ console.log(`\nAll workers done. Run: loom collect`);
33
65
  }
34
66
  }
@@ -0,0 +1 @@
1
+ export declare function watch(_args: string[]): Promise<void>;
@@ -0,0 +1,69 @@
1
+ import { readdir, stat, readFile } from 'fs/promises';
2
+ import { join } from 'path';
3
+ import { existsSync } from 'fs';
4
+ import { STATE_DIR } from '../state/session.js';
5
+ const WORKERS_DIR = join(STATE_DIR, 'workers');
6
+ const POLL_MS = 800;
7
+ // A rotating set of ANSI colors for worker prefixes
8
+ const COLORS = ['\x1b[36m', '\x1b[33m', '\x1b[35m', '\x1b[32m', '\x1b[34m', '\x1b[31m'];
9
+ const RESET = '\x1b[0m';
10
+ const DIM = '\x1b[2m';
11
+ export async function watch(_args) {
12
+ if (!existsSync(WORKERS_DIR)) {
13
+ console.log('No active session. Run: loom crew "<task>"');
14
+ process.exit(1);
15
+ }
16
+ console.log(`${DIM}Watching worker logs. Ctrl+C to stop.${RESET}\n`);
17
+ // Track how many bytes we've read from each log file
18
+ const offsets = {};
19
+ const seen = new Set();
20
+ // eslint-disable-next-line no-constant-condition
21
+ while (true) {
22
+ if (!existsSync(WORKERS_DIR))
23
+ break;
24
+ const files = await readdir(WORKERS_DIR);
25
+ const logFiles = files.filter(f => f.endsWith('.log')).sort();
26
+ for (const logFile of logFiles) {
27
+ const workerId = logFile.replace('.log', '');
28
+ const color = COLORS[parseInt(workerId.replace('w', ''), 10) % COLORS.length] ?? COLORS[0];
29
+ const filePath = join(WORKERS_DIR, logFile);
30
+ if (!seen.has(workerId)) {
31
+ seen.add(workerId);
32
+ const resultExists = existsSync(join(WORKERS_DIR, `${workerId}-result.md`));
33
+ console.log(`${color}[${workerId}]${RESET} ${DIM}started${resultExists ? ' (already done)' : ''}${RESET}`);
34
+ }
35
+ const currentSize = (await stat(filePath)).size;
36
+ const offset = offsets[workerId] ?? 0;
37
+ if (currentSize > offset) {
38
+ const buf = await readFile(filePath);
39
+ const newContent = buf.slice(offset).toString('utf8');
40
+ offsets[workerId] = currentSize;
41
+ const lines = newContent.split('\n');
42
+ for (const line of lines) {
43
+ if (line.trim()) {
44
+ process.stdout.write(`${color}[${workerId}]${RESET} ${line}\n`);
45
+ }
46
+ }
47
+ }
48
+ // Check if worker just finished (result file appeared)
49
+ const resultPath = join(WORKERS_DIR, `${workerId}-result.md`);
50
+ const doneKey = `${workerId}-done`;
51
+ if (existsSync(resultPath) && !seen.has(doneKey)) {
52
+ seen.add(doneKey);
53
+ console.log(`${color}[${workerId}]${RESET} ${DIM}✓ result written${RESET}`);
54
+ }
55
+ }
56
+ // Exit when all known workers have results
57
+ if (logFiles.length > 0) {
58
+ const allDone = logFiles.every(f => {
59
+ const id = f.replace('.log', '');
60
+ return existsSync(join(WORKERS_DIR, `${id}-result.md`));
61
+ });
62
+ if (allDone) {
63
+ console.log(`\n${DIM}All workers done. Run: loom collect${RESET}`);
64
+ break;
65
+ }
66
+ }
67
+ await new Promise(resolve => setTimeout(resolve, POLL_MS));
68
+ }
69
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chuckssmith/agentloom",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "A workflow layer for Claude Code — reusable roles, persistence loops, and multi-agent crew coordination",
5
5
  "keywords": [
6
6
  "ai",