@llblab/pi-actors 0.29.2 → 0.30.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/lib/tools.ts CHANGED
@@ -27,6 +27,34 @@ export type RegisterToolInput = Registry.RegisterToolInput;
27
27
  export type RegisterToolRuntimeDeps<TContext> =
28
28
  Registry.RegisterToolRuntimeDeps<TContext>;
29
29
 
30
+ export interface CoreActorToolDefinitionDeps<TContext extends AsyncRunToolContext> {
31
+ configPath: string;
32
+ getActiveTools: () => string[];
33
+ getRuntimeTool: (name: string) => unknown;
34
+ registryRuntime: Pick<
35
+ RegisterToolRuntimeDeps<TContext>,
36
+ | "getExternalToolConflict"
37
+ | "getTools"
38
+ | "notify"
39
+ | "registerRuntimeTool"
40
+ >;
41
+ setActiveTools: (toolNames: string[]) => void;
42
+ }
43
+
44
+ export const RESERVED_TOOL_NAMES = new Set([
45
+ "read",
46
+ "write",
47
+ "edit",
48
+ "bash",
49
+ "find",
50
+ "grep",
51
+ "ls",
52
+ "register_tool",
53
+ "message",
54
+ "spawn",
55
+ "inspect",
56
+ ]);
57
+
30
58
  type JsonSchema = Record<string, unknown>;
31
59
 
32
60
  function stringSchema(description: string): JsonSchema {
@@ -183,7 +211,8 @@ function compactAsyncRunStatus(value: unknown): string {
183
211
  tokens.push(`failures=${failures}`);
184
212
  if (result.code !== undefined) tokens.push(`code=${String(result.code)}`);
185
213
  if (result.killed === true) tokens.push("killed=true");
186
- if (status.candidate_recipe) tokens.push(`candidate_recipe=${String(status.candidate_recipe)}`);
214
+ if (status.candidate_recipe)
215
+ tokens.push(`candidate_recipe=${String(status.candidate_recipe)}`);
187
216
  return `\n${tokens.join(" ")}`;
188
217
  }
189
218
 
@@ -448,10 +477,17 @@ function compactSessionRuns(
448
477
  summary: Record<string, unknown> = {},
449
478
  ): string {
450
479
  const suffix = [
451
- summary.other_sessions !== undefined ? `other_sessions=${String(summary.other_sessions)}` : "",
452
- summary.other_runs !== undefined ? `other_runs=${String(summary.other_runs)}` : "",
453
- ].filter(Boolean).join(" ");
454
- if (runs.length === 0) return `\nsession=${session} runs=0${suffix ? ` ${suffix}` : ""}`;
480
+ summary.other_sessions !== undefined
481
+ ? `other_sessions=${String(summary.other_sessions)}`
482
+ : "",
483
+ summary.other_runs !== undefined
484
+ ? `other_runs=${String(summary.other_runs)}`
485
+ : "",
486
+ ]
487
+ .filter(Boolean)
488
+ .join(" ");
489
+ if (runs.length === 0)
490
+ return `\nsession=${session} runs=0${suffix ? ` ${suffix}` : ""}`;
455
491
  return `\nsession=${session} runs=${runs.length}${suffix ? ` ${suffix}` : ""}\n${runs
456
492
  .map((run) => {
457
493
  const tokens = [
@@ -471,14 +507,21 @@ function getPiActorsRuntimeStatus(): Record<string, unknown> {
471
507
  const packageRoot = dirname(packagedRecipeRoot);
472
508
  const packageJsonPath = join(packageRoot, "package.json");
473
509
  const packageJson = existsSync(packageJsonPath)
474
- ? JSON.parse(readFileSync(packageJsonPath, "utf8")) as Record<string, unknown>
510
+ ? (JSON.parse(readFileSync(packageJsonPath, "utf8")) as Record<
511
+ string,
512
+ unknown
513
+ >)
475
514
  : {};
476
515
  let git_commit: string | undefined;
477
516
  try {
478
- git_commit = execFileSync("git", ["-C", packageRoot, "rev-parse", "--short", "HEAD"], {
479
- encoding: "utf8",
480
- stdio: ["ignore", "pipe", "ignore"],
481
- }).trim();
517
+ git_commit = execFileSync(
518
+ "git",
519
+ ["-C", packageRoot, "rev-parse", "--short", "HEAD"],
520
+ {
521
+ encoding: "utf8",
522
+ stdio: ["ignore", "pipe", "ignore"],
523
+ },
524
+ ).trim();
482
525
  } catch {
483
526
  git_commit = undefined;
484
527
  }
@@ -510,12 +553,13 @@ function compactToolActor(name: string, tool: Record<string, unknown>): string {
510
553
 
511
554
  function compactRecipeImports(summary: Record<string, unknown>): string {
512
555
  const active = Array.isArray(summary.active)
513
- ? summary.active as Array<Record<string, unknown>>
556
+ ? (summary.active as Array<Record<string, unknown>>)
514
557
  : [];
515
558
  const lines = active.flatMap((entry) => {
516
559
  const imports = asRecord(entry.imports);
517
560
  return Object.entries(imports).map(([alias, value]) => {
518
- const binding = typeof value === "string" ? { from: value } : asRecord(value);
561
+ const binding =
562
+ typeof value === "string" ? { from: value } : asRecord(value);
519
563
  return `recipe=${String(entry.id ?? "<unknown>")} alias=${alias} from=${String(binding.from ?? value)}`;
520
564
  });
521
565
  });
@@ -552,7 +596,10 @@ function compactRecipeDoctor(summary: Record<string, unknown>): string {
552
596
  );
553
597
  }
554
598
  for (const item of remediations.slice(0, 8)) {
555
- const action = compactPreview(item.action, Limits.DOCTOR_ACTION_PREVIEW_CHARS);
599
+ const action = compactPreview(
600
+ item.action,
601
+ Limits.DOCTOR_ACTION_PREVIEW_CHARS,
602
+ );
556
603
  const blocked = item.blocked_candidate
557
604
  ? ` blocked=${compactPreview(item.blocked_candidate, Limits.DOCTOR_ACTION_PREVIEW_CHARS)}`
558
605
  : "";
@@ -717,7 +764,11 @@ function formatToolActorFailure(
717
764
  function shadowedRecipeLaunchDiagnostic(
718
765
  recipe: unknown,
719
766
  ): Record<string, unknown> | undefined {
720
- if (typeof recipe !== "string" || recipe.includes("/") || recipe.includes("~"))
767
+ if (
768
+ typeof recipe !== "string" ||
769
+ recipe.includes("/") ||
770
+ recipe.includes("~")
771
+ )
721
772
  return undefined;
722
773
  const discovery = RecipeDiscovery.discoverRecipeSources([
723
774
  { root: Paths.getRecipeRoot(), defaultTool: true, mutableUsage: true },
@@ -730,7 +781,9 @@ function candidateRecipeName(run: string): string {
730
781
  return `${run.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "spawn"}.json`;
731
782
  }
732
783
 
733
- function candidateRecipeDefaults(values: Record<string, unknown>): Record<string, unknown> | undefined {
784
+ function candidateRecipeDefaults(
785
+ values: Record<string, unknown>,
786
+ ): Record<string, unknown> | undefined {
734
787
  const ignored = new Set([
735
788
  "actor_address",
736
789
  "communication_file",
@@ -753,7 +806,11 @@ function writeSpawnCandidateRecipe(
753
806
  process.env.PI_ACTORS_ENABLE_SPAWN_CANDIDATES_IN_TEST !== "1"
754
807
  )
755
808
  return undefined;
756
- if (input.template === undefined || input.file !== undefined || input.recipe !== undefined)
809
+ if (
810
+ input.template === undefined ||
811
+ input.file !== undefined ||
812
+ input.recipe !== undefined
813
+ )
757
814
  return undefined;
758
815
  const root = Paths.getRecipeCandidateRoot();
759
816
  mkdirSync(root, { recursive: true });
@@ -772,7 +829,8 @@ function writeSpawnCandidateRecipe(
772
829
 
773
830
  function enhanceSpawnRecipeError(error: unknown, recipe: unknown): Error {
774
831
  const diagnostic = shadowedRecipeLaunchDiagnostic(recipe);
775
- if (!diagnostic) return error instanceof Error ? error : new Error(String(error));
832
+ if (!diagnostic)
833
+ return error instanceof Error ? error : new Error(String(error));
776
834
  const original = error instanceof Error ? error.message : String(error);
777
835
  return Object.assign(
778
836
  new Error(
@@ -832,12 +890,17 @@ async function routeBranchEnvelope(
832
890
  try {
833
891
  return await AsyncRuns.sendRunMessage(runId, JSON.stringify(branchMessage));
834
892
  } catch (error) {
835
- const record = error && typeof error === "object" ? (error as Record<string, unknown>) : {};
893
+ const record =
894
+ error && typeof error === "object"
895
+ ? (error as Record<string, unknown>)
896
+ : {};
836
897
  if (record.queued === true) {
837
898
  return {
838
899
  control_path: record.control_path,
839
900
  control_type: record.control_type,
840
- delivery_error: record.delivery_error ?? (error instanceof Error ? error.message : String(error)),
901
+ delivery_error:
902
+ record.delivery_error ??
903
+ (error instanceof Error ? error.message : String(error)),
841
904
  inbox_id: record.inbox_id,
842
905
  queued: true,
843
906
  run: runId,
@@ -1070,7 +1133,12 @@ export function createInspectToolDefinition<TContext = unknown>(
1070
1133
  const target = String(input.target ?? "");
1071
1134
  const view = String(input.view ?? "");
1072
1135
  if (target === "recipes" || target === "recipe-registry") {
1073
- if (view !== "status" && view !== "summary" && view !== "doctor" && view !== "imports") {
1136
+ if (
1137
+ view !== "status" &&
1138
+ view !== "summary" &&
1139
+ view !== "doctor" &&
1140
+ view !== "imports"
1141
+ ) {
1074
1142
  throw new Error(
1075
1143
  "inspect recipes supports view=status, view=summary, view=doctor, or view=imports.",
1076
1144
  );
@@ -1086,7 +1154,9 @@ export function createInspectToolDefinition<TContext = unknown>(
1086
1154
  const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
1087
1155
  const summary = {
1088
1156
  ...RecipeDiscovery.summarizeDiscovery(discovered),
1089
- candidates: RecipeDiscovery.listCandidateRecipes(join(recipeRoot, "candidates")),
1157
+ candidates: RecipeDiscovery.listCandidateRecipes(
1158
+ join(recipeRoot, "candidates"),
1159
+ ),
1090
1160
  };
1091
1161
  return {
1092
1162
  content: [
@@ -1147,9 +1217,10 @@ export function createInspectToolDefinition<TContext = unknown>(
1147
1217
  const runs = allRuns.filter(
1148
1218
  (run) => address.value === "all" || run.ownerId === address.value,
1149
1219
  );
1150
- const sessionSummary = address.value === "all"
1151
- ? {}
1152
- : summarizeOtherSessions(address.value || "", allRuns);
1220
+ const sessionSummary =
1221
+ address.value === "all"
1222
+ ? {}
1223
+ : summarizeOtherSessions(address.value || "", allRuns);
1153
1224
  return {
1154
1225
  content: [
1155
1226
  {
@@ -1725,6 +1796,30 @@ export function createActorMessageToolDefinition<TContext = unknown>(
1725
1796
  };
1726
1797
  }
1727
1798
 
1799
+ export function createCoreActorToolDefinitions<TContext extends AsyncRunToolContext>(
1800
+ deps: CoreActorToolDefinitionDeps<TContext>,
1801
+ ): any[] {
1802
+ return [
1803
+ createRegisterToolDefinition<TContext>({
1804
+ configPath: deps.configPath,
1805
+ getActiveTools: deps.getActiveTools,
1806
+ getExternalToolConflict: deps.registryRuntime.getExternalToolConflict,
1807
+ getTools: deps.registryRuntime.getTools,
1808
+ notify: deps.registryRuntime.notify,
1809
+ registerRuntimeTool: deps.registryRuntime.registerRuntimeTool,
1810
+ reservedToolNames: RESERVED_TOOL_NAMES,
1811
+ setActiveTools: deps.setActiveTools,
1812
+ }),
1813
+ createSpawnToolDefinition<TContext>(),
1814
+ createActorMessageToolDefinition<TContext>({
1815
+ getTool: (name) => deps.getRuntimeTool(name),
1816
+ }),
1817
+ createInspectToolDefinition<TContext>({
1818
+ getTool: (name) => deps.getRuntimeTool(name),
1819
+ }),
1820
+ ];
1821
+ }
1822
+
1728
1823
  export function createRuntimeToolDefinition(
1729
1824
  cfg: RegisteredTool,
1730
1825
  exec: Execution.RegisteredToolExec,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-actors",
3
- "version": "0.29.2",
3
+ "version": "0.30.0",
4
4
  "private": false,
5
5
  "description": "Local Actor Kernel for Pi",
6
6
  "keywords": [
@@ -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.2
5
+ version: 0.30.0
6
6
  ---
7
7
 
8
8
  # Actors (pi-actors)
@@ -132,38 +132,15 @@ The table is compact and optimistic by default: bounded body previews, capped no
132
132
 
133
133
  Let terminal notifications arrive; avoid sleep-poll loops except during diagnosis.
134
134
 
135
- ## Stable Multi-Actor Review Rules
135
+ ## Runtime Communication Rules
136
136
 
137
- - Prefer independent read-only reviewers for review swarms. Use shared room messages for coordination signals and observability, not for letting reviewers converge early, unless the task explicitly asks for collaborative discussion.
138
- - Treat inspector-visible communication logs as recipe-quality evidence. Full room/direct timelines show whether recipes coordinate clearly, emit useful summaries, over-chat, miss handoffs, choose poor message types, or need better mailbox/artifact conventions. Use `inspect room:<run> view=messages|previews`, `inspect run:<id> view=communication`, and the actor inspector table/full-message views to improve recipes after real runs.
139
- - Smoke-test provider/model availability before launching expensive fanout, or choose a provider known to be configured in this environment. A failed provider fanout creates noisy run transitions without useful review signal.
140
137
  - Keep one public communication model: `spawn` creates actors, `message` sends typed envelopes, and `inspect` observes. Avoid adding public side channels or storage nouns when a normal actor address/view can express the operation.
141
138
  - Keep route and semantic type separate. Direct, room, coordinator, and session messages may share `type`; delivery behavior comes from `to`.
139
+ - Treat inspector-visible communication logs as recipe evidence. Use `inspect room:<run> view=messages|previews`, `inspect run:<id> view=communication`, and the actor inspector table/full-message views to improve mailbox/artifact conventions after real runs.
142
140
  - Any UI, summary, or aggregate view that scans run directories must apply coordinator/session ownership filters before exposing summaries or body previews.
143
141
  - Treat `communication.json` as visible actor context, not a global mutable truth table. Run-level snapshots should identify the run actor; branch-local snapshots should identify the branch actor.
144
142
  - Prefer same-run provenance checks on lateral actor routes. If `from` is accepted for room or branch routes, validate that it belongs to the addressed run.
145
143
 
146
- ## Persistent Backlog Implementers
147
-
148
- When using actors as backlog implementers, avoid one-shot subagents that exit after one task. Use long-lived branch actors and keep task selection with the coordinator:
149
-
150
- 1. Coordinator assigns a concrete backlog slice with `task.assign`.
151
- 2. Actor posts `task.claim` to `room:<run>` before editing.
152
- 3. Actor executes and validates the slice.
153
- 4. Actor posts `task.result` and `awaiting_assignment`.
154
- 5. Actor stays alive until the coordinator sends another `task.assign` or an explicit `control.kill`.
155
-
156
- Use `front`/`back` actors for opposite backlog ends when reducing overlap. Implementer workflows should be packaged as reusable recipe composition, not bespoke scripts: use `coordinator-locker` for queue/assignment/locking, subagent launcher recipes for execution cells, actor-message utility recipes for structured handoffs, and `lib/mailbox-loop.ts` helpers when writing mailbox-consuming workers. Mailbox loops should claim one run or branch inbox message at a time, mark success as `handled`, mark exceptions as `failed`, and treat only `control.kill` as the generic loop termination message; `control.stop` and `control.cancel` are actor-domain messages only when the recipe declares and handles them. Bounded drains may process available work until `control.kill` or a max-message guard. If the existing recipe library cannot express the scenario, add missing reusable component recipes first, then compose the higher-level workflow from them. Supervisors should route coordinator assignments by `body.actor`, preserve the assignment as an object rather than a JSON string, and keep stopped-worker summaries tied to the original actor list.
157
-
158
- Current packaged building blocks:
159
-
160
- - `coordinator-locker`: long-lived queue/lock coordinator for assignment and resource ownership.
161
- - `subagent-prompt`, `subagent-tools`, `subagents-prompts`: execution launchers for one or many agent prompts.
162
- - `utility-actor-message`: deterministic actor-message envelope construction for handoffs/results.
163
- - `utility-run-ops-snapshot` and `pipeline-async-run-ops`: inspect live runs/messages before deciding the next assignment.
164
-
165
- The missing higher-level persistent backlog-implementer workflow is intentionally future work until it can be expressed from reusable recipe cells.
166
-
167
144
  ## Command Template Standard
168
145
 
169
146
  Forms:
@@ -273,102 +250,19 @@ Tool templates may be:
273
250
 
274
251
  The user recipe root is the default tool set by location. It accepts canonical JSON recipes and literate Markdown recipes with frontmatter plus fenced `template`/`json recipe` blocks; same-id JSON shadows Markdown in the same priority layer. Packaged recipes are lower-priority standard-library components and are not tools unless copied or registered into the agent recipe root. Ideal runtime behavior is reactive: create/edit/delete recipe files, validate them, then connect valid tools or surface diagnostics without requiring agents to hand-maintain a separate registry.
275
252
 
276
- ## Recipe Navigator
253
+ ## Top Recipes
277
254
 
278
- Use packaged recipes by name with `spawn file=<name>` for async actors, or register/call them as tools when repeated use deserves a stable shortcut. The links below point to recipe files shipped with this extension; read the JSON for args, defaults, mailbox, artifacts, and imports.
255
+ Use packaged recipes by name with `spawn file=<name>` for async actors, or register/call them as tools when repeated use deserves a stable shortcut.
279
256
 
280
- ### Coordination and Services
281
-
282
- - [`coordinator-locker`](../../recipes/coordinator-locker.json): queue + acquire/renew/release lease locks + journaled coordinator messages + platform-adapted control metadata.
283
- - [`locker`](../../recipes/locker.json): modular queue + acquire/renew/release lease locks + journaled locker messages + platform-adapted control metadata.
284
- - [`utility-coordinator-lock-snapshot`](../../recipes/utility-coordinator-lock-snapshot.json): one-shot JSON snapshot of a coordinator-locker state directory.
285
- - [`music-player`](../../recipes/music-player.json): background local/URL/directory/playlist playback actor controlled by messages.
286
- - [`actor-worker`](../../recipes/actor-worker.json): canonical mailbox-backed branch worker reference that claims branch inbox work, emits room-visible task lifecycle messages, writes compact `worker-status.json`, optionally writes per-task result artifacts under `worker-artifacts/`, surfaces stale-claim counts when `stale_claim_ms` is set, and terminates on `control.kill`.
287
-
288
- ### Subagent Atoms
289
-
290
- - Launchers: [`subagent-prompt`](../../recipes/subagent-prompt.json), [`subagent-tools`](../../recipes/subagent-tools.json), [`subagents-prompts`](../../recipes/subagents-prompts.json).
291
- - Review chain: [`subagent-review`](../../recipes/subagent-review.json), [`subagent-verify`](../../recipes/subagent-verify.json), [`subagent-merge`](../../recipes/subagent-merge.json), [`subagent-judge`](../../recipes/subagent-judge.json), [`subagent-normalize`](../../recipes/subagent-normalize.json).
292
- - Planning/evidence: [`subagent-plan`](../../recipes/subagent-plan.json), [`subagent-task-card`](../../recipes/subagent-task-card.json), [`subagent-evidence-map`](../../recipes/subagent-evidence-map.json), [`subagent-contradiction-map`](../../recipes/subagent-contradiction-map.json), [`subagent-critic`](../../recipes/subagent-critic.json).
293
- - Handoffs: [`subagent-checkpoint`](../../recipes/subagent-checkpoint.json), [`subagent-followup`](../../recipes/subagent-followup.json), [`subagent-message`](../../recipes/subagent-message.json), [`subagent-artifact`](../../recipes/subagent-artifact.json), [`subagent-conflict-report`](../../recipes/subagent-conflict-report.json).
294
- - Composition: [`subagent-quorum`](../../recipes/subagent-quorum.json), [`subagent-review-coordinator`](../../recipes/subagent-review-coordinator.json), [`lens-swarm`](../../recipes/lens-swarm.json).
295
-
296
- ### Pipelines
297
-
298
- - [`pipeline-release-readiness`](../../recipes/pipeline-release-readiness.json): changelog/package/skill/validation evidence → release review → artifact report.
299
- - [`pipeline-release-summary`](../../recipes/pipeline-release-summary.json): evidence-only release summary, risk checklist, and PR body draft artifact without release side effects.
257
+ - [`pipeline-room-swarm`](../../recipes/pipeline-room-swarm.json): room-visible swarm coordination with roles, rounds, optional locker, artifact synthesis, and `subagent_ttl_ms` for hard participant budgets.
300
258
  - [`pipeline-repo-health`](../../recipes/pipeline-repo-health.json): git/doc/validation evidence → normalized repository health report.
301
- - [`pipeline-async-run-ops`](../../recipes/pipeline-async-run-ops.json): run summary + selected run messages → operations report.
302
- - [`pipeline-docs-maintenance`](../../recipes/pipeline-docs-maintenance.json): docs index/review/planning → maintenance artifact.
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
- - 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. 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
-
307
- ### Utilities
308
-
309
- - Repo/release evidence: [`utility-git-status`](../../recipes/utility-git-status.json), [`utility-git-log`](../../recipes/utility-git-log.json), [`utility-changelog-head`](../../recipes/utility-changelog-head.json), [`utility-changelog-section`](../../recipes/utility-changelog-section.json), [`utility-package-summary`](../../recipes/utility-package-summary.json), [`utility-skill-summary`](../../recipes/utility-skill-summary.json).
310
- - Validation/state: [`utility-validation-wrapper`](../../recipes/utility-validation-wrapper.json), [`utility-validate-recipe`](../../recipes/utility-validate-recipe.json), [`utility-run-summary`](../../recipes/utility-run-summary.json), [`utility-run-ops-snapshot`](../../recipes/utility-run-ops-snapshot.json), [`utility-run-state-files`](../../recipes/utility-run-state-files.json), [`utility-jsonl-tail`](../../recipes/utility-jsonl-tail.json).
311
- - Artifacts/media/messages: [`utility-artifact-manifest`](../../recipes/utility-artifact-manifest.json), [`utility-artifact-write`](../../recipes/utility-artifact-write.json), [`utility-actor-message`](../../recipes/utility-actor-message.json), [`utility-markdown-index`](../../recipes/utility-markdown-index.json), [`utility-playlist-scan`](../../recipes/utility-playlist-scan.json), [`utility-playlist-build`](../../recipes/utility-playlist-build.json).
312
-
313
- Deep inventory: [`docs/recipe-library.md`](../../docs/recipe-library.md).
314
-
315
- ## Operating Patterns
316
-
317
- - **Short deterministic command**: call foreground registered tool or command template.
318
- - **Long job/service/fanout**: `spawn` async recipe, then inspect/messages/artifacts.
319
- - **One-off experiment**: inline `template`; promote after repeat use.
320
- - **Reusable workflow**: packaged or user recipe with public knobs, mailbox, artifacts, docs.
321
- - **Subagent/swarm execution**: compose packaged recipes/pipelines from smaller recipe cells; add missing generic cells to the extension rather than creating one-off external orchestration scripts.
322
- - **Consensus-first build**: when many lenses should shape one artifact, have proposer subagents post room messages, then one named implementer writes, one QA reviewer checks, and one finalizer emits `run.done`; do not ask every lens to mutate the same artifact.
323
- - **Coordinated workers**: spawn `coordinator-locker` when several actors need a shared queue, acquire/renew/release resource leases, or a journaled coordination point.
324
- - **Release/review pipeline**: pi-actors can prepare evidence, summaries, and artifacts; external actions such as commit, PR, merge, tag, and publish require the appropriate gated release workflow.
325
-
326
- ## Complementary Methodology Engines
327
-
328
- pi-actors is the local execution engine for methodology skills. A methodology skill can define abstract patterns such as lens swarm, quorum, task cards, lock discipline, consensus-first build, or clean-context merge; pi-actors turns those patterns into concrete local actors, recipes, queues, leases, artifacts, and messages.
329
-
330
- Example mapping:
331
-
332
- ```text
333
- methodology says: protect shared files
334
- pi-actors does: spawn coordinator-locker, enqueue tasks, lease resources
335
-
336
- methodology says: run reviewers then merge
337
- pi-actors does: spawn review pipeline, inspect messages/artifacts
338
- ```
339
-
340
- Keep the split clean: methodology chooses coordination shape; pi-actors supplies addressable local machinery.
341
-
342
- ## Lifecycle Discipline
343
-
344
- 1. Choose existing recipe/tool when available.
345
- 2. Spawn with a stable actor id for observable work.
346
- 3. Inspect `status` after launch.
347
- 4. Use notifications and `inspect`; do not busy-poll.
348
- 5. Read `messages` and `artifacts`, not only stdout.
349
- 6. Use `message` for explicit control or domain commands; treat direct branch messages as intended initiating work. Direct branch envelopes are queued under the recipient branch inbox and can be inspected with `inspect branch:<run>/<branch> view=mailbox`; queued entries have stable `id` values and internal `claimed` / `handled` / `failed` states for worker protocols and retries. Room messages are shared transcript/context.
350
- 7. Promote repeated inline forms to recipes.
351
- 8. Keep recipes small and shallow: files over 1 MiB or import chains deeper than 32 are rejected.
352
- 9. Update docs/context when changing public behavior; if the change affects how agents operate this extension, update this skill and the bundled prompt guidance too.
353
-
354
- ## Common Pitfalls
355
-
356
- - Treating actor mechanics as multi-agent methodology.
357
- - Repeating inline templates instead of promoting recipes.
358
- - Creating task-specific external orchestration scripts when the scenario belongs in pi-actors as a reusable recipe/pipeline with prompts, roles, artifact paths, and model/tool policy passed as args.
359
- - Embedding complex shell loops or Bash `${...}` parameter expansion directly in command templates; braces are pi-actors placeholders too, so put only generic trusted helper cells in packaged scripts when command-template composition is not enough.
360
- - Omitting stable run ids for work that needs follow-up.
361
- - Sending domain messages without checking `mailbox`.
362
- - Expecting current room messages to wake prompt-only subagents; use direct branch messages or a runner protocol for initiating work.
363
- - Reading only stdout and missing actor messages/artifacts.
364
- - Assuming every packaged message-controlled script is native-Windows-ready; core run control is platform-adapted, but Unix-tool scripts must be migrated recipe by recipe.
365
- - Baking local absolute paths into published docs or reusable recipes.
366
- - Creating recipes that perform external side effects without explicit operator gates.
367
- - Letting project insights live only in chat instead of updating BACKLOG/CHANGELOG/docs and, when agent behavior changes, the packaged skill or prompt guidance.
368
- - Preserving old runtime/event/FIFO vocabulary instead of `spawn`/`message`/`inspect` and actor messages.
259
+ - [`pipeline-release-readiness`](../../recipes/pipeline-release-readiness.json): changelog/package/skill/validation evidence → release review → artifact report.
260
+ - [`actor-worker`](../../recipes/actor-worker.json): canonical mailbox-backed branch worker reference for claim/handle/status/artifact patterns.
261
+ - [`coordinator-locker`](../../recipes/coordinator-locker.json): queue + lease locks + journaled coordinator messages for multi-actor ownership.
369
262
 
370
263
  ## Deep References
371
264
 
265
+ - `docs/actors-deep-reference.md` — recipe navigator, operating patterns, lifecycle discipline, pitfalls.
372
266
  - `docs/command-templates.md` — execution graph semantics.
373
267
  - `docs/template-recipes.md` — recipe storage, imports, defaults, references.
374
268
  - `docs/async-runs.md` — detached lifecycle, state, cancellation, observability.
@@ -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.2
5
+ version: 0.30.0
6
6
  ---
7
7
 
8
8
  # Swarm
@@ -285,6 +285,25 @@ Async run management is an adapter concern, not a portable Swarm script requirem
285
285
 
286
286
  `Reference binding`: Use a local generic async-run runtime or tool registry adapter. If the local runtime exposes a single action tool, bind these verbs as actions rather than adding more Swarm scripts. Swarm scripts themselves should stay atomic and narrowly specialized.
287
287
 
288
+ ## Stable Multi-Agent Review Rules
289
+
290
+ - Prefer independent read-only reviewers for review swarms. Use shared room messages for coordination signals and observability, not for letting reviewers converge early, unless the task explicitly asks for collaborative discussion.
291
+ - Treat communication logs as recipe-quality evidence. Timelines show whether agents coordinate clearly, emit useful summaries, over-chat, miss handoffs, choose poor message types, or need better mailbox/artifact conventions.
292
+ - Smoke-test provider/model availability before launching expensive fanout, or choose a provider known to be configured in the environment. A failed provider fanout creates noisy run transitions without useful review signal.
293
+ - Keep methodology and runtime split: Swarm chooses decomposition, quorum, lenses, lock discipline, and merge shape; the local runtime supplies actors, messages, files, locks, artifacts, and cancellation.
294
+
295
+ ## Persistent Implementer Pattern
296
+
297
+ Use this pattern only when the work benefits from long-lived workers rather than one-shot subagents. Keep task selection with the coordinator and use reusable adapter cells for queues, locks, messages, and mailbox loops.
298
+
299
+ 1. Coordinator assigns a concrete task with `task.assign` or an adapter-equivalent envelope.
300
+ 2. Actor claims before editing or mutating shared state.
301
+ 3. Actor executes and validates the slice.
302
+ 4. Actor posts a result plus an explicit availability/blocked status.
303
+ 5. Actor stays alive until another assignment or an explicit runtime/domain stop.
304
+
305
+ Use opposite-end or lens-specific implementers only to reduce overlap, not as a default. If a host adapter cannot express this scenario from reusable cells, add missing generic cells before packaging a broad workflow.
306
+
288
307
  ## `swarm_quorum`
289
308
 
290
309
  Multi-model review by independent subagents.