@llblab/pi-actors 0.29.0 → 0.29.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.29.1: Subagent TTL Kill Hotfix
6
+
7
+ - `[Coordinator]` Added `subagent_ttl_ms` / `--subagent-ttl-ms` to the room-swarm adapter so timed-out subagent `pi -p` processes are terminated instead of only awaited.
8
+
5
9
  ## 0.29.0: Candidate Recipe Memory
6
10
 
7
11
  - `[Spawn]` Capture inline spawn templates as non-registered candidate recipes under `~/.pi/agent/recipes/candidates`, making successful ad hoc actor patterns easy to replay by explicit path and promote manually.
@@ -200,7 +200,25 @@ async function stopLocker(locker) {
200
200
  locker.child.kill("SIGTERM");
201
201
  }
202
202
  }
203
- function runPi(prompt, model, thinking) {
203
+ function terminateProcessGroup(child, signal) {
204
+ if (!child.pid)
205
+ return;
206
+ try {
207
+ if (process.platform !== "win32")
208
+ process.kill(-child.pid, signal);
209
+ else
210
+ child.kill(signal);
211
+ }
212
+ catch {
213
+ try {
214
+ child.kill(signal);
215
+ }
216
+ catch {
217
+ // Process already exited.
218
+ }
219
+ }
220
+ }
221
+ function runPi(prompt, model, thinking, ttlMs = 0) {
204
222
  return new Promise((resolve) => {
205
223
  const args = [
206
224
  "--tools",
@@ -216,17 +234,57 @@ function runPi(prompt, model, thinking) {
216
234
  args.push("--thinking", thinking);
217
235
  }
218
236
  args.push("-p", prompt);
219
- const child = spawn("pi", args, { stdio: ["ignore", "pipe", "pipe"] });
237
+ const child = spawn("pi", args, {
238
+ detached: process.platform !== "win32",
239
+ stdio: ["ignore", "pipe", "pipe"],
240
+ });
220
241
  let stdout = "";
221
242
  let stderr = "";
243
+ let settled = false;
244
+ let timedOut = false;
245
+ let ttlTimer;
246
+ let forceTimer;
247
+ const clearTimers = () => {
248
+ if (ttlTimer)
249
+ clearTimeout(ttlTimer);
250
+ if (forceTimer)
251
+ clearTimeout(forceTimer);
252
+ };
253
+ if (ttlMs > 0) {
254
+ ttlTimer = setTimeout(() => {
255
+ timedOut = true;
256
+ stderr += `\nSubagent TTL expired after ${ttlMs}ms; terminating process group.\n`;
257
+ terminateProcessGroup(child, "SIGTERM");
258
+ forceTimer = setTimeout(() => terminateProcessGroup(child, "SIGKILL"), 1000);
259
+ }, ttlMs);
260
+ }
222
261
  child.stdout.on("data", (chunk) => {
223
262
  stdout += chunk;
224
263
  });
225
264
  child.stderr.on("data", (chunk) => {
226
265
  stderr += chunk;
227
266
  });
228
- child.on("close", (code) => resolve({ code, stdout, stderr }));
229
- child.on("error", (error) => resolve({ code: 1, stdout, stderr: String(error) }));
267
+ child.on("close", (code, signal) => {
268
+ if (settled)
269
+ return;
270
+ settled = true;
271
+ clearTimers();
272
+ resolve({
273
+ code: timedOut ? 124 : (code ?? (signal ? 1 : 0)),
274
+ killed: timedOut,
275
+ signal,
276
+ stderr,
277
+ stdout,
278
+ timed_out: timedOut,
279
+ });
280
+ });
281
+ child.on("error", (error) => {
282
+ if (settled)
283
+ return;
284
+ settled = true;
285
+ clearTimers();
286
+ resolve({ code: 1, stdout, stderr: String(error) });
287
+ });
230
288
  });
231
289
  }
232
290
  async function readRoomTranscript(config) {
@@ -268,7 +326,7 @@ async function synthesize(config, locker) {
268
326
  }
269
327
  const transcript = await readRoomTranscript(config);
270
328
  const prompt = `Synthesize this transcript into a concise Markdown artifact. Mission: ${config.mission}. Include: Title, Consensus, Roles, Protocol, Final Artifact Shape, Next Actions, Open Questions. Use only the transcript evidence below.\n\nTRANSCRIPT:\n${transcript.slice(-24000)}`;
271
- const result = await runPi(prompt, config.model, config.thinking);
329
+ const result = await runPi(prompt, config.model, config.thinking, config.subagentTtlMs);
272
330
  const output = result.stdout.trim();
273
331
  const diagnostics = config.stats
274
332
  ? `\n\n## Diagnostics\n\nparticipant_attempts=${config.stats.participantAttempts}\nparticipant_success=${config.stats.participantSuccess}\nparticipant_failures=${config.stats.participantFailures}\nsynthesis_code=${result.code}\ntranscript_messages=${transcript.trim() ? transcript.split("\n").length : 0}\n`
@@ -371,7 +429,7 @@ async function executeParticipantPrompt(role, basePrompt, config) {
371
429
  "\nPlease acknowledge and address these direct messages in your response.\n";
372
430
  finalPrompt += inboxSection;
373
431
  }
374
- const result = await runPi(finalPrompt, config.model, config.thinking);
432
+ const result = await runPi(finalPrompt, config.model, config.thinking, config.subagentTtlMs);
375
433
  if (claimedIds.length > 0) {
376
434
  const finalStatus = result.code === 0 ? "handled" : "failed";
377
435
  await updateInboxMessagesStatus(config.runId, branchName, claimedIds, finalStatus);
@@ -399,13 +457,13 @@ async function participantJoin(role, config) {
399
457
  const displayName = role.name;
400
458
  const address = `branch:${config.runId}/${role.name}`;
401
459
  const joinPrompt = `You are ${displayName}, ${role.persona}. Mission: ${config.mission}. Call tool message exactly once with to=${shellQuote(config.room)}, from=${shellQuote(address)}, type='actor.join', summary='${displayName} joined', body JSON {"role":${JSON.stringify(role.persona)},"display":${JSON.stringify(displayName)},"caps":["coordination","synthesis"],"claim":"coordinate on mission"}. Then print one short line.`;
402
- await runPi(joinPrompt, config.model, config.thinking);
460
+ await runPi(joinPrompt, config.model, config.thinking, config.subagentTtlMs);
403
461
  }
404
462
  async function participantLeave(role, config) {
405
463
  const displayName = role.name;
406
464
  const address = `branch:${config.runId}/${role.name}`;
407
465
  const leavePrompt = `Call tool message exactly once with to=${shellQuote(config.room)}, from=${shellQuote(address)}, type='actor.leave', summary='${displayName} left', body='finished coordinated work'. Then print goodbye.`;
408
- await runPi(leavePrompt, config.model, config.thinking);
466
+ await runPi(leavePrompt, config.model, config.thinking, config.subagentTtlMs);
409
467
  }
410
468
  // 1. consensus / swarm mode: iterative chat in a room
411
469
  async function runConsensus(config, locker) {
@@ -559,6 +617,7 @@ export async function runCoordinator(argv = process.argv.slice(2)) {
559
617
  artifactPath: arg("artifact-path", ""),
560
618
  locker: boolArg("locker", false),
561
619
  lockerLeaseMs: numberArg("locker-lease-ms", 600000),
620
+ subagentTtlMs: numberArg("subagent-ttl-ms", 0),
562
621
  stats: {
563
622
  participantAttempts: 0,
564
623
  participantSuccess: 0,
@@ -11,6 +11,7 @@
11
11
  "delay:int",
12
12
  "locker:bool",
13
13
  "locker_lease_ms:int",
14
+ "subagent_ttl_ms:int",
14
15
  "artifact_path:path",
15
16
  "repo:path"
16
17
  ],
@@ -23,6 +24,7 @@
23
24
  "delay": "10",
24
25
  "locker": "false",
25
26
  "locker_lease_ms": "600000",
27
+ "subagent_ttl_ms": "0",
26
28
  "artifact_path": "{state_dir}/room-swarm-artifact.md",
27
29
  "repo": "~/.pi/agent/extensions/pi-actors"
28
30
  },
@@ -44,5 +46,5 @@
44
46
  "run.failed"
45
47
  ]
46
48
  },
47
- "template": "{repo}/scripts/coordinator.mjs --run-id={run_id} --mode={mode} --mission={mission} --model={model} --thinking={thinking} --roles={roles} --roles-path={roles_path} --rounds={rounds} --delay={delay} --locker={locker} --locker-lease-ms={locker_lease_ms} --artifact-path={artifact_path}"
49
+ "template": "{repo}/scripts/coordinator.mjs --run-id={run_id} --mode={mode} --mission={mission} --model={model} --thinking={thinking} --roles={roles} --roles-path={roles_path} --rounds={rounds} --delay={delay} --locker={locker} --locker-lease-ms={locker_lease_ms} --subagent-ttl-ms={subagent_ttl_ms} --artifact-path={artifact_path}"
48
50
  }
@@ -2,7 +2,7 @@
2
2
  name: actors
3
3
  description: Highest-density practical guide for pi-actors. Read this skill whenever prompt and tools are not enough for spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
4
4
  metadata:
5
- version: 0.29.0
5
+ version: 0.29.1
6
6
  ---
7
7
 
8
8
  # Actors (pi-actors)
@@ -302,7 +302,7 @@ Use packaged recipes by name with `spawn file=<name>` for async actors, or regis
302
302
  - [`pipeline-docs-maintenance`](../../recipes/pipeline-docs-maintenance.json): docs index/review/planning → maintenance artifact.
303
303
  - Artifacts: [`pipeline-artifact-report`](../../recipes/pipeline-artifact-report.json), [`pipeline-artifact-write`](../../recipes/pipeline-artifact-write.json), [`pipeline-artifact-bundle`](../../recipes/pipeline-artifact-bundle.json).
304
304
  - Review gates: [`pipeline-quorum-review`](../../recipes/pipeline-quorum-review.json), [`pipeline-review-readiness`](../../recipes/pipeline-review-readiness.json).
305
- - Task-first workflows: [`pipeline-architect-coordinator`](../../recipes/pipeline-architect-coordinator.json), [`pipeline-research-synthesis`](../../recipes/pipeline-research-synthesis.json), [`pipeline-development-tasking`](../../recipes/pipeline-development-tasking.json), [`pipeline-checkpoint-continuation`](../../recipes/pipeline-checkpoint-continuation.json), [`pipeline-media-library`](../../recipes/pipeline-media-library.json), [`pipeline-room-swarm`](../../recipes/pipeline-room-swarm.json). For room swarms, choose `mode` from `consensus`, `pipeline`, `fanout`, or `pool`; prefer `roles_path` for custom role JSON and keep role `name` ASCII-safe for branch addresses. Use `locker=true` when the swarm needs a coordinator-locker-backed artifact lock and journal.
305
+ - Task-first workflows: [`pipeline-architect-coordinator`](../../recipes/pipeline-architect-coordinator.json), [`pipeline-research-synthesis`](../../recipes/pipeline-research-synthesis.json), [`pipeline-development-tasking`](../../recipes/pipeline-development-tasking.json), [`pipeline-checkpoint-continuation`](../../recipes/pipeline-checkpoint-continuation.json), [`pipeline-media-library`](../../recipes/pipeline-media-library.json), [`pipeline-room-swarm`](../../recipes/pipeline-room-swarm.json). For room swarms, choose `mode` from `consensus`, `pipeline`, `fanout`, or `pool`; prefer `roles_path` for custom role JSON and keep role `name` ASCII-safe for branch addresses. Set `subagent_ttl_ms` when participant processes need a hard kill budget. Use `locker=true` when the swarm needs a coordinator-locker-backed artifact lock and journal.
306
306
 
307
307
  ### Utilities
308
308
 
@@ -2,7 +2,7 @@
2
2
  name: swarm
3
3
  description: Subagent orchestration with scoped locks and quorum consensus. Use for multi-model review, parallel scoped work, delegated audit, and coordinated subagent execution.
4
4
  metadata:
5
- version: 0.29.0
5
+ version: 0.29.1
6
6
  ---
7
7
 
8
8
  # Swarm
@@ -84,7 +84,7 @@ Pipeline recipes demonstrate second-order composition:
84
84
  - `recipes/pipeline-development-tasking.json`: Plan → task card → critique → integrator handoff.
85
85
  - `recipes/pipeline-docs-maintenance.json`: Docs index → documentation review → maintenance plan → artifact report.
86
86
  - `recipes/pipeline-media-library.json`: Playlist build → media-library artifact report.
87
- - `recipes/pipeline-room-swarm.json`: Room participants join `room:<run>`, coordinate over repeated room-visible rounds, leave cleanly, and synthesize the room transcript into a caller-provided artifact path. Supported coordinator modes are `consensus`, `pipeline`, `fanout`, and `pool`; unknown modes fail closed instead of silently running consensus. Keep model/thinking/mission policy caller-owned. Custom roles can be supplied with `roles_path` as a JSON array of `{ "name", "persona" }` objects; `name` stays ASCII-safe for `branch:<run>/<name>` addresses and debugger output remains plain and name-driven. The packaged swarm uses contacts for peer awareness but does not rely on direct branch delivery unless a caller-specific worker protocol consumes branch envelopes. Set `locker=true` to compose a local `coordinator-locker` cell under `{state_dir}/locker` for artifact ownership, resource lease locks, and a decision journal without merging locker policy into the room-participant script.
87
+ - `recipes/pipeline-room-swarm.json`: Room participants join `room:<run>`, coordinate over repeated room-visible rounds, leave cleanly, and synthesize the room transcript into a caller-provided artifact path. Supported coordinator modes are `consensus`, `pipeline`, `fanout`, and `pool`; unknown modes fail closed instead of silently running consensus. Keep model/thinking/mission policy caller-owned. Custom roles can be supplied with `roles_path` as a JSON array of `{ "name", "persona" }` objects; `name` stays ASCII-safe for `branch:<run>/<name>` addresses and debugger output remains plain and name-driven. The packaged swarm uses contacts for peer awareness but does not rely on direct branch delivery unless a caller-specific worker protocol consumes branch envelopes. Set `subagent_ttl_ms` to a positive millisecond budget when participant `pi -p` processes must be killed instead of awaited indefinitely. Set `locker=true` to compose a local `coordinator-locker` cell under `{state_dir}/locker` for artifact ownership, resource lease locks, and a decision journal without merging locker policy into the room-participant script.
88
88
  - `recipes/pipeline-artifact-report.json`: Normalize → artifact-shaped output → actor-message-shaped record. This pipeline prepares a candidate artifact and emits `artifact.prepared`/`artifact.blocked`; the `artifact_path` is a target path, not a guarantee that the file was written.
89
89
  - `recipes/pipeline-artifact-write.json`: Normalize → artifact-shaped output → deterministic artifact write → actor-message-shaped record. Use only when the caller explicitly wants filesystem writes; `write_mode` is `create`, `overwrite`, or `append`.
90
90
  - `recipes/pipeline-artifact-bundle.json`: Optional validation → deterministic artifact write → machine-readable manifest generation → deterministic manifest write → actor-message-shaped record. Use when the caller explicitly wants a filesystem handoff bundle with both artifact and manifest paths.
@@ -220,7 +220,21 @@ async function stopLocker(locker) {
220
220
  }
221
221
  }
222
222
 
223
- function runPi(prompt, model, thinking) {
223
+ function terminateProcessGroup(child, signal) {
224
+ if (!child.pid) return;
225
+ try {
226
+ if (process.platform !== "win32") process.kill(-child.pid, signal);
227
+ else child.kill(signal);
228
+ } catch {
229
+ try {
230
+ child.kill(signal);
231
+ } catch {
232
+ // Process already exited.
233
+ }
234
+ }
235
+ }
236
+
237
+ function runPi(prompt, model, thinking, ttlMs = 0) {
224
238
  return new Promise((resolve) => {
225
239
  const args = [
226
240
  "--tools",
@@ -237,19 +251,53 @@ function runPi(prompt, model, thinking) {
237
251
  }
238
252
  args.push("-p", prompt);
239
253
 
240
- const child = spawn("pi", args, { stdio: ["ignore", "pipe", "pipe"] });
254
+ const child = spawn("pi", args, {
255
+ detached: process.platform !== "win32",
256
+ stdio: ["ignore", "pipe", "pipe"],
257
+ });
241
258
  let stdout = "";
242
259
  let stderr = "";
260
+ let settled = false;
261
+ let timedOut = false;
262
+ let ttlTimer;
263
+ let forceTimer;
264
+ const clearTimers = () => {
265
+ if (ttlTimer) clearTimeout(ttlTimer);
266
+ if (forceTimer) clearTimeout(forceTimer);
267
+ };
268
+ if (ttlMs > 0) {
269
+ ttlTimer = setTimeout(() => {
270
+ timedOut = true;
271
+ stderr += `\nSubagent TTL expired after ${ttlMs}ms; terminating process group.\n`;
272
+ terminateProcessGroup(child, "SIGTERM");
273
+ forceTimer = setTimeout(() => terminateProcessGroup(child, "SIGKILL"), 1000);
274
+ }, ttlMs);
275
+ }
243
276
  child.stdout.on("data", (chunk) => {
244
277
  stdout += chunk;
245
278
  });
246
279
  child.stderr.on("data", (chunk) => {
247
280
  stderr += chunk;
248
281
  });
249
- child.on("close", (code) => resolve({ code, stdout, stderr }));
250
- child.on("error", (error) =>
251
- resolve({ code: 1, stdout, stderr: String(error) }),
252
- );
282
+ child.on("close", (code, signal) => {
283
+ if (settled) return;
284
+ settled = true;
285
+ clearTimers();
286
+ resolve({
287
+ code: timedOut ? 124 : (code ?? (signal ? 1 : 0)),
288
+ killed: timedOut,
289
+ signal,
290
+ stderr,
291
+ stdout,
292
+ timed_out: timedOut,
293
+ });
294
+ });
295
+ child.on("error", (error) => {
296
+ if (settled) return;
297
+ settled = true;
298
+ clearTimers();
299
+ resolve({ code: 1, stdout, stderr: String(error) });
300
+ });
253
301
  });
254
302
  }
255
303
 
@@ -294,7 +342,7 @@ async function synthesize(config, locker) {
294
342
  }
295
343
  const transcript = await readRoomTranscript(config);
296
344
  const prompt = `Synthesize this transcript into a concise Markdown artifact. Mission: ${config.mission}. Include: Title, Consensus, Roles, Protocol, Final Artifact Shape, Next Actions, Open Questions. Use only the transcript evidence below.\n\nTRANSCRIPT:\n${transcript.slice(-24000)}`;
297
- const result = await runPi(prompt, config.model, config.thinking);
345
+ const result = await runPi(prompt, config.model, config.thinking, config.subagentTtlMs);
298
346
  const output = result.stdout.trim();
299
347
  const diagnostics = config.stats
300
348
  ? `\n\n## Diagnostics\n\nparticipant_attempts=${config.stats.participantAttempts}\nparticipant_success=${config.stats.participantSuccess}\nparticipant_failures=${config.stats.participantFailures}\nsynthesis_code=${result.code}\ntranscript_messages=${transcript.trim() ? transcript.split("\n").length : 0}\n`
@@ -417,7 +465,7 @@ async function executeParticipantPrompt(role, basePrompt, config) {
417
465
  finalPrompt += inboxSection;
418
466
  }
419
467
 
420
- const result = await runPi(finalPrompt, config.model, config.thinking);
468
+ const result = await runPi(finalPrompt, config.model, config.thinking, config.subagentTtlMs);
421
469
 
422
470
  if (claimedIds.length > 0) {
423
471
  const finalStatus = result.code === 0 ? "handled" : "failed";
@@ -454,14 +502,14 @@ async function participantJoin(role, config) {
454
502
  const displayName = role.name;
455
503
  const address = `branch:${config.runId}/${role.name}`;
456
504
  const joinPrompt = `You are ${displayName}, ${role.persona}. Mission: ${config.mission}. Call tool message exactly once with to=${shellQuote(config.room)}, from=${shellQuote(address)}, type='actor.join', summary='${displayName} joined', body JSON {"role":${JSON.stringify(role.persona)},"display":${JSON.stringify(displayName)},"caps":["coordination","synthesis"],"claim":"coordinate on mission"}. Then print one short line.`;
457
- await runPi(joinPrompt, config.model, config.thinking);
505
+ await runPi(joinPrompt, config.model, config.thinking, config.subagentTtlMs);
458
506
  }
459
507
 
460
508
  async function participantLeave(role, config) {
461
509
  const displayName = role.name;
462
510
  const address = `branch:${config.runId}/${role.name}`;
463
511
  const leavePrompt = `Call tool message exactly once with to=${shellQuote(config.room)}, from=${shellQuote(address)}, type='actor.leave', summary='${displayName} left', body='finished coordinated work'. Then print goodbye.`;
464
- await runPi(leavePrompt, config.model, config.thinking);
512
+ await runPi(leavePrompt, config.model, config.thinking, config.subagentTtlMs);
465
513
  }
466
514
 
467
515
  // 1. consensus / swarm mode: iterative chat in a room
@@ -646,6 +694,7 @@ const config = {
646
694
  artifactPath: arg("artifact-path", ""),
647
695
  locker: boolArg("locker", false),
648
696
  lockerLeaseMs: numberArg("locker-lease-ms", 600000),
697
+ subagentTtlMs: numberArg("subagent-ttl-ms", 0),
649
698
  stats: {
650
699
  participantAttempts: 0,
651
700
  participantSuccess: 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-actors",
3
- "version": "0.29.0",
3
+ "version": "0.29.1",
4
4
  "private": false,
5
5
  "description": "Local Actor Kernel for Pi",
6
6
  "keywords": [
@@ -11,6 +11,7 @@
11
11
  "delay:int",
12
12
  "locker:bool",
13
13
  "locker_lease_ms:int",
14
+ "subagent_ttl_ms:int",
14
15
  "artifact_path:path",
15
16
  "repo:path"
16
17
  ],
@@ -23,6 +24,7 @@
23
24
  "delay": "10",
24
25
  "locker": "false",
25
26
  "locker_lease_ms": "600000",
27
+ "subagent_ttl_ms": "0",
26
28
  "artifact_path": "{state_dir}/room-swarm-artifact.md",
27
29
  "repo": "~/.pi/agent/extensions/pi-actors"
28
30
  },
@@ -44,5 +46,5 @@
44
46
  "run.failed"
45
47
  ]
46
48
  },
47
- "template": "{repo}/scripts/coordinator.mjs --run-id={run_id} --mode={mode} --mission={mission} --model={model} --thinking={thinking} --roles={roles} --roles-path={roles_path} --rounds={rounds} --delay={delay} --locker={locker} --locker-lease-ms={locker_lease_ms} --artifact-path={artifact_path}"
49
+ "template": "{repo}/scripts/coordinator.mjs --run-id={run_id} --mode={mode} --mission={mission} --model={model} --thinking={thinking} --roles={roles} --roles-path={roles_path} --rounds={rounds} --delay={delay} --locker={locker} --locker-lease-ms={locker_lease_ms} --subagent-ttl-ms={subagent_ttl_ms} --artifact-path={artifact_path}"
48
50
  }
@@ -2,7 +2,7 @@
2
2
  name: actors
3
3
  description: Highest-density practical guide for pi-actors. Read this skill whenever prompt and tools are not enough for spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
4
4
  metadata:
5
- version: 0.29.0
5
+ version: 0.29.1
6
6
  ---
7
7
 
8
8
  # Actors (pi-actors)
@@ -302,7 +302,7 @@ Use packaged recipes by name with `spawn file=<name>` for async actors, or regis
302
302
  - [`pipeline-docs-maintenance`](../../recipes/pipeline-docs-maintenance.json): docs index/review/planning → maintenance artifact.
303
303
  - Artifacts: [`pipeline-artifact-report`](../../recipes/pipeline-artifact-report.json), [`pipeline-artifact-write`](../../recipes/pipeline-artifact-write.json), [`pipeline-artifact-bundle`](../../recipes/pipeline-artifact-bundle.json).
304
304
  - Review gates: [`pipeline-quorum-review`](../../recipes/pipeline-quorum-review.json), [`pipeline-review-readiness`](../../recipes/pipeline-review-readiness.json).
305
- - Task-first workflows: [`pipeline-architect-coordinator`](../../recipes/pipeline-architect-coordinator.json), [`pipeline-research-synthesis`](../../recipes/pipeline-research-synthesis.json), [`pipeline-development-tasking`](../../recipes/pipeline-development-tasking.json), [`pipeline-checkpoint-continuation`](../../recipes/pipeline-checkpoint-continuation.json), [`pipeline-media-library`](../../recipes/pipeline-media-library.json), [`pipeline-room-swarm`](../../recipes/pipeline-room-swarm.json). For room swarms, choose `mode` from `consensus`, `pipeline`, `fanout`, or `pool`; prefer `roles_path` for custom role JSON and keep role `name` ASCII-safe for branch addresses. Use `locker=true` when the swarm needs a coordinator-locker-backed artifact lock and journal.
305
+ - Task-first workflows: [`pipeline-architect-coordinator`](../../recipes/pipeline-architect-coordinator.json), [`pipeline-research-synthesis`](../../recipes/pipeline-research-synthesis.json), [`pipeline-development-tasking`](../../recipes/pipeline-development-tasking.json), [`pipeline-checkpoint-continuation`](../../recipes/pipeline-checkpoint-continuation.json), [`pipeline-media-library`](../../recipes/pipeline-media-library.json), [`pipeline-room-swarm`](../../recipes/pipeline-room-swarm.json). For room swarms, choose `mode` from `consensus`, `pipeline`, `fanout`, or `pool`; prefer `roles_path` for custom role JSON and keep role `name` ASCII-safe for branch addresses. Set `subagent_ttl_ms` when participant processes need a hard kill budget. Use `locker=true` when the swarm needs a coordinator-locker-backed artifact lock and journal.
306
306
 
307
307
  ### Utilities
308
308
 
@@ -2,7 +2,7 @@
2
2
  name: swarm
3
3
  description: Subagent orchestration with scoped locks and quorum consensus. Use for multi-model review, parallel scoped work, delegated audit, and coordinated subagent execution.
4
4
  metadata:
5
- version: 0.29.0
5
+ version: 0.29.1
6
6
  ---
7
7
 
8
8
  # Swarm