@llblab/pi-actors 0.37.1 → 0.38.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 (74) hide show
  1. package/BACKLOG.md +1 -1
  2. package/CHANGELOG.md +14 -0
  3. package/README.md +6 -5
  4. package/dist/index.js +23 -3
  5. package/dist/lib/async-runs.d.ts +5 -0
  6. package/dist/lib/async-runs.js +87 -8
  7. package/dist/lib/command-templates.d.ts +2 -0
  8. package/dist/lib/execution.d.ts +4 -0
  9. package/dist/lib/execution.js +154 -13
  10. package/dist/lib/model-context.d.ts +56 -0
  11. package/dist/lib/model-context.js +220 -0
  12. package/dist/lib/observability.d.ts +2 -0
  13. package/dist/lib/observability.js +32 -6
  14. package/dist/lib/preflight-diagnostics.d.ts +27 -0
  15. package/dist/lib/preflight-diagnostics.js +86 -0
  16. package/dist/lib/prompts.d.ts +1 -1
  17. package/dist/lib/prompts.js +2 -2
  18. package/dist/lib/recipes-context.d.ts +7 -0
  19. package/dist/lib/recipes-context.js +20 -2
  20. package/dist/lib/recipes-discovery.js +15 -0
  21. package/dist/lib/recipes-references.d.ts +2 -0
  22. package/dist/lib/recipes-references.js +10 -0
  23. package/dist/lib/tools-local.d.ts +2 -1
  24. package/dist/lib/tools-local.js +6 -4
  25. package/dist/lib/tools-response.js +22 -1
  26. package/dist/lib/tools-spawn.d.ts +2 -1
  27. package/dist/lib/tools-spawn.js +2 -1
  28. package/dist/recipes/lens-swarm.json +15 -2
  29. package/dist/recipes/pipeline-release-readiness.json +22 -3
  30. package/dist/recipes/pipeline-review-readiness.json +22 -3
  31. package/dist/recipes/subagent-judge.json +2 -1
  32. package/dist/recipes/subagent-merge.json +5 -2
  33. package/dist/recipes/subagent-normalize.json +2 -1
  34. package/dist/recipes/subagent-preflight.json +29 -0
  35. package/dist/recipes/subagent-review-coordinator.json +79 -7
  36. package/dist/recipes/subagent-review.json +2 -1
  37. package/dist/recipes/subagent-verify.json +2 -1
  38. package/dist/scripts/async-runner.mjs +84 -10
  39. package/dist/scripts/conformance.mjs +1 -0
  40. package/dist/skills/actors/SKILL.md +4 -4
  41. package/dist/skills/swarm/SKILL.md +2 -2
  42. package/docs/async-runs.md +3 -1
  43. package/docs/command-templates.md +4 -1
  44. package/docs/recipe-library.md +3 -2
  45. package/docs/template-recipes.md +2 -2
  46. package/index.ts +24 -3
  47. package/lib/async-runs.ts +157 -8
  48. package/lib/command-templates.ts +2 -0
  49. package/lib/execution.ts +218 -24
  50. package/lib/model-context.ts +359 -0
  51. package/lib/observability.ts +35 -6
  52. package/lib/preflight-diagnostics.ts +132 -0
  53. package/lib/prompts.ts +2 -2
  54. package/lib/recipes-context.ts +29 -2
  55. package/lib/recipes-discovery.ts +15 -0
  56. package/lib/recipes-references.ts +12 -0
  57. package/lib/tools-local.ts +22 -8
  58. package/lib/tools-response.ts +26 -1
  59. package/lib/tools-spawn.ts +6 -2
  60. package/package.json +1 -1
  61. package/recipes/lens-swarm.json +15 -2
  62. package/recipes/pipeline-release-readiness.json +22 -3
  63. package/recipes/pipeline-review-readiness.json +22 -3
  64. package/recipes/subagent-judge.json +2 -1
  65. package/recipes/subagent-merge.json +5 -2
  66. package/recipes/subagent-normalize.json +2 -1
  67. package/recipes/subagent-preflight.json +29 -0
  68. package/recipes/subagent-review-coordinator.json +79 -7
  69. package/recipes/subagent-review.json +2 -1
  70. package/recipes/subagent-verify.json +2 -1
  71. package/scripts/async-runner.mjs +84 -10
  72. package/scripts/conformance.mjs +1 -0
  73. package/skills/actors/SKILL.md +4 -4
  74. package/skills/swarm/SKILL.md +2 -2
@@ -8,7 +8,7 @@
8
8
  * chasing a one-off lib entrypoint domain.
