@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
@@ -30,6 +30,23 @@ export function compactNextActions(actions) {
30
30
  function formatFailureCount(value) {
31
31
  return Array.isArray(value) ? value.length : undefined;
32
32
  }
33
+ function compactPolicyAxis(label, value) {
34
+ const axis = asRecord(value);
35
+ const source = typeof axis.source === "string" ? axis.source : undefined;
36
+ if (!source || source === "unused")
37
+ return undefined;
38
+ const renderedValue = typeof axis.value === "string" && axis.value.trim()
39
+ ? `:${axis.value.trim()}`
40
+ : "";
41
+ return `${label}=${source}${renderedValue}`;
42
+ }
43
+ function compactModelPolicy(value) {
44
+ const policy = asRecord(value);
45
+ return [
46
+ compactPolicyAxis("model", policy.model),
47
+ compactPolicyAxis("thinking", policy.thinking),
48
+ ].filter((token) => Boolean(token));
49
+ }
33
50
  export function actorRunNextActions(run) {
34
51
  const id = String(run ?? "").trim();
35
52
  if (!id)
@@ -46,6 +63,7 @@ export function compactAsyncRunStatus(value) {
46
63
  const result = asRecord(status.result);
47
64
  const run = String(status.run ?? "<unknown>");
48
65
  const tokens = [`run=${run}`, `status=${String(status.status ?? "unknown")}`];
66
+ tokens.push(...compactModelPolicy(status.model_policy ?? progress.model_policy));
49
67
  if (status.tool)
50
68
  tokens.push(`tool=${String(status.tool)}`);
51
69
  if (status.recipe)
@@ -222,9 +240,12 @@ export function compactRecipeRegistry(summary) {
222
240
  const recommendations = Array.isArray(summary.recommendations)
223
241
  ? summary.recommendations.length
224
242
  : 0;
243
+ const currentPolicy = Array.isArray(summary.active)
244
+ ? summary.active.filter((entry) => entry.current_policy).length
245
+ : 0;
225
246
  const nextActions = Array.isArray(summary.next_actions)
226
247
  ? summary.next_actions
227
248
  : [];
228
- return `\nrecipes active=${active} drafts=${drafts} shadowed=${shadowed} invalid=${invalid} disabled=${disabled} recommendations=${recommendations} diagnostics=${diagnostics}${compactNextActions(nextActions)}`;
249
+ return `\nrecipes active=${active} drafts=${drafts} shadowed=${shadowed} invalid=${invalid} disabled=${disabled} current_policy=${currentPolicy} recommendations=${recommendations} diagnostics=${diagnostics}${compactNextActions(nextActions)}`;
229
250
  }
230
251
  export const DEFAULT_INSPECT_LINES = Limits.DEFAULT_INSPECT_LINES;
@@ -3,7 +3,8 @@
3
3
  * Zones: actor launch, draft recipe capture, launch diagnostics
4
4
  * Owns the public spawn execution path for run-backed actors
5
5
  */
6
- export interface SpawnToolContext {
6
+ import * as ModelContext from "./model-context.ts";
7
+ export interface SpawnToolContext extends ModelContext.CurrentModelContext {
7
8
  cwd: string;
8
9
  sessionManager?: {
9
10
  getSessionId?: () => string;
@@ -7,6 +7,7 @@ import { mkdirSync, writeFileSync } from "node:fs";
7
7
  import { join } from "node:path";
8
8
  import * as AsyncRuns from "./async-runs.js";
9
9
  import * as Messages from "./messages.js";
10
+ import * as ModelContext from "./model-context.js";
10
11
  import * as Paths from "./paths.js";
11
12
  import * as RecipesDiscovery from "./recipes-discovery.js";
12
13
  import * as Rooms from "./rooms.js";
@@ -123,7 +124,7 @@ export function createSpawnToolDefinition() {
123
124
  template: input.template,
124
125
  }
125
126
  : {}),
126
- values: asRecord(input.values),
127
+ values: ModelContext.withCurrentModelValues(asRecord(input.values), ctx),
127
128
  ...(input.artifacts &&
128
129
  typeof input.artifacts === "object" &&
129
130
  !Array.isArray(input.artifacts)
@@ -10,6 +10,10 @@
10
10
  "model:string",
11
11
  "thinking:string",
12
12
  "tools:string",
13
+ "subagent_ttl_ms:int",
14
+ "reviewer_concurrency:string",
15
+ "min_successful_reviewers:int",
16
+ "merge_policy:string",
13
17
  "claim:string",
14
18
  "evidence_policy:string",
15
19
  "risk_policy:string",
@@ -21,12 +25,17 @@
21
25
  "architecture",
22
26
  "operator UX"
23
27
  ],
24
- "thinking": "off",
28
+ "model": "{current_model}",
29
+ "thinking": "{current_thinking}",
25
30
  "tools": "",
31
+ "subagent_ttl_ms": 600000,
32
+ "reviewer_concurrency": "",
33
+ "min_successful_reviewers": 1,
34
+ "merge_policy": "Preserve partial reviewer reports, branch status, and degraded confidence markers. If reviewer evidence is below threshold, do not invent consensus.",
26
35
  "claim": "The reviewed scope has clear, evidence-based findings, risks, and recommended next actions.",
27
36
  "evidence_policy": "Cite inspected files, command outputs, or explicit uncertainty for every material claim.",
28
37
  "risk_policy": "Preserve minority high-impact risks and ideas; separate confirmed issues from hypotheses.",
29
- "output_format": "Markdown sections: Summary, Consensus Findings, Minority Findings, Verification, Judge Notes, Risks, Next Actions."
38
+ "output_format": "Markdown sections: Status (complete/degraded/insufficient_data), Summary, Consensus Findings, Minority Findings, Verification, Judge Notes, Risks, Next Actions."
30
39
  },
31
40
  "mailbox": {
32
41
  "accepts": [
@@ -55,6 +64,10 @@
55
64
  "judge_model": "{model}",
56
65
  "thinking": "{thinking}",
57
66
  "tools": "{tools}",
67
+ "subagent_ttl_ms": "{subagent_ttl_ms}",
68
+ "reviewer_concurrency": "{reviewer_concurrency}",
69
+ "min_successful_reviewers": "{min_successful_reviewers}",
70
+ "merge_policy": "{merge_policy}",
58
71
  "evidence_policy": "{evidence_policy}",
59
72
  "risk_policy": "{risk_policy}",
60
73
  "output_format": "{output_format}"
@@ -20,7 +20,12 @@
20
20
  "verifier_model:string",
21
21
  "merger_model:string",
22
22
  "judge_model:string",
23
- "tools:string"
23
+ "thinking:string",
24
+ "tools:string",
25
+ "subagent_ttl_ms:int",
26
+ "reviewer_concurrency:string",
27
+ "min_successful_reviewers:int",
28
+ "merge_policy:string"
24
29
  ],
25
30
  "defaults": {
26
31
  "scope": ".",
@@ -36,7 +41,16 @@
36
41
  "documentation",
37
42
  "packaged skills"
38
43
  ],
39
- "tools": ""
44
+ "reviewer_model": "{current_model}",
45
+ "verifier_model": "{current_model}",
46
+ "merger_model": "{current_model}",
47
+ "judge_model": "{current_model}",
48
+ "thinking": "{current_thinking}",
49
+ "tools": "",
50
+ "subagent_ttl_ms": 600000,
51
+ "reviewer_concurrency": "",
52
+ "min_successful_reviewers": 1,
53
+ "merge_policy": "Preserve partial reviewer reports, branch status, and degraded confidence markers. If reviewer evidence is below threshold, do not invent consensus."
40
54
  },
41
55
  "mailbox": {
42
56
  "accepts": [
@@ -90,8 +104,13 @@
90
104
  "verifier_model": "{verifier_model}",
91
105
  "merger_model": "{merger_model}",
92
106
  "judge_model": "{judge_model}",
107
+ "thinking": "{thinking}",
93
108
  "tools": "{tools}",
94
- "output_format": "Markdown sections: Release Verdict, Blocking Issues, Package/Docs Risks, Validation Evidence, Publish Notes, Required Follow-up."
109
+ "subagent_ttl_ms": "{subagent_ttl_ms}",
110
+ "reviewer_concurrency": "{reviewer_concurrency}",
111
+ "min_successful_reviewers": "{min_successful_reviewers}",
112
+ "merge_policy": "{merge_policy}",
113
+ "output_format": "Markdown sections: Status (complete/degraded/insufficient_data), Release Verdict, Blocking Issues, Package/Docs Risks, Validation Evidence, Publish Notes, Required Follow-up."
95
114
  }
96
115
  },
97
116
  {
@@ -11,7 +11,12 @@
11
11
  "verifier_model:string",
12
12
  "merger_model:string",
13
13
  "judge_model:string",
14
- "tools:string"
14
+ "thinking:string",
15
+ "tools:string",
16
+ "subagent_ttl_ms:int",
17
+ "reviewer_concurrency:string",
18
+ "min_successful_reviewers:int",
19
+ "merge_policy:string"
15
20
  ],
16
21
  "defaults": {
17
22
  "lenses": [
@@ -21,7 +26,16 @@
21
26
  "operator UX"
22
27
  ],
23
28
  "release_gate": "The scope is safe to ship or hand to the next release step.",
24
- "tools": ""
29
+ "reviewer_model": "{current_model}",
30
+ "verifier_model": "{current_model}",
31
+ "merger_model": "{current_model}",
32
+ "judge_model": "{current_model}",
33
+ "thinking": "{current_thinking}",
34
+ "tools": "",
35
+ "subagent_ttl_ms": 600000,
36
+ "reviewer_concurrency": "",
37
+ "min_successful_reviewers": 1,
38
+ "merge_policy": "Preserve partial reviewer reports, branch status, and degraded confidence markers. If reviewer evidence is below threshold, do not invent consensus."
25
39
  },
26
40
  "mailbox": {
27
41
  "accepts": [
@@ -45,8 +59,13 @@
45
59
  "verifier_model": "{verifier_model}",
46
60
  "merger_model": "{merger_model}",
47
61
  "judge_model": "{judge_model}",
62
+ "thinking": "{thinking}",
48
63
  "tools": "{tools}",
49
- "output_format": "Markdown sections: Ship Verdict, Blocking Findings, Nonblocking Risks, Evidence, Required Follow-up."
64
+ "subagent_ttl_ms": "{subagent_ttl_ms}",
65
+ "reviewer_concurrency": "{reviewer_concurrency}",
66
+ "min_successful_reviewers": "{min_successful_reviewers}",
67
+ "merge_policy": "{merge_policy}",
68
+ "output_format": "Markdown sections: Status (complete/degraded/insufficient_data), Ship Verdict, Blocking Findings, Nonblocking Risks, Evidence, Required Follow-up."
50
69
  }
51
70
  }
52
71
  }
@@ -13,7 +13,8 @@
13
13
  "rubric": "Judge evidence preservation, severity calibration, consensus purity, internal consistency, and merge bias.",
14
14
  "evidence": "Use the provided raw outputs or artifact paths; mark any missing evidence.",
15
15
  "output_format": "Markdown sections: Verdict, Issues, Evidence Preservation, Bias Risks, Required Fixes.",
16
- "thinking": "medium",
16
+ "model": "{current_model}",
17
+ "thinking": "{current_thinking}",
17
18
  "tools": ""
18
19
  },
19
20
  "mailbox": {
@@ -3,6 +3,7 @@
3
3
  "args": [
4
4
  "inputs:string",
5
5
  "mode:enum(consensus-first,risk-first)",
6
+ "merge_policy:string",
6
7
  "risk_policy:string",
7
8
  "output_format:string",
8
9
  "model:string",
@@ -11,9 +12,11 @@
11
12
  ],
12
13
  "defaults": {
13
14
  "mode": "consensus-first",
15
+ "merge_policy": "Preserve partial reviewer evidence, branch status, and degraded confidence markers.",
14
16
  "risk_policy": "Preserve minority severe findings and mark merger-added claims explicitly.",
15
17
  "output_format": "Markdown sections: Summary, Consensus, Minority Findings, Contradictions, Next Actions.",
16
- "thinking": "medium",
18
+ "model": "{current_model}",
19
+ "thinking": "{current_thinking}",
17
20
  "tools": ""
18
21
  },
19
22
  "mailbox": {
@@ -27,5 +30,5 @@
27
30
  "run.failed"
28
31
  ]
29
32
  },
30
- "template": "pi -p --model {model} --thinking {thinking} {tools?--tools:--no-tools} {tools} Merge these subagent outputs using mode {mode}: {inputs}. Risk policy: {risk_policy}. Output format: {output_format}"
33
+ "template": "pi -p --model {model} --thinking {thinking} {tools?--tools:--no-tools} {tools} Merge these subagent outputs using mode {mode}: {inputs}. Merge policy: {merge_policy}. Risk policy: {risk_policy}. Output format: {output_format}"
31
34
  }
@@ -11,7 +11,8 @@
11
11
  "defaults": {
12
12
  "format": "Markdown sections: Summary, Findings, Evidence, Risks, Next Actions.",
13
13
  "preservation_policy": "Do not change meaning, severity, evidence, or uncertainty while normalizing.",
14
- "thinking": "off",
14
+ "model": "{current_model}",
15
+ "thinking": "{current_thinking}",
15
16
  "tools": ""
16
17
  },
17
18
  "mailbox": {
@@ -0,0 +1,29 @@
1
+ {
2
+ "async": true,
3
+ "args": [
4
+ "stage:string",
5
+ "model:string",
6
+ "thinking:string",
7
+ "tools:string",
8
+ "output_format:string"
9
+ ],
10
+ "defaults": {
11
+ "stage": "subagent",
12
+ "model": "{current_model}",
13
+ "thinking": "{current_thinking}",
14
+ "tools": "",
15
+ "output_format": "Reply exactly: ACTOR_PREFLIGHT_OK"
16
+ },
17
+ "mailbox": {
18
+ "accepts": [
19
+ "control.kill"
20
+ ],
21
+ "emits": [
22
+ "preflight.completed",
23
+ "command.done",
24
+ "run.done",
25
+ "run.failed"
26
+ ]
27
+ },
28
+ "template": "pi -p --model {model} --thinking {thinking} {tools?--tools:--no-tools} {tools} Preflight check for stage {stage}. Confirm this model, thinking level, and tool policy can start before expensive fanout. Do not inspect files. {output_format}"
29
+ }
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "async": true,
3
3
  "imports": {
4
+ "preflight": "subagent-preflight.json",
4
5
  "reviewer": "subagent-review.json",
5
6
  "verifier": "subagent-verify.json",
6
7
  "merger": "subagent-merge.json",
@@ -17,6 +18,10 @@
17
18
  "judge_model:string",
18
19
  "thinking:string",
19
20
  "tools:string",
21
+ "subagent_ttl_ms:int",
22
+ "reviewer_concurrency:string",
23
+ "min_successful_reviewers:int",
24
+ "merge_policy:string",
20
25
  "evidence_policy:string",
21
26
  "risk_policy:string",
22
27
  "output_format:string"
@@ -28,11 +33,19 @@
28
33
  "operator UX"
29
34
  ],
30
35
  "claim": "The reviewed scope is ready for the next implementation or release step.",
31
- "thinking": "off",
36
+ "thinking": "{current_thinking}",
32
37
  "tools": "",
38
+ "subagent_ttl_ms": 600000,
39
+ "reviewer_concurrency": "",
40
+ "min_successful_reviewers": 1,
41
+ "merge_policy": "Preserve partial reviewer reports, branch status, and degraded confidence markers. If reviewer evidence is below threshold, do not invent consensus.",
33
42
  "evidence_policy": "Cite inspected files, command output, or explicit uncertainty for every material claim.",
34
43
  "risk_policy": "Preserve minority high-impact risks and separate confirmed issues from hypotheses.",
35
- "output_format": "Markdown sections: Summary, Consensus Findings, Minority Findings, Verification, Judge Notes, Risks, Next Actions."
44
+ "output_format": "Markdown sections: Status (complete/degraded/insufficient_data), Summary, Consensus Findings, Minority Findings, Verification, Judge Notes, Risks, Next Actions.",
45
+ "reviewer_model": "{current_model}",
46
+ "verifier_model": "{current_model}",
47
+ "merger_model": "{current_model}",
48
+ "judge_model": "{current_model}"
36
49
  },
37
50
  "mailbox": {
38
51
  "accepts": [
@@ -49,12 +62,65 @@
49
62
  ]
50
63
  },
51
64
  "template": [
65
+ {
66
+ "parallel": true,
67
+ "failure": "root",
68
+ "template": [
69
+ {
70
+ "label": "preflight:reviewer",
71
+ "name": "preflight",
72
+ "timeout": "{subagent_ttl_ms}",
73
+ "values": {
74
+ "stage": "reviewer",
75
+ "model": "{reviewer_model}",
76
+ "thinking": "{thinking}",
77
+ "tools": "{tools}"
78
+ }
79
+ },
80
+ {
81
+ "label": "preflight:verifier",
82
+ "name": "preflight",
83
+ "timeout": "{subagent_ttl_ms}",
84
+ "values": {
85
+ "stage": "verifier",
86
+ "model": "{verifier_model}",
87
+ "thinking": "{thinking}",
88
+ "tools": "{tools}"
89
+ }
90
+ },
91
+ {
92
+ "label": "preflight:merger",
93
+ "name": "preflight",
94
+ "timeout": "{subagent_ttl_ms}",
95
+ "values": {
96
+ "stage": "merger",
97
+ "model": "{merger_model}",
98
+ "thinking": "{thinking}",
99
+ "tools": "{tools}"
100
+ }
101
+ },
102
+ {
103
+ "label": "preflight:judge",
104
+ "name": "preflight",
105
+ "timeout": "{subagent_ttl_ms}",
106
+ "values": {
107
+ "stage": "judge",
108
+ "model": "{judge_model}",
109
+ "thinking": "{thinking}",
110
+ "tools": "{tools}"
111
+ }
112
+ }
113
+ ]
114
+ },
52
115
  {
53
116
  "parallel": true,
54
117
  "repeat": "{lenses.length}",
118
+ "concurrency": "{reviewer_concurrency}",
119
+ "min_successful": "{min_successful_reviewers}",
55
120
  "failure": "branch",
56
121
  "template": {
57
122
  "name": "reviewer",
123
+ "timeout": "{subagent_ttl_ms}",
58
124
  "values": {
59
125
  "scope": "{scope}",
60
126
  "lens": "{lenses[index]}",
@@ -68,9 +134,10 @@
68
134
  },
69
135
  {
70
136
  "name": "verifier",
137
+ "timeout": "{subagent_ttl_ms}",
71
138
  "values": {
72
139
  "claim": "{claim}",
73
- "evidence": "Use previous reviewer outputs from stdin and named scope: {scope}.",
140
+ "evidence": "Use previous reviewer outputs from stdin and named scope: {scope}. Respect the parallel_status header and verify only against usable reviewer evidence.",
74
141
  "model": "{verifier_model}",
75
142
  "thinking": "{thinking}",
76
143
  "tools": "{tools}",
@@ -79,32 +146,37 @@
79
146
  },
80
147
  {
81
148
  "name": "merger",
149
+ "timeout": "{subagent_ttl_ms}",
82
150
  "values": {
83
151
  "inputs": "Use previous reviewer and verifier outputs from stdin.",
84
152
  "mode": "consensus-first",
153
+ "merge_policy": "{merge_policy}",
85
154
  "model": "{merger_model}",
86
- "thinking": "medium",
155
+ "thinking": "{thinking}",
87
156
  "tools": "{tools}",
88
157
  "risk_policy": "{risk_policy}"
89
158
  }
90
159
  },
91
160
  {
92
161
  "name": "judge",
162
+ "timeout": "{subagent_ttl_ms}",
93
163
  "values": {
94
164
  "report": "Use merged output from stdin.",
95
- "evidence": "Use previous reviewer, verifier, and merger outputs from stdin.",
165
+ "evidence": "Use previous reviewer, verifier, and merger outputs from stdin. Preserve complete/degraded/insufficient_data status.",
96
166
  "model": "{judge_model}",
97
- "thinking": "medium",
167
+ "thinking": "{thinking}",
98
168
  "tools": "{tools}"
99
169
  }
100
170
  },
101
171
  {
102
172
  "name": "normalizer",
173
+ "timeout": "{subagent_ttl_ms}",
103
174
  "values": {
104
175
  "input": "Use merged and judged output from stdin.",
105
176
  "format": "{output_format}",
177
+ "preservation_policy": "Preserve branch status and explicitly mark Status as complete, degraded, or insufficient_data.",
106
178
  "model": "{merger_model}",
107
- "thinking": "off",
179
+ "thinking": "{thinking}",
108
180
  "tools": "{tools}"
109
181
  }
110
182
  }
@@ -17,7 +17,8 @@
17
17
  "evidence_policy": "Cite inspected files, command output, or explicit uncertainty for every material claim.",
18
18
  "risk_policy": "Preserve minority high-impact risks and separate confirmed issues from hypotheses.",
19
19
  "output_format": "Markdown sections: Findings, Evidence, Risks, Next Actions.",
20
- "thinking": "off",
20
+ "model": "{current_model}",
21
+ "thinking": "{current_thinking}",
21
22
  "tools": ""
22
23
  },
23
24
  "mailbox": {
@@ -14,7 +14,8 @@
14
14
  "acceptance": "Separate proven, disproven, unknown, and missing evidence.",
15
15
  "evidence_policy": "Do not infer beyond provided evidence or inspected artifacts.",
16
16
  "output_format": "Markdown sections: Verdict, Evidence, Gaps, Confidence.",
17
- "thinking": "off",
17
+ "model": "{current_model}",
18
+ "thinking": "{current_thinking}",
18
19
  "tools": ""
19
20
  },
20
21
  "mailbox": {
@@ -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",