9
9
  */
10
10
 
11
- import { appendFileSync, existsSync, readFileSync } from "node:fs";
11
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
12
12
  import { dirname, join } from "node:path";
13
13
  import { fileURLToPath, pathToFileURL } from "node:url";
14
14
 
@@ -25,8 +25,10 @@ async function importRuntimeModule(name) {
25
25
  );
26
26
  }
27
27
 
28
- const { appendRecipeContextToPiArgs } =
28
+ const { appendRecipeContextToPiArgs, materializePiPrintPromptArg } =
29
29
  await importRuntimeModule("recipes-context");
30
+ const { buildReviewPreflightDiagnostic, formatReviewPreflightDiagnostic } =
31
+ await importRuntimeModule("preflight-diagnostics");
30
32
  const { execCommandTemplate } = await importRuntimeModule("command-templates");
31
33
  const { executeRegisteredTool } = await importRuntimeModule("execution");
32
34
  const { writeJsonAtomic } = await importRuntimeModule("file-state");
@@ -75,6 +77,7 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
75
77
 
76
78
  function progress(phase, extra = {}) {
77
79
  writeJsonAtomic(progressPath, {
80
+ ...(meta.model_policy ? { model_policy: meta.model_policy } : {}),
78
81
  phase,
79
82
  updatedAt: new Date().toISOString(),
80
83
  ...extra,
@@ -83,6 +86,7 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
83
86
 
84
87
  let activeSubagents = 0;
85
88
  let completedSubagents = 0;
89
+ let promptCounter = 0;
86
90
  const subagentFailures = [];
87
91
 
88
92
  function getCommandDoneDelivery(result) {
@@ -97,18 +101,65 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
97
101
  });
98
102
  }
99
103
 
104
+ function promptFilePath() {
105
+ promptCounter += 1;
106
+ const dir = join(stateDir, "prompts");
107
+ mkdirSync(dir, { recursive: true });
108
+ return join(dir, `command-${String(promptCounter).padStart(3, "0")}.md`);
109
+ }
110
+
111
+ function readPromptText(promptFile) {
112
+ if (!promptFile) return undefined;
113
+ try {
114
+ return readFileSync(promptFile, "utf8");
115
+ } catch {
116
+ return undefined;
117
+ }
118
+ }
119
+
100
120
  async function observedExec(command, args, options) {
101
- const commandDetail = formatCommandDetail(command, args);
102
- const execArgs = appendRecipeContextToPiArgs(
121
+ const contextArgs = appendRecipeContextToPiArgs(
103
122
  command,
104
123
  args,
105
124
  meta.recipe_context_records,
106
125
  options?.actorRecipeContext,
107
126
  );
127
+ const materialized = materializePiPrintPromptArg(
128
+ command,
129
+ contextArgs,
130
+ promptFilePath,
131
+ );
132
+ const execArgs = materialized.args;
133
+ const commandDetail = formatCommandDetail(command, execArgs);
108
134
  activeSubagents += 1;
109
- event("command.start", { activeSubagents, command: commandDetail });
135
+ event("command.start", {
136
+ activeSubagents,
137
+ command: commandDetail,
138
+ ...(materialized.promptFile ? { prompt_file: materialized.promptFile } : {}),
139
+ ...(materialized.promptBytes ? { prompt_bytes: materialized.promptBytes } : {}),
140
+ });
110
141
  progressRunning();
111
- const result = await execCommandTemplate(command, execArgs, options);
142
+ let result = await execCommandTemplate(command, execArgs, options);
143
+ const preflightDiagnostic = result.code !== 0
144
+ ? buildReviewPreflightDiagnostic({
145
+ args: execArgs,
146
+ code: result.code,
147
+ killed: result.killed,
148
+ ...(materialized.promptFile ? { promptFile: materialized.promptFile } : {}),
149
+ promptText: readPromptText(materialized.promptFile),
150
+ stderr: result.stderr,
151
+ stdout: result.stdout,
152
+ })
153
+ : undefined;
154
+ if (preflightDiagnostic) {
155
+ result = {
156
+ ...result,
157
+ stderr: [
158
+ result.stderr,
159
+ formatReviewPreflightDiagnostic(preflightDiagnostic),
160
+ ].filter(Boolean).join("\n"),
161
+ };
162
+ }
112
163
  activeSubagents = Math.max(0, activeSubagents - 1);
113
164
  completedSubagents += 1;
114
165
  if (result.code !== 0) {
@@ -116,6 +167,8 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
116
167
  code: result.code,
117
168
  command: commandDetail,
118
169
  killed: result.killed,
170
+ ...(materialized.promptFile ? { prompt_file: materialized.promptFile } : {}),
171
+ ...(preflightDiagnostic ? { preflight: preflightDiagnostic } : {}),
119
172
  });
120
173
  }
121
174
  event("command.done", {
@@ -123,6 +176,9 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
123
176
  code: result.code,
124
177
  command: commandDetail,
125
178
  killed: result.killed,
179
+ ...(materialized.promptFile ? { prompt_file: materialized.promptFile } : {}),
180
+ ...(materialized.promptBytes ? { prompt_bytes: materialized.promptBytes } : {}),
181
+ ...(preflightDiagnostic ? { preflight: preflightDiagnostic } : {}),
126
182
  });
127
183
  outbox(
128
184
  "command.done",
@@ -134,6 +190,9 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
134
190
  code: result.code,
135
191
  command: commandDetail,
136
192
  killed: result.killed,
193
+ ...(materialized.promptFile ? { prompt_file: materialized.promptFile } : {}),
194
+ ...(materialized.promptBytes ? { prompt_bytes: materialized.promptBytes } : {}),
195
+ ...(preflightDiagnostic ? { preflight: preflightDiagnostic } : {}),
137
196
  },
138
197
  getCommandDoneDelivery(result),
139
198
  result.code === 0 ? "info" : "error",
@@ -163,6 +222,7 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
163
222
  code: result.details.code,
164
223
  command: result.details.command,
165
224
  killed: result.details.killed,
225
+ ...(meta.model_policy ? { model_policy: meta.model_policy } : {}),
166
226
  truncated: result.details.truncated,
167
227
  completedAt: new Date().toISOString(),
168
228
  });
@@ -173,15 +233,29 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
173
233
  event("run.done", { code: result.details.code });
174
234
  } catch (error) {
175
235
  const message = error instanceof Error ? error.message : String(error);
236
+ const details = error && typeof error === "object" ? error.details : undefined;
176
237
  appendFileSync(stderrPath, `${message}\n`);
177
238
  writeJsonAtomic(resultPath, {
178
- code: 1,
239
+ code: typeof details?.code === "number" ? details.code : 1,
179
240
  error: message,
180
- killed: false,
241
+ killed: Boolean(details?.killed),
242
+ ...(Array.isArray(details?.branches) ? { branches: details.branches } : {}),
243
+ ...(details?.failureReason ? { failure_reason: details.failureReason } : {}),
244
+ ...(meta.model_policy ? { model_policy: meta.model_policy } : {}),
245
+ ...(details?.softQuorum ? { soft_quorum: details.softQuorum } : {}),
181
246
  completedAt: new Date().toISOString(),
182
247
  });
183
- progress("failed", { completed: 0, failures: [{ message }] });
184
- event("run.failed", { error: message });
248
+ progress("failed", {
249
+ completed: 0,
250
+ failures: Array.isArray(details?.branches) && details.branches.length > 0
251
+ ? details.branches
252
+ : [{ message }],
253
+ ...(details?.failureReason ? { failureReason: details.failureReason } : {}),
254
+ });
255
+ event("run.failed", {
256
+ error: message,
257
+ ...(details?.failureReason ? { failure_reason: details.failureReason } : {}),
258
+ });
185
259
  throw error;
186
260
  }
187
261
  }
@@ -14,6 +14,7 @@ import { fileURLToPath } from "node:url";
14
14
  const conformanceSuites = [
15
15
  "tests/protocol-examples.test.ts",
16
16
  "tests/recipes-discovery.test.ts",
17
+ "tests/review-swarm-dogfood.test.ts",
17
18
  "tests/registry.test.ts",
18
19
  "tests/runtime.test.ts",
19
20
  "tests/async-runs.test.ts",
@@ -2,7 +2,7 @@
2
2
  name: actors
3
3
  description: Required practical guide for non-trivial pi-actors use. Read before using or changing spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
4
4
  metadata:
5
- version: 0.37.1
5
+ version: 0.38.0
6
6
  ---
7
7
 
8
8
  # Actors (pi-actors)
@@ -199,9 +199,9 @@ Rules:
199
199
  7. Declare `mailbox` for actors that accept or emit meaningful messages.
200
200
  8. Declare `artifacts` for durable outputs the coordinator should inspect.
201
201
  9. File-backed recipe identity comes from the filename basename; legacy top-level `name` fields are ignored by loaders.
202
- 10. File-backed async recipes pass child `pi -p` actors a bounded JSONL recipe context bundle by default: raw entry/import recipe records, derived `name`, import path/alias, and `"you_are_here": true` on the launching recipe node. Set `"actor_context": false` or `"off"` to suppress it for minimal prompts.
202
+ 10. File-backed async recipes pass child `pi -p` actors a bounded JSONL recipe context bundle by default: raw entry/import recipe records, derived `name`, import path/alias, and `"you_are_here": true` on the launching recipe node. The runner materializes child prompts under `prompts/command-NNN.md` and invokes Pi with `@file` args so large prompts and recipe context stay inspectable and argv-safe. Set `"actor_context": false` or `"off"` to suppress recipe context for minimal prompts.
203
203
  11. Keep packaged recipes generic: no machine-local paths, no private companion identities, no project-specific defaults unless the recipe is explicitly project-specific.
204
- 12. Do not ship concrete model-version defaults in packaged recipes; expose `model`, `models`, and stage-specific model args so the caller must choose current policy at launch.
204
+ 12. Do not ship concrete model-version defaults in packaged recipes. For review-oriented subagent/lens recipes, default model/thinking args through `{current_model}` and `{current_thinking}` so they inherit the selected Pi session policy; keep `model`, `models`, `thinking`, and stage-specific model args explicit so callers can override policy at launch.
205
205
 
206
206
  Priority for same-id recipes:
207
207
 
@@ -284,7 +284,7 @@ The user recipe root is the default tool set by location. It accepts canonical J
284
284
 
285
285
  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.
286
286
 
287
- Packaged review recipes are directly spawnable. Use `spawn file="pipeline-review-readiness" values={...}` for readiness review or `spawn file="subagent-review" values={...}` for one reviewer; pass model/thinking/tool policy through values, then inspect the run. Do not recreate their script commands, call packaged scripts directly, or create wrapper recipes just to launch the maintained recipe.
287
+ Packaged review recipes are directly spawnable. Use `spawn file="pipeline-review-readiness" values={...}` for readiness review or `spawn file="subagent-review" values={...}` for one reviewer; pass model/thinking/tool policy through values, then inspect the run. Review coordinators preflight stage models before fanout; `ACTOR_PREFLIGHT_FAILED` diagnostics identify the failed stage, selected policy, provider error class, prompt file, and override args. Quorum-aware review fanout exposes `subagent_ttl_ms`, `reviewer_concurrency`, `min_successful_reviewers`, and `merge_policy`; partial reviewer evidence is preserved and marked `complete`, `degraded`, or `insufficient_data`. Run status/progress exposes `model_policy` so inherited vs explicit model/thinking choices remain visible. Do not recreate their script commands, call packaged scripts directly, or create wrapper recipes just to launch the maintained recipe.
288
288
 
289
289
  - [`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.
290
290
  - [`pipeline-repo-health`](../../recipes/pipeline-repo-health.json): git/doc/validation evidence → normalized repository health report.
@@ -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.37.1
5
+ version: 0.38.0
6
6
  ---
7
7
 
8
8
  # Swarm
@@ -162,7 +162,7 @@ Use [`references/development-swarm.md`](./references/development-swarm.md) for c
162
162
 
163
163
  Purpose: turn one result into many risk lenses and a decision-grade verdict.
164
164
 
165
- Use lens swarm for broad coverage, quorum for confidence on one critical judgement, or both for high-stakes releases. The final report should separate consensus findings, minority findings, merger findings, risks, and recommended next actions.
165
+ Use lens swarm for broad coverage, quorum for confidence on one critical judgement, or both for high-stakes releases. In adapters that expose current session model/thinking policy, default ordinary same-policy review swarms to that current policy and require explicit args only when intentionally varying models or thinking levels. Run a cheap model/tool preflight before launching expensive reviewer fanout; if it fails, use the `ACTOR_PREFLIGHT_FAILED` stage/model/error-class/prompt-file diagnostic to choose explicit override args instead of rerunning blindly. For packaged review swarms, tune `min_successful_reviewers`, `reviewer_concurrency`, `subagent_ttl_ms`, and `merge_policy` instead of manual reruns; preserve partial reports and label the outcome `complete`, `degraded`, or `insufficient_data`. The final report should separate consensus findings, minority findings, merger findings, risks, and recommended next actions.
166
166
 
167
167
  A review swarm synthesis must not fabricate claims. Every final finding should trace to a reviewer note, checked artifact, command output, source, or explicit merger rationale. Devil's Advocate critical findings must be preserved or explicitly disproved with evidence.
168
168