pi-subagents 0.60.0 → 0.62.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 (76) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/docs/agents.md +7 -3
  3. package/docs/configuration.md +9 -5
  4. package/docs/extension-api.md +14 -7
  5. package/docs/models.md +1 -1
  6. package/docs/observability.md +1 -1
  7. package/docs/tool-reference.md +13 -4
  8. package/docs/workflows.md +14 -13
  9. package/install.mjs +1 -1
  10. package/package.json +1 -1
  11. package/skills/pi-subagents/SKILL.md +6 -4
  12. package/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
  13. package/skills/pi-subagents/references/execution-controls.md +41 -11
  14. package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
  15. package/skills/pi-subagents/references/prompting-and-roles.md +4 -4
  16. package/skills/pi-subagents/references/review-and-validation.md +1 -1
  17. package/src/agents/agent-management.ts +121 -63
  18. package/src/agents/agent-serializer.ts +3 -0
  19. package/src/agents/agents.ts +543 -223
  20. package/src/agents/runtime-agent-registry.ts +5 -1
  21. package/src/api/background-work.ts +7 -2
  22. package/src/api/external-runs.ts +67 -4
  23. package/src/api/preflight.ts +17 -8
  24. package/src/api/shared-types.ts +1 -0
  25. package/src/extension/index.ts +7 -4
  26. package/src/extension/public-execution.ts +48 -4
  27. package/src/extension/rpc.ts +62 -4
  28. package/src/extension/schemas.ts +17 -9
  29. package/src/extension/tool-description.ts +15 -19
  30. package/src/runs/background/active-async-capacity.ts +26 -7
  31. package/src/runs/background/async-execution.ts +98 -51
  32. package/src/runs/background/async-job-tracker.ts +62 -3
  33. package/src/runs/background/async-resume.ts +6 -3
  34. package/src/runs/background/async-status.ts +59 -9
  35. package/src/runs/background/auto-drain.ts +1 -1
  36. package/src/runs/background/fleet-view.ts +1 -1
  37. package/src/runs/background/process-terminal.ts +16 -0
  38. package/src/runs/background/result-watcher.ts +1 -1
  39. package/src/runs/background/resume-guidance.ts +1 -1
  40. package/src/runs/background/run-status.ts +2 -2
  41. package/src/runs/background/scheduled-runs.ts +63 -6
  42. package/src/runs/background/steering.ts +4 -1
  43. package/src/runs/background/subagent-runner.ts +15 -9
  44. package/src/runs/background/subagent-wait.ts +20 -21
  45. package/src/runs/background/wait-completions.ts +1 -1
  46. package/src/runs/background/wait-tool.ts +18 -18
  47. package/src/runs/foreground/execution.ts +75 -9
  48. package/src/runs/foreground/subagent-executor.ts +181 -60
  49. package/src/runs/shared/acceptance.ts +113 -27
  50. package/src/runs/shared/capability-ceiling.ts +1 -0
  51. package/src/runs/shared/dynamic-fanout.ts +1 -1
  52. package/src/runs/shared/host-step-status.ts +1 -0
  53. package/src/runs/shared/model-fallback.ts +61 -17
  54. package/src/runs/shared/parallel-utils.ts +2 -6
  55. package/src/runs/shared/permissions.ts +1 -1
  56. package/src/runs/shared/pi-args.ts +24 -12
  57. package/src/runs/shared/pi-spawn.ts +69 -35
  58. package/src/runs/shared/structured-output.ts +33 -6
  59. package/src/runs/shared/subagent-prompt-runtime.ts +20 -3
  60. package/src/runs/shared/task-intent.ts +17 -6
  61. package/src/runs/shared/tool-timeout.ts +1 -1
  62. package/src/runs/shared/workflow-graph.ts +3 -2
  63. package/src/shared/atomic-json.ts +3 -1
  64. package/src/shared/fork-context.ts +0 -12
  65. package/src/shared/fork-session-cwd.ts +27 -0
  66. package/src/shared/launch-contract.ts +3 -0
  67. package/src/shared/types.ts +43 -4
  68. package/src/shared/workflow-child-permit.ts +91 -0
  69. package/src/slash/prompt-template-bridge.ts +37 -1
  70. package/src/slash/slash-commands.ts +18 -26
  71. package/src/slash/subagents-admin.ts +2 -0
  72. package/src/tui/render.ts +95 -52
  73. package/src/workflows/scripted-workflow.ts +153 -4
  74. package/src/workflows/workflow-child-summary.ts +1 -1
  75. package/src/workflows/workflow-receipt.ts +41 -4
  76. package/src/workflows/workflow-resources.ts +150 -0
package/CHANGELOG.md CHANGED
@@ -3,6 +3,61 @@
3
3
 
4
4
  ## [Unreleased]
5
5
 
6
+ ## [0.62.0] - 2026-08-31
7
+
8
+ ### Highlights
9
+ - Child agents can report completion evidence more cleanly and stay away from tools they should not use.
10
+ - Session-only schedules keep personal scheduled work tied to the session that created it.
11
+ - Async forked runs now start and resume in the working directory you requested.
12
+ - Windows child launches are more reliable, with clearer errors when Pi cannot find a valid CLI.
13
+ - External CLI and read-only recovery paths are sturdier when workers disappear or prompts include unusual line separators.
14
+
15
+ ### Added
16
+ - Let native children with `outputSchema` include required acceptance evidence in the same `structured_output` call with `acceptance.report: "on"`; `acceptance.report: "off"` keeps fenced acceptance reports. Thanks [@mapleluvr](https://github.com/mapleluvr) for #1770.
17
+ - Add per-agent `excludeTools` deny-lists that compose with Pi's ambient or explicit child tool selection. Thanks [@expoli](https://github.com/expoli) for #1776.
18
+ - Add session-only durable schedules that only run in the session that created them. Thanks [@yangfeng20](https://github.com/yangfeng20) for #1777.
19
+
20
+ ### Fixed
21
+ - Keep async forked runs in the requested child `cwd` when they start or resume. Thanks [@stekman08](https://github.com/stekman08) for #1785.
22
+ - Accept JSON-encoded acceptance objects from model tool calls, while still failing clearly for malformed strings. Thanks [@mapleluvr](https://github.com/mapleluvr) for #1781.
23
+ - Keep steer and follow-up receipt statuses separate from their redacted message previews (#1773).
24
+ - Create async lifecycle sidecars before external CLI workers begin worktree changes, so disappeared runners are reported as failed runs. Thanks [@fkhawajagh](https://github.com/fkhawajagh) for #1764.
25
+ - Preserve explicit read-only intent when escaped line separators surround no-edit wording. Thanks [@fkhawajagh](https://github.com/fkhawajagh) for #1765.
26
+ - Launch child Pi processes through the resolved CLI JavaScript on Windows, and run JavaScript `PI_SUBAGENT_PI_BINARY` overrides with Node. Thanks [@caohuipeng](https://github.com/caohuipeng) for #1768.
27
+ - Resolve the installed Pi CLI on Windows wrapper hosts from the forwarded package root, and report a clear error when no verified CLI can be found. Thanks [@lux032](https://github.com/lux032) for #1780.
28
+
29
+ ## [0.61.0] - 2026-08-31
30
+
31
+ ### Highlights
32
+ - Subagent runs use less context and repeat less status text, so everyday delegation is cheaper and easier to scan.
33
+ - Status, Fleet, widgets, RPC, and background-work views refresh with less duplicated work.
34
+ - Async recovery is more reliable when active status files, child reports, or provider fallback attempts go wrong.
35
+ - Workflow permissions are clearer with named workflow resources and `bg_wait` as the primary background wait tool.
36
+ - Model listings now show effective models for discovered and runtime-registered agents.
37
+
38
+ ### Added
39
+ - Add extension-owned named workflow resources so permission and policy extensions can distinguish trusted workflow resources from raw scripts. Thanks [@mathiasloh](https://github.com/mathiasloh) for #1751.
40
+ - Add workflow-only `globalConcurrencyLimit` and `maxSubagentSpawnsPerRun` overrides for top-level `workflowScript` calls. Thanks [@RapierCraft](https://github.com/RapierCraft) for #1760.
41
+ - Remove the deprecated compatibility wait alias; use `bg_wait` instead (#1729).
42
+
43
+ ### Changed
44
+ - Show effective model mappings for discovered and runtime-registered subagents through management and `/subagents-models`. Thanks [@RapierCraft](https://github.com/RapierCraft) for #1732.
45
+ - Trim default subagent prompt guidelines to five parent-facing entries while keeping advanced workflow details in the packaged guide. Thanks [@Ran-Xing](https://github.com/Ran-Xing) for #1746.
46
+ - Reduce repeated heartbeat status updates during delegated runs while keeping foreground progress and complete terminal responses (#1739).
47
+ - Clarify that `fallbackModels` handles provider/model timeouts but not run-level `timeoutMs` / `maxRuntimeMs` expiry. Thanks [@kaplan-shaked](https://github.com/kaplan-shaked) for #1745.
48
+ - Pass requested session and timestamp context to background-work providers so they can avoid listing unrelated sessions while preserving strict validation (#1737).
49
+ - Avoid repeated external-run display normalization during Fleet refresh while still validating externally replaced or mutated records (#1736).
50
+ - Clarify that `oracle` and top-reasoning models are escalation tools, not routine defaults.
51
+ - Serve broad RPC status requests from restored in-memory state when safe, while preserving targeted status and transcript behavior (#1735).
52
+
53
+ ### Fixed
54
+ - Isolate corrupt active async status files during restoration so valid runs remain available and corrupt run artifacts are preserved. Thanks [@zhexulong](https://github.com/zhexulong) for #1756.
55
+ - Reduce async widget update churn during running workflows by repainting animation ticks without reinstalling the widget and coalescing close status refreshes (#1726).
56
+ - Avoid repeated staged workflow projection during widget rendering. Thanks [@kkkhs](https://github.com/kkkhs) for #1730.
57
+ - Preserve durable file-only child reports and continue read-only workflow review after malformed acceptance metadata (#1724).
58
+ - Preserve model origins across fallback and fork preparation, so eligible fallbacks work for unavailable configured primaries while invalid explicit models still fail closed. Thanks [@xz-dev](https://github.com/xz-dev) for #1747.
59
+ - Stop hidden 125ms spinner redraws while keeping progress refreshes and one-second animation frames (#1747).
60
+
6
61
  ## [0.60.0] - 2026-08-30
7
62
 
8
63
  ### Highlights
package/docs/agents.md CHANGED
@@ -255,6 +255,7 @@ package: code-analysis
255
255
  description: Fast codebase recon
256
256
  aliases: explorer, code-scout
257
257
  tools: read, grep, find, ls, bash, mcp:chrome-devtools
258
+ excludeTools: bash
258
259
  extensions:
259
260
  subagentOnlyExtensions: ./tools/child-only-search.ts
260
261
  model: claude-haiku-4-5
@@ -283,7 +284,7 @@ allowNestedSubagents: true
283
284
  Your system prompt goes here.
284
285
  ```
285
286
 
286
- Simple-scalar list fields accept either a comma-separated form or a newline block list with one `- item` per line. This applies to `tools`, `defaultReads`, `skill`/`skills`, `skillPath`, `fallbackModels`, `extensions`, and `subagentOnlyExtensions`:
287
+ Simple-scalar list fields accept either a comma-separated form or a newline block list with one `- item` per line. This applies to `tools`, `excludeTools`, `defaultReads`, `skill`/`skills`, `skillPath`, `fallbackModels`, `extensions`, and `subagentOnlyExtensions`:
287
288
 
288
289
  ```yaml
289
290
  tools:
@@ -301,11 +302,12 @@ Field notes:
301
302
  | `package` | Optional package identifier. A file with `name: scout` and `package: code-analysis` registers as `code-analysis.scout`; serialization keeps `name` and `package` separate. |
302
303
  | `aliases` | Optional comma-separated or block-list names that resolve to this agent for selection and explicit `agent` and task inputs. Runtime status, persistence, and config still use the canonical `name`. Exact canonical names take precedence over aliases, and alias collisions between distinct canonical agents fail as ambiguous. |
303
304
  | `tools` | Strict child tool allowlist. Named extension tools must also have their provider loaded. `mcp:` entries select direct MCP tools when `pi-mcp-adapter` is installed. |
305
+ | `excludeTools` | Optional child tool deny-list applied after normal tool resolution. With an explicit `tools` allowlist, matching names are removed; when `tools` is omitted, the names are forwarded to Pi as `--exclude-tools` so the ambient tool set is inherited minus those names. Unknown names are ignored by Pi without making the agent definition invalid. |
304
306
  | `allowNestedSubagents` | Set `true` to authorize the child-safe nested `subagent` runtime without making omitted `tools` an allowlist. Inherited depth and capability ceilings remain authoritative. |
305
307
  | `extensions` | Omitted means normal extensions; empty means no extensions; list values allowlist specific extensions. |
306
308
  | `subagentOnlyExtensions` | Extension paths loaded only in spawned child sessions for this agent. Tools registered there are unavailable to the main agent unless also installed through normal Pi extension configuration. |
307
309
  | `model` | Default model. Bare ids prefer the current provider when possible, then unique registry matches. |
308
- | `fallbackModels` | Ordered backup models for provider/model failures such as quota, auth, timeout, or unavailable model. Ordinary task failures do not trigger fallback. |
310
+ | `fallbackModels` | Ordered backup models for provider/model failures such as quota, auth, provider-reported timeout, or unavailable model. Expiration of the run-level `timeoutMs` / `maxRuntimeMs` deadline is terminal and does not trigger fallback. Ordinary task failures do not trigger fallback. |
309
311
  | `thinking` | Appended as a `:level` suffix at runtime unless a suffix is already present. |
310
312
  | `systemPromptMode` | `replace` by default; `append` keeps Pi's base prompt. |
311
313
  | `inheritProjectContext` | Keeps or strips inherited repository instruction blocks. |
@@ -319,7 +321,7 @@ Field notes:
319
321
  | `defaultProgress` | Maintain `progress.md`. |
320
322
  | `async` | Default a single-agent launch to background (`true`) or foreground (`false`) when the call omits `async`. Explicit call values and `forceTopLevelAsync` win. |
321
323
  | `timeoutMs` | Positive integer default runtime deadline in milliseconds for single-agent launches. Foreground launches use 30 minutes when neither the call nor agent provides a timeout; explicit `timeoutMs`/`maxRuntimeMs` and agent defaults win. |
322
- | `toolTimeoutMs` | Optional positive integer hard per-tool-call deadline in milliseconds. An explicit call value wins, then this agent default, global `toolTimeoutMs`, and `PI_SUBAGENT_TOOL_TIMEOUT_MS`. When omitted, known-fast built-in tools get a five-minute default; long-running tools get attention notices but no hard default. It does not extend the run-level deadline; `contact_supervisor`, `intercom`, and `subagent_wait` are exempt. |
324
+ | `toolTimeoutMs` | Optional positive integer hard per-tool-call deadline in milliseconds. An explicit call value wins, then this agent default, global `toolTimeoutMs`, and `PI_SUBAGENT_TOOL_TIMEOUT_MS`. When omitted, known-fast built-in tools get a five-minute default; long-running tools get attention notices but no hard default. It does not extend the run-level deadline; `contact_supervisor`, `intercom`, and `bg_wait` are exempt. |
323
325
  | `acceptance` | Acceptance default for single-agent launches. Use a scalar level such as `checked` or an inline/block YAML map such as `{ level: "none", reason: "lightweight lookup" }`. Explicit call values win; chain and parallel acceptance remains task/step configuration. |
324
326
  | `acceptanceRole` | Optional `read-only` or `writer` role for automatic acceptance inference. Explicit task mutation or no-edit intent wins; otherwise the declared role replaces agent-name guessing. This does not grant or revoke tools. |
325
327
  | `mutationTools` | Comma-separated extension tool names whose calls count as mutation attempts for the completion guard. This declares evidence only; list and load each tool through `tools` and its extension provider as usual. |
@@ -385,6 +387,8 @@ How `tools` behaves:
385
387
  - `tools:` empty: emits `--no-tools`.
386
388
  - `allowNestedSubagents: true`: explicitly enables child-safe nested fanout without turning omitted `tools` into an allowlist. Depth and inherited capability ceilings still apply.
387
389
 
390
+ `excludeTools` is applied after this resolution. It can narrow an explicit `tools` allowlist or, when `tools` is omitted, compose with Pi's ambient builtin tools through `--exclude-tools`. Runtime-injected tools are excluded only when their exact names are listed. An empty `excludeTools` list has no effect.
391
+
388
392
  An allowlisted name does not load the extension that registers it. Load that provider through normal Pi extension discovery, `extensions`, `subagentOnlyExtensions`, or a path-like `tools` entry.
389
393
 
390
394
  More rules:
@@ -36,7 +36,7 @@ Controls the duration, in milliseconds, for model exclusions. The default is `86
36
36
  { "toolDescriptionMode": "compact" }
37
37
  ```
38
38
 
39
- Controls the parent-facing `subagent` tool description registered at startup. The default registers split prompt metadata: a short tool description plus `promptSnippet` and `promptGuidelines`. Set `"full"` to register the complete description as one tool description, or `"compact"` to keep the execution modes, async/`subagent_wait` guidance, child-safety boundary, management/action split, one-writer review guidance, and artifact/status essentials with less prompt bloat.
39
+ Controls the parent-facing `subagent` tool description registered at startup. The default registers split prompt metadata: a short tool description plus `promptSnippet` and `promptGuidelines`. Set `"full"` to register the complete description as one tool description, or `"compact"` to keep the execution modes, async/`bg_wait` guidance, child-safety boundary, management/action split, one-writer review guidance, and artifact/status essentials with less prompt bloat.
40
40
 
41
41
  `custom` reads `subagent-tool-description.md` from the project config directory, then from `~/.pi/agent/subagent-tool-description.md`. Missing, empty, unreadable, or oversized custom files fall back to the full description. Custom templates may use `{{fullDescription}}`, `{{compactDescription}}`, `{{safetyGuidance}}`, `{{agentDir}}`, and `{{projectConfigDir}}`; the safety guidance is always present so custom prose cannot remove the runtime guardrails. Restart Pi after changing the mode or custom file.
42
42
 
@@ -187,9 +187,9 @@ Controls the under-editor widget for active background runs. It defaults to `tru
187
187
  { "waitTool": { "enabled": true, "defaultTimeoutMs": 120000 } }
188
188
  ```
189
189
 
190
- `defaultTimeoutMs` sets the blocking window used when a `subagent_wait` call omits `timeoutMs`; explicit call values win, followed by this setting, then the 30-minute fallback. When the window elapses, the tool returns a non-error `window_elapsed` result with the still-active work identities, and that work keeps running. Set `enabled` to `false` to keep the tool registered while making direct calls return immediately instead of blocking. The default is enabled. You can also set `"waitTool": false`; set `PI_SUBAGENT_WAIT_TOOL_ENABLED=false` (or `0`, `off`, `disabled`) to override config for one process. The effective enabled and default-timeout values are passed explicitly to child runtimes. Headless `agent_end` auto-drain retains its own strict deadline and fails if required work remains unresolved. Invalid config or environment values fail instead of being coerced.
190
+ `defaultTimeoutMs` sets the blocking window used when a `bg_wait` call omits `timeoutMs`; explicit call values win, followed by this setting, then the 30-minute fallback. `bg_wait` is the only registered wait tool. When the window elapses, the tool returns a non-error `window_elapsed` result with the still-active work identities, and that work keeps running. Set `enabled` to `false` to make direct calls return immediately instead of blocking. The default is enabled. You can also set `"waitTool": false`; set `PI_SUBAGENT_WAIT_TOOL_ENABLED=false` (or `0`, `off`, `disabled`) to override config for one process. The effective enabled and default-timeout values are passed explicitly to child runtimes. Headless `agent_end` auto-drain retains its own strict deadline and fails if required work remains unresolved. Invalid config or environment values fail instead of being coerced.
191
191
 
192
- Blocking `subagent_wait({ id: "..." })` keeps the current tool call open until that run changes. By default it returns when a run needs attention. Use `subagent_wait({ stopOnAttention: false })` only for run-to-completion flows that should wait through idle or long-thinking attention; supervisor/contact requests still stop the wait. In a long-lived interactive parent session, `subagent_wait({ id: "...", nonBlocking: true })` instead resolves the prefix once, persists the exact run identity, returns a subscription token immediately, and wakes that session on completion, failure, attention, reconciliation failure, or timeout. Armed subscriptions appear in ordinary `subagent({ action: "status" })` output and are not counted as active child work.
192
+ Blocking `bg_wait({ id: "..." })` keeps the current tool call open until that run changes. By default it returns when a run needs attention. Use `bg_wait({ stopOnAttention: false })` only for run-to-completion flows that should wait through idle or long-thinking attention; supervisor/contact requests still stop the wait. In a long-lived interactive parent session, `bg_wait({ id: "...", nonBlocking: true })` instead resolves the prefix once, persists the exact run identity, returns a subscription token immediately, and wakes that session on completion, failure, attention, reconciliation failure, or timeout. Use it for provider, detached, or other background work without a native completion notification; ordinary async subagent runs notify the parent natively and do not need a wait subscription. Armed subscriptions appear in ordinary `subagent({ action: "status" })` output and are not counted as active child work.
193
193
 
194
194
  This is different from `waitTool.enabled=false`, which returns immediately without registering any future wake. Provider items remain available only to blocking fleet-wide waits; non-blocking subscriptions require one async or remembered detached foreground run id.
195
195
 
@@ -217,7 +217,7 @@ Forces depth-0 internal single, parallel, and chain runs into background mode an
217
217
  { "timeoutMs": 3600000 }
218
218
  ```
219
219
 
220
- Global default runtime deadline, in milliseconds, for subagent runs. It replaces the built-in 30-minute backstop for foreground launches (single, parallel, chain, and workflowScript) and plain single-agent async runs whenever no call-level `timeoutMs`/`maxRuntimeMs` applies. For single-agent launches, selected agent frontmatter `timeoutMs` still wins. This only moves the *default*.
220
+ Global default runtime deadline, in milliseconds, for subagent runs. It replaces the built-in 30-minute backstop for foreground launches (single, parallel, chain, and workflowScript) and plain single-agent async runs whenever no call-level `timeoutMs`/`maxRuntimeMs` applies. For single-agent launches, selected agent frontmatter `timeoutMs` still wins. This only moves the *default*. Expiring this run-level deadline is terminal and does not trigger `fallbackModels`; only provider/model failures reported before the deadline can fall back.
221
221
 
222
222
  Use it when foreground orchestration or plain async single-agent runs need a longer default than 30 minutes. It does not set async composite top-level deadlines, and it does not replace async fan-out child deadlines.
223
223
 
@@ -233,7 +233,7 @@ Optional hard per-tool-call deadline in milliseconds. When configured, a child t
233
233
 
234
234
  Without a configured value, Pi still applies a five-minute hard timeout to known-fast built-in tools: `read`, `grep`, `find`, `ls`, `edit`, `write`, and `structured_output`. Long-running tools such as `bash`, custom tools, and MCP tools do not get a hard default. They get the normal open-tool attention notice after `activeNoticeAfterMs` and remain bounded by the run-level deadline.
235
235
 
236
- The tool timer tracks each active `toolCallId` separately and never extends the run-level deadline: when the remaining run budget is shorter, the ordinary run-level timeout wins. `contact_supervisor`, `intercom`, and `subagent_wait` are exempt because their legitimate purpose can be to wait for a human, supervisor, or child run. Use hard tool timeouts only for wedge protection; an elapsed timeout is not a mutation-safe boundary. Configured values must be positive integers no greater than `2147483647`; invalid or out-of-range values are rejected with a visible error rather than silently ignored.
236
+ The tool timer tracks each active `toolCallId` separately and never extends the run-level deadline: when the remaining run budget is shorter, the ordinary run-level timeout wins. `contact_supervisor`, `intercom`, and `bg_wait` are exempt because their legitimate purpose can be to wait for a human, supervisor, or background run. Use hard tool timeouts only for wedge protection; an elapsed timeout is not a mutation-safe boundary. Configured values must be positive integers no greater than `2147483647`; invalid or out-of-range values are rejected with a visible error rather than silently ignored.
237
237
 
238
238
  ## `globalConcurrencyLimit`
239
239
 
@@ -243,6 +243,8 @@ The tool timer tracks each active `toolCallId` separately and never extends the
243
243
 
244
244
  Caps simultaneously running children inside one run, including durable legacy multi-child runs and `workflowScript` launches through `runs.run`/`runs.all`. Queued workflow children retain their stable keys and begin when a running sibling releases capacity. The default is `20`.
245
245
 
246
+ Inline or file-backed top-level workflow calls may set a positive safe-integer `globalConcurrencyLimit` to override this value for that workflow. The override is workflow-only and is not forwarded to child calls.
247
+
246
248
  ## `maxSubagentSpawnsPerSession`
247
249
 
248
250
  ```json
@@ -261,6 +263,8 @@ Optionally caps the total number of child subagent launches during one parent se
261
263
 
262
264
  Caps cumulative logical child admissions in one top-level run tree. The default is `64`. `PI_SUBAGENT_MAX_SPAWNS_PER_RUN` overrides the config when it is a positive integer. Invalid, zero, or missing values fall back to the configured positive value or `64`.
263
265
 
266
+ Inline or file-backed top-level workflow calls may set a positive safe-integer `maxSubagentSpawnsPerRun`; it overrides the environment and config for that workflow. Inherited nested budgets remain authoritative, and the override is not forwarded to child calls.
267
+
264
268
  The budget counts single launches, expanded `tasks`/`count`, static chain steps and parallel groups, actual dynamic `expand` items, appended chain steps, workflow children, and nested child calls. Static and materialized dynamic groups are admitted atomically. Startup retries, model fallback, and retained-child resume reuse the original logical child claim. Claims are never released or refunded. This cap is independent from the session-wide cumulative spawn budget and `globalConcurrencyLimit`.
265
269
 
266
270
  ## `maxActiveAsyncRunsPerSession`
@@ -32,6 +32,7 @@ Method notes:
32
32
  - `steer` requires an async run `id` (plus optional child `index`) and a non-empty `message`; its reply preserves the normal acknowledged-delivery result. Optional `mode` values are `steer` (default), `follow_up`, and `auto`, and receipts include `deliveryStatus: "delivered" | "queued"`. RPC steering disables the direct tool's pause-and-revive recovery in every mode so an extension keeps authority over the exact child it spawned; `ping.capabilities.nonRecoveringSteer` advertises this guarantee.
33
33
  - `resume` requires a run target and non-empty `message`. It delegates to the existing revival path, which validates current-session ownership, persisted session/recovery metadata, stopped/live state, capability ceilings, and the exclusive session lease before returning the new async run details. Callers may request a `file-only` output path for the revived result without overriding its model, tools, or budgets. `ping.capabilities.resume` advertises this seam.
34
34
  - `stop` targets current-session top-level async runs through the stop control channel and records a `stopped` lifecycle instead of reporting a timeout.
35
+ - `status` keeps targeted and rich requests on the executor-backed path. A request with no `id`, `runId`, `dir`, `index`, `view`, or `lines` may use the restored in-memory projections and a short summary; when the live state is missing, stale, session-mismatched, or not restored, it falls back to normal executor status. Status `view`, `lines`, and `index` are forwarded for targeted transcript/fleet requests. Successful replies retain `text`, `details`, `fleet`, and `asyncSnapshot`; the short summary intentionally omits canonical filesystem details, wait subscriptions, and budget annotations.
35
36
 
36
37
  Capability advertisements on `ping`:
37
38
 
@@ -42,6 +43,7 @@ Capability advertisements on `ping`:
42
43
  - `processTerminalProof` — the process-terminal proof status (see [observability.md](observability.md#process-terminal-proof)).
43
44
  - `nonRecoveringSteer` — RPC steering never pauses-and-revives.
44
45
  - `resume` — the revival seam described above.
46
+ - `statusProjection: { version: 1, untargeted: "in-memory-when-ready", targeted: "executor" }` — untargeted status may use restored bounded projections; targeted or rich status remains executor-backed.
45
47
  - `fleetStatus: { version: 1 }` — successful `status` replies additionally include `data.fleet`.
46
48
 
47
49
  Structured delegation progress updates carry `runId` as soon as foreground execution allocates it, so a caller can retain the package-owned revival target even if its own tool turn is interrupted before the terminal response. Foreground `details.results[]` rows also include a numeric `index` that is unique within the run and stable across partial progress snapshots and the final result; use `(runId, index)` instead of row position to correlate single, counted parallel, and chain children.
@@ -54,6 +56,8 @@ Entries are bounded, current-session public display records with an opaque recon
54
56
 
55
57
  The DTO intentionally never exposes run, async, or tool IDs. Clients must ignore unknown fields and fall back to status text when the capability is absent.
56
58
 
59
+ `data.asyncSnapshot` is a separate bounded projection included on successful status replies when available. Its `runs[].id` contains the async run id; unlike the fleet DTO, it is not an opaque display key. Fleet keys remain opaque and must not be interpreted as run or async identifiers.
60
+
57
61
  ### Scope
58
62
 
59
63
  `pi.events` is in-process only. It does not reach separate Pi processes or child subagents; use the file lifecycle artifacts or `pi-intercom` for cross-process coordination.
@@ -129,7 +133,7 @@ updateExternalRun(ctx.sessionManager.getSessionId(), "dependency-review", {
129
133
  unregisterExternalRun(ctx.sessionManager.getSessionId(), "dependency-review");
130
134
  ```
131
135
 
132
- The API validates and caches bounded display fields when the caller registers or updates a job. FleetView reads that cache only. It does not poll caller code. `snapshotExternalRuns(sessionId)` and `listExternalRuns(sessionId)` return bounded current-session snapshots. By default, malformed cached records throw with the validation error. Display-only Fleet callers can pass `{ ignoreMalformed: true, onMalformedRecord }` to remove bad records and keep rendering with a programmatic diagnostic.
136
+ The API validates and caches bounded display fields when the caller registers or updates a job. FleetView reads that cache only. It does not poll caller code. `snapshotExternalRuns(sessionId)` and `listExternalRuns(sessionId)` return bounded current-session snapshots. Snapshots filter the session-qualified cache key before inspecting record fields; API-written records avoid repeated normalization through module-private provenance, while records replaced or mutated through the process-local registry are validated on demand. By default, malformed records for the requested session throw with the validation error. Display-only Fleet callers can pass `{ ignoreMalformed: true, onMalformedRecord }` to remove bad records and keep rendering with a programmatic diagnostic.
133
137
 
134
138
  External jobs are observational. The caller owns execution, persistence, cancellation, and result delivery. FleetView does not expose stop, steer, resume, cancel, or Herdr controls for them. Supplied report and transcript paths are shown as bounded text only; FleetView does not read arbitrary external paths.
135
139
 
@@ -229,6 +233,8 @@ Results:
229
233
  - Result mode is explicit. Text remains literal even when it looks like JSON. Structured mode returns the separately captured, schema-validated JSON value.
230
234
  - Terminal usage reports input, output, cache-read, cache-write, cost, turns, tool calls, and duration alongside the effective model and thinking level when known.
231
235
 
236
+ Live update events are bounded progress snapshots, not patches, so consumers should replace the prior snapshot rather than merge it as a delta. Structured delegation coalesces heartbeats whose delegation-visible progress and recent output are unchanged; a duration-only heartbeat therefore does not produce another update. The terminal response remains authoritative for the complete result, error, and final usage details.
237
+
232
238
  Bounds:
233
239
 
234
240
  - Schemas are capped at 64 KiB; tasks and returned text/structured values are capped at 1 MiB, with smaller bounds on identity/configuration strings and a maximum `timeoutMs` of 2,147,483,647.
@@ -279,7 +285,7 @@ Schedules created while a ceiling is active are rejected until durable schedule
279
285
 
280
286
  ## Background-work provider API
281
287
 
282
- Other Pi extensions can make their current-session jobs visible to `subagent_wait` through the process-local provider contract:
288
+ Other Pi extensions can make their current-session jobs visible to `bg_wait` through the process-local provider contract:
283
289
 
284
290
  ```ts
285
291
  import { registerBackgroundWorkProvider } from "pi-subagents/background-work";
@@ -287,8 +293,8 @@ import { registerBackgroundWorkProvider } from "pi-subagents/background-work";
287
293
  const dispose = registerBackgroundWorkProvider({
288
294
  name: "my-background-extension",
289
295
  wakeChannels: ["my-extension:job-finished"],
290
- listActiveWork: () => jobs
291
- .filter((job) => job.status === "running")
296
+ listActiveWork: (context) => jobs
297
+ .filter((job) => job.status === "running" && (!context || job.ownerSessionId === context.sessionId))
292
298
  .map((job) => ({ id: job.id, sessionId: job.ownerSessionId })),
293
299
  reconcile: ({ sessionId, nowMs }) => reconcileJobs(sessionId, nowMs),
294
300
  });
@@ -296,13 +302,14 @@ const dispose = registerBackgroundWorkProvider({
296
302
 
297
303
  Semantics:
298
304
 
299
- - Each item needs a stable provider-local ID and the exact Pi session ID that owns it. `subagent_wait` captures those identities rather than a count, so one job finishing while another starts still satisfies first-completion waits without losing the replacement.
305
+ - Each item needs a stable provider-local ID and the exact Pi session ID that owns it. `bg_wait` captures those identities rather than a count, so one job finishing while another starts still satisfies first-completion waits without losing the replacement.
306
+ - `listActiveWork` receives an optional `{ sessionId, nowMs }` context during snapshots. Providers can use `sessionId` to avoid scanning unrelated work; existing zero-argument `() => items` providers continue to work, and returned items are still validated and filtered to the exact requested session.
300
307
  - It filters snapshots to the active session, fails closed if a provider disappears while its work is tracked, and surfaces malformed snapshots or provider errors with provider context.
301
308
  - Wake channels only shorten polling; validated snapshots remain authoritative.
302
309
  - Providers share a registry through `Symbol.for("pi-subagents.background-work.v1")`, allowing independently loaded extension modules to meet in one Pi process.
303
310
  - Registration is reload-safe: a new provider with the same name replaces the old callback, and the old disposer cannot remove the replacement. Call the disposer during extension shutdown when possible.
304
311
 
305
- Child processes do not gain provider tools or extensions automatically. Add `subagent_wait` to the child agent's `tools` allowlist and load each provider through `extensions` or `subagentOnlyExtensions`. The parent's effective `waitTool` setting is serialized through foreground, async, resume, chain, parallel, and fanout launch paths; `PI_SUBAGENT_WAIT_TOOL_ENABLED` keeps precedence.
312
+ Child processes do not gain provider tools or extensions automatically. Add `bg_wait` to the child agent's `tools` allowlist and load each provider through `extensions` or `subagentOnlyExtensions`. The parent's effective `waitTool` setting is serialized through foreground, async, resume, chain, parallel, and fanout launch paths; `PI_SUBAGENT_WAIT_TOOL_ENABLED` keeps precedence.
306
313
 
307
314
  ## External job provider bridge
308
315
 
@@ -399,7 +406,7 @@ The API returns discriminated structured results with canonical project root, bi
399
406
 
400
407
  A host that embeds this extension owns whether completion wakes can be delivered at all.
401
408
 
402
- Ordinary async and foreground completion wakes use `registerSubagentNotify` and `sendCompletion`. They listen for completion events and deliver through `pi.sendMessage(..., { triggerTurn })`. Session shutdown stops the result watcher and disposes this completion notifier. `createWaitSubscriptionManager` is separate: it is the explicit non-blocking `subagent_wait` subscription path, not the ordinary completion wake path.
409
+ Ordinary async and foreground completion wakes use `registerSubagentNotify` and `sendCompletion`. They listen for completion events and deliver through `pi.sendMessage(..., { triggerTurn })`. Session shutdown stops the result watcher and disposes this completion notifier. `createWaitSubscriptionManager` is separate: it is the explicit non-blocking `bg_wait` subscription path for work without native notification, not the ordinary completion wake path.
403
410
 
404
411
  Detached children do not stop when the session does. They are the host process's children, not the session's, so the run keeps going, completes, and notifies nobody. What is lost is the notification, not the work.
405
412
 
package/docs/models.md CHANGED
@@ -100,7 +100,7 @@ A setup that works well in practice: route agents by task shape instead of runni
100
100
 
101
101
  The routing rule: use the capability tiers (1–3) when the task is well-scoped, and the intent tier (4) when scoping or judging is the task itself.
102
102
 
103
- Give tier-4 agents cross-provider `fallbackModels` so subscription usage limits degrade gracefully instead of failing the run. Fallback triggers on rate-limit and overload errors automatically:
103
+ Give tier-4 agents cross-provider `fallbackModels` so subscription usage limits degrade gracefully instead of failing the run. Fallback triggers on retryable provider/model failures such as rate-limit, overload, unavailable-model, and provider-reported timeout errors. The outer run-level `timeoutMs` / `maxRuntimeMs` deadline is terminal and does not start another fallback attempt:
104
104
 
105
105
  ```yaml
106
106
  ---
@@ -157,7 +157,7 @@ For a top-level async run, `details.asyncDir` points at that directory; the fina
157
157
 
158
158
  The result file is consumed and deleted once its completion notice is delivered. Before deletion, the watcher writes a versioned replay record under `<resultsDir>/completion-replay/<runId>.json` and a bounded output archive under `<resultsDir>/output-archives/<runId>.json`. Replay records expire with the completion deduplication window and are best-effort temporary state, not a permanent run ledger.
159
159
 
160
- `subagent_wait` surfaces a slim projection of each terminal payload it covered in its own tool-result `details.completions` — run identity, per-child agent/`runId`/success, artifact paths, and the bounded `archivePath`, without duplicating output text. It reads the replay when watcher delivery or a watcher restart has removed the one-shot result file and in-memory completion state is unavailable. Durable non-blocking wait subscriptions use the same replay in their delivered details. Workflow result files record each child's `runId` explicitly, since a workflow child's `artifactPaths` entry points at its saved output rather than the artifact files keyed by the id. Extensions observing `tool_result` events can read run and artifact identity from there instead of parsing the text summary.
160
+ `bg_wait` surfaces a slim projection of each terminal payload it covered in its own tool-result `details.completions` — run identity, per-child agent/`runId`/success, artifact paths, and the bounded `archivePath`, without duplicating output text. It reads the replay when watcher delivery or a watcher restart has removed the one-shot result file and in-memory completion state is unavailable. Durable non-blocking wait subscriptions use the same replay in their delivered details. Workflow result files record each child's `runId` explicitly, since a workflow child's `artifactPaths` entry points at its saved output rather than the artifact files keyed by the id. Extensions observing `tool_result` events can read run and artifact identity from there instead of parsing the text summary.
161
161
 
162
162
  Output archives reference an existing child output artifact or session file when one is available. For children without either file, the archive stores a per-child `result-tail` entry with `resultIndex`, bounded to 64 KiB per child, and records whether it was truncated. Replay and archive JSON use `version: 1`; consumers must ignore unknown fields.
163
163
 
@@ -4,12 +4,21 @@ Parameters and actions for the `subagent` tool. These are what the LLM passes wh
4
4
 
5
5
  ## Execution examples
6
6
 
7
- Chaining is code-driven through `workflowScript`. Use `await runs.run(...)` for sequential steps and `await runs.all([{ key, agent, task }, ...])` for ordinary parallel fanout. `runs.all` resolves to an ordered array, not a key map, so use indexes, destructuring, or `.map(...)`, not `results.<key>`. Do not read `.output` from an unawaited `runs.run` launch. Stored `runs.run` promises are only for the advanced rolling fanout pattern under [Workflow steering](#workflow-steering), where every promise is later observed with direct `await`, `Promise.race`, or `Promise.all`. Legacy top-level `chain`, `tasks`, and `parallel` inputs are not supported. Helper functions must be plain functions or explicit Promise chains. Nested `async function` helpers, async arrows, and async methods are rejected so child-launch tracking stays portable across Node and Bun. Host steps are similarly narrow: use `runs.host(key, { kind: "command", command, timeoutMs, output?, role?, provider? })`; there is no per-step `cwd`, and commands and relative output paths use the workflow `cwd`. Set `cwd` on the outer `subagent({...})` request instead, or put a trusted directory change in the command (for example, `cd /path/to/worktree && npm test`).
7
+ Chaining is code-driven through `workflowScript`. Use `await runs.run(...)` for sequential steps and `await runs.all([{ key, agent, task }, ...])` for ordinary parallel fanout. `runs.all` resolves to an ordered array, not a key map, so use indexes, destructuring, or `.map(...)`, not `results.<key>`. Do not read `.output` from an unawaited `runs.run` launch. Stored `runs.run` promises are only for the advanced rolling fanout pattern under [Workflow steering](#workflow-steering), where every promise is later observed with direct `await`, `Promise.race`, or `Promise.all`. Legacy top-level `chain`, `tasks`, and `parallel` inputs are not supported. Helper functions must be plain functions or explicit Promise chains. Nested `async function` helpers, async arrows, and async methods are rejected so child-launch tracking stays portable across Node and Bun. For permission-sensitive host calls, use an extension-owned named resource such as `{ workflow: "run-ci", args: { command: "npm test" } }`; raw public `workflowScript`/`workflowScriptPath` inputs have unknown resource provenance and cannot call `runs.host`. A resolved resource may internally use `runs.host(key, { kind: "command", command, timeoutMs, output?, role?, provider? })` within its authority ceiling; there is no per-step `cwd`, and commands and relative output paths use the workflow `cwd`. Set `cwd` on the outer `subagent({...})` request instead, or put a trusted directory change in the command (for example, `cd /path/to/worktree && npm test`).
8
8
 
9
9
  Use `{ action: "validate", workflowScript }` to check statically decidable syntax and structure without launching children. It returns `{ ok, errors }` and fails the tool call when `ok` is false. Dynamic keys and values remain valid because runtime-only cases are not guessed.
10
10
 
11
11
  Use `workflowScriptPath` instead of `workflowScript` to load the same JavaScript statement body from a file. The two fields are mutually exclusive. Relative paths resolve against the request `cwd`, and absolute paths pass through. The host reads the file before validation, scheduling, or sandbox execution. The workflow sandbox still has no filesystem access. Missing, unreadable, and empty files fail as file input errors.
12
12
 
13
+ For permission-extension interoperability, use one of the package-owned named resources with bounded `args` instead of caller-supplied workflow text:
14
+
15
+ ```js
16
+ { workflow: "review", args: { task: "Review the auth flow" } }
17
+ { workflow: "run-ci", args: { command: "npm test" } }
18
+ ```
19
+
20
+ The host resolves the script and authority internally and records bounded provenance in workflow details and receipts. Named resources cannot be combined with `agent`, `task`, `workflowScript`, or `workflowScriptPath`; user/project resource registries are not part of this first slice.
21
+
13
22
  ```js
14
23
  { workflowScriptPath: "workflows/review.js", cwd: "/path/to/project" }
15
24
  { action: "validate", workflowScriptPath: "workflows/review.js" }
@@ -99,8 +108,8 @@ The complete plain-JSON inventory is validated before the first launch (maximum
99
108
  | `async` | boolean | default-on | Background execution. Workflows default to background. `async:false` blocks the parent until completion. |
100
109
  | `chatProgress` | `auto \| off \| live-card` | `auto` | WorkflowScript chat projection. `auto` renders a live in-chat card only for watched foreground workflows in the same Git repository, including managed worktrees; it is off otherwise. Explicit `live-card` requires `async:false` and the same Git repository. Async workflows have no inline live card, so omit `chatProgress` or use `auto`/`off`; use `async:false` only when the parent must block. |
101
110
  | `isolation` | `none \| worktree` | - | Workflow child isolation. `none` runs in the shared cwd and does not need Git. `worktree` requires a managed Git worktree. Do not combine it with a contradictory `worktree` value. |
102
- | `timeoutMs` / `maxRuntimeMs` | number | config `timeoutMs`, else 30 min foreground / single-agent async | Optional run-level max runtime in milliseconds. When omitted, the global [`timeoutMs`](configuration.md#timeoutms) config provides the default; absent that, foreground and plain single-agent async runs fall back to 30 minutes, while composite async runs (chains, parallel tasks, workflows) stay unbounded at the top level. |
103
- | `toolTimeoutMs` | number | fast-tool default | Optional positive hard per-tool-call deadline in milliseconds. Precedence: call value → agent frontmatter → config → `PI_SUBAGENT_TOOL_TIMEOUT_MS`. The timer starts on `tool_execution_start`, clears on the matching `tool_execution_end`, and terminates the run with `timedOut: true` if the tool remains open. When omitted, known-fast built-in tools get a five-minute default; long-running tools get attention notices but no hard default. It never extends the run deadline; `contact_supervisor`, `intercom`, and `subagent_wait` are exempt. |
111
+ | `timeoutMs` / `maxRuntimeMs` | number | config `timeoutMs`, else 30 min foreground / single-agent async | Optional run-level max runtime in milliseconds. When omitted, the global [`timeoutMs`](configuration.md#timeoutms) config provides the default; absent that, foreground and plain single-agent async runs fall back to 30 minutes, while composite async runs (chains, parallel tasks, workflows) stay unbounded at the top level. Expiration of this run-level deadline is terminal and does not trigger `fallbackModels`. |
112
+ | `toolTimeoutMs` | number | fast-tool default | Optional positive hard per-tool-call deadline in milliseconds. Precedence: call value → agent frontmatter → config → `PI_SUBAGENT_TOOL_TIMEOUT_MS`. The timer starts on `tool_execution_start`, clears on the matching `tool_execution_end`, and terminates the run with `timedOut: true` if the tool remains open. When omitted, known-fast built-in tools get a five-minute default; long-running tools get attention notices but no hard default. It never extends the run deadline; `contact_supervisor`, `intercom`, and `bg_wait` are exempt. |
104
113
  | `toolBudget` | object | none | Optional child tool-call budget `{ soft?, hard, block? }`. At `soft` the child is nudged to finalize. After `hard`, configured tools are blocked; `block` defaults to `read`, `grep`, `find`, and `ls`, while `"*"` blocks every tool call. Final assistant text is never blocked. |
105
114
  | `usageBudget` | object | none | Optional root-only reported-usage budget `{ tokens?: { soft?, hard }, costUsd?: { soft?, hard } }`. Soft limits are status-only. Hard limits prevent later child launches after reported usage is reconciled; already-running children are not stopped and no reservations are made. |
106
115
  | `cwd` | string | runtime cwd | Override working directory. |
@@ -411,7 +420,7 @@ Acceptance provenance is stored separately from child prose. `evidenceStatus` pr
411
420
 
412
421
  ### The acceptance report
413
422
 
414
- For `attested` or stricter levels, the child prompt includes a standardized acceptance section and asks for a fenced `acceptance-report` JSON block.
423
+ For `attested` or stricter levels, the child prompt includes a standardized acceptance section and asks for a fenced `acceptance-report` JSON block. With `outputSchema`, set `acceptance.report: "on"` to require the same report in the final `structured_output` call, or `"off"` to keep the fenced-report path. Omitting `report` preserves the default behavior. Runs without `outputSchema` never gain a standalone structured-output tool from this option.
415
424
 
416
425
  The parser canonicalizes known enum synonyms, snake_case report keys and wrappers, underscore fence tags, unambiguous scalar arrays, string booleans, and criterion-id separators. Unknown or ambiguous keys and enum values fail with field-level diagnostics. Explicit empty `changedFiles` and `testsAddedOrUpdated` arrays are recorded as not applicable; missing fields and empty required command or validation evidence still fail.
417
426
 
package/docs/workflows.md CHANGED
@@ -57,6 +57,17 @@ subagent({ action: "validate", workflowScriptPath: "workflows/review.js" });
57
57
 
58
58
  The fields are mutually exclusive. Relative paths resolve against the request `cwd`; absolute paths pass through. The host reads the file before validation, schedule creation, or workflow sandbox execution. The sandbox still has no filesystem access. Missing, unreadable, and empty files return file input errors instead of script syntax errors.
59
59
 
60
+ ### Named workflow resources for permission extensions
61
+
62
+ Use a named workflow resource when a permission or policy extension needs to distinguish extension-resolved workflow content from raw model-authored scripts:
63
+
64
+ ```js
65
+ subagent({ workflow: "review", args: { task: "Review the change" } });
66
+ subagent({ workflow: "run-ci", args: { command: "npm test" } });
67
+ ```
68
+
69
+ The host resolves the name and validates bounded plain-JSON `args` before starting the workflow. Resource provenance is recorded in workflow details and receipts for downstream permission/policy checks. Resource authority is not caller-supplied: `runs.host` is available only when the resolved resource explicitly grants the requested host key and command. Inline `workflowScript` and `workflowScriptPath` remain raw, unknown-provenance inputs, so their `runs.host` calls are unavailable through the public execution boundary. Named resources cannot be combined with `agent`, `task`, `workflowScript`, or `workflowScriptPath`; this first slice ships only the package-owned `review` and `run-ci` resources, not a user/project resource registry.
70
+
60
71
  ### Opt-in bounded workflows
61
72
 
62
73
  Composite workflows have no default parent deadline. Add bounds only when the workflow contract calls for them:
@@ -162,23 +173,13 @@ The board is bounded and contains only lane/stage keys, state, success, retained
162
173
 
163
174
  ### Host command steps
164
175
 
165
- Use `runs.host(...)` when the operator wants one non-interactive command to be part of the workflow evidence instead of a child-agent run:
176
+ Use the named `run-ci` resource when a permission/policy extension needs to admit one supported non-interactive command as workflow evidence instead of a child-agent run:
166
177
 
167
178
  ```js
168
- subagent({ workflowScript: `
169
- const tests = await runs.host("unit-tests", {
170
- kind: "command",
171
- command: "npm run test:unit",
172
- timeoutMs: 120000,
173
- output: "reports/unit-tests.log",
174
- role: "ci",
175
- provider: "local"
176
- });
177
- return { state: tests.state, exitCode: tests.exitCode, outputPath: tests.outputPath };
178
- ` });
179
+ subagent({ workflow: "run-ci", args: { command: "npm test", timeoutMs: 120000 } });
179
180
  ```
180
181
 
181
- The first version supports only `kind: "command"`. `command` and `timeoutMs` are required; `output` must be a relative path without traversal. `role` may be `ci` or `gate`, and `provider` is display metadata only. **There is no per-step `cwd` field:** the command and relative output path use the workflow cwd. Set `cwd` on the outer `subagent({...})` request when the workflow should run in another directory, or put a trusted directory change in the command (for example, `cd /path/to/worktree && npm test`) when only that step differs. The command has no stdin, receives the workflow cwd, and must be awaited or returned. Stdout, stderr, and the saved log are bounded. A nonzero exit, timeout, abort, or output-write failure fails the workflow. Async status and terminal receipts store the bounded host-step state; renderers do not run commands or read command output.
182
+ The first named resource version supports only `npm test` and `npm run typecheck`, with bounded timeout values. Its resolved script uses `runs.host("ci", ...)` and the resource authority admits only the selected command. **There is no per-step `cwd` field:** the command and relative output path use the workflow cwd. Set `cwd` on the outer `subagent({...})` request when the workflow should run in another directory. The command has no stdin, receives the workflow cwd, and must be awaited or returned. Stdout, stderr, and the saved log are bounded. A nonzero exit, timeout, abort, or output-write failure fails the workflow. Async status and terminal receipts store the bounded host-step state; renderers do not run commands or read command output.
182
183
 
183
184
  ### Steering a workflow child
184
185
 
package/install.mjs CHANGED
@@ -87,7 +87,7 @@ if (fs.existsSync(EXTENSION_DIR)) {
87
87
  console.log(`
88
88
  The extension is now available in pi. Tools added:
89
89
  • subagent - Delegate tasks to agents and inspect run status
90
- subagent_wait - Block until background subagent runs finish (delivers their results)
90
+ bg_wait - Wait for background/provider/detached work without native completion notifications
91
91
 
92
92
  Documentation: ${EXTENSION_DIR}/README.md
93
93
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-subagents",
3
- "version": "0.60.0",
3
+ "version": "0.62.0",
4
4
  "description": "Pi extension for single-agent delegation and scripted multi-agent workflows",
5
5
  "author": "Nico Bailon",
6
6
  "license": "MIT",
@@ -55,10 +55,12 @@ block. Final reviews, validation gates, oracle checks, and publication checks
55
55
  stay async.
56
56
 
57
57
  In an ordinary interactive session, yield after launching or triaging useful
58
- async lanes and let Pi wake the parent on completion; do not call blocking
59
- `subagent_wait()` merely because a child is active. Use blocking
60
- `subagent_wait()` only when a headless/run-to-completion contract or a required
61
- same-turn artifact makes the result necessary before this turn ends. For
58
+ async lanes and let Pi wake the parent on completion; ordinary async subagents
59
+ already have native completion notifications, so do not call `bg_wait()` merely
60
+ because a child is active. Use blocking `bg_wait()` only for provider,
61
+ detached, or other background work without a native notification when a
62
+ headless/run-to-completion contract or a required same-turn artifact makes the
63
+ result necessary before this turn ends. For
62
64
  “continue/orchestrate/work until done,” keep the lane board moving while a safe
63
65
  immediate action remains; if only async lanes are running, record the revisit
64
66
  trigger and yield.
@@ -21,7 +21,7 @@ This file is a detailed reference loaded from `skills/pi-subagents/SKILL.md`.
21
21
  become second decision-makers.
22
22
  - **Respect the fixed authority policy.** `authorityPolicy` is a small `auto` / `confirm` / `forbid` map for supported operational actions. Worktree discard, destructive cleanup, and spawn-budget grants default to confirmation; stop, steer, and schedule creation remain automatic. Use `worktree.discard` with the durable `handoffPath`; confirm-required actions refuse safely without an interactive UI and retained paths include manual Git recovery commands.
23
23
 
24
- Runtime config can change orchestration behavior. `intercomBridge.resultDelivery: false` disables only external acknowledged grouped-result delivery when native parent notifications own completion; supervisor asks/progress stay active, and enabled transport failures are still reported. `asyncByDefault` and `forceTopLevelAsync` affect whether launches detach; `waitTool` can make direct `subagent_wait()` calls return immediately while headless auto-drain remains active, and its effective value is propagated to child runtimes; `globalConcurrencyLimit` bounds concurrent fanout, while a positive `maxSubagentSpawnsPerSession` optionally caps cumulative launches (`0` or unset is unlimited). Status and doctor report the budget; static work preflights declared capacity; only the settled root interactive parent can use `grant-spawn-budget` after native confirmation, with total grants bounded by the original cap. Compaction does not reset usage or grants; `singleRunOutputBaseDir` and `worktreeBaseDir` route outputs and worktrees; `completionBatch` groups async notifications. `artifactDir` is `session` (default), `project`, or `temp` and chooses where subagent artifacts are stored. Set `asyncWidget: false` to hide the above-editor background-run widget when a companion footer or dashboard owns that space (fleet inspector remains available). Per-run `artifacts: false` disables artifact capture for that launch. Async status and result artifacts include `lifecycleArtifactVersion` and fields such as `workflowGraph`, `steps`, `results`, `totalTokens`, `totalCost`, `turnCount`, `toolCount`, and nested `children`. Child protocol failures expose a structured `protocolError`; `protocol_output_limit` means a child emitted a JSONL line above the 16 MiB live-parser cap. Prefer these artifacts and `status` views over scraping terminal output.
24
+ Runtime config can change orchestration behavior. `intercomBridge.resultDelivery: false` disables only external acknowledged grouped-result delivery when native parent notifications own completion; supervisor asks/progress stay active, and enabled transport failures are still reported. `asyncByDefault` and `forceTopLevelAsync` affect whether launches detach; `waitTool` can make direct `bg_wait()` calls return immediately while headless auto-drain remains active, and its effective value is propagated to child runtimes; `globalConcurrencyLimit` bounds concurrent fanout, while a positive `maxSubagentSpawnsPerSession` optionally caps cumulative launches (`0` or unset is unlimited). Status and doctor report the budget; static work preflights declared capacity; only the settled root interactive parent can use `grant-spawn-budget` after native confirmation, with total grants bounded by the original cap. Compaction does not reset usage or grants; `singleRunOutputBaseDir` and `worktreeBaseDir` route outputs and worktrees; `completionBatch` groups async notifications. `artifactDir` is `session` (default), `project`, or `temp` and chooses where subagent artifacts are stored. Set `asyncWidget: false` to hide the above-editor background-run widget when a companion footer or dashboard owns that space (fleet inspector remains available). Per-run `artifacts: false` disables artifact capture for that launch. Async status and result artifacts include `lifecycleArtifactVersion` and fields such as `workflowGraph`, `steps`, `results`, `totalTokens`, `totalCost`, `turnCount`, `toolCount`, and nested `children`. Child protocol failures expose a structured `protocolError`; `protocol_output_limit` means a child emitted a JSONL line above the 16 MiB live-parser cap. Prefer these artifacts and `status` views over scraping terminal output.
25
25
 
26
26
  ### Keep report artifacts out of the repository root
27
27
 
@@ -140,17 +140,45 @@ Async does not mean parallel writes. Do not edit the same active worktree while
140
140
  Do not end your turn immediately after launching an async child if you promised to keep working. Continue the local inspection, synthesis, or validation prep, then check the async run when its result is needed. If no safe independent work remains, return control and let Pi wake the session; do not convert the child to foreground.
141
141
 
142
142
  In an ordinary interactive chat, normally return control after launching or
143
- triaging useful async work and let Pi wake the session on completion; do not
144
- call `subagent_wait()` merely to wait. A run-to-completion user request is not
145
- by itself a reason to use foreground children. Override the normal yield-and-
146
- wake flow only when this exact turn cannot safely end without the result, such
147
- as a headless provider flow or a skill contract that must produce a same-turn
148
- artifact. Use `subagent_wait()`, not `async:false`, for that current-turn
149
- dependency. Never substitute sleep or status-polling loops.
150
-
151
- `subagent_wait()` returns when the next initially active async run or registered provider item finishes or a subagent needs attention. Use `subagent_wait({ all: true })` for all work active at call time, `subagent_wait({ id: "..." })` for one async or remembered detached foreground run, and `subagent_wait({ timeoutMs })` to cap the block; active work keeps running if it elapses. `subagent_wait({ stopOnAttention: false })` keeps a blocking wait through idle or long-thinking attention, but supervisor/contact requests still stop it. In a long-lived interactive parent session, use `subagent_wait({ id: "...", nonBlocking: true })` to resolve the prefix to one exact run, persist an armed subscription, return immediately, and wake later on completion, failure, attention, reconciliation failure, or timeout. Ordinary status lists armed subscriptions separately from active children. This differs from disabling `waitTool`, which returns immediately without arming a future wake. If a foreground child detaches for supervisor coordination, reply first, then wait on its id; do not resume or launch a replacement while it remains detached. Headless sessions also auto-drain exact current-session work at `agent_end` as a final safeguard.
152
-
153
- Providers are discovered through the `pi-subagents/background-work` registry and must expose a stable item id and owning session id. Load a provider through the child’s `extensions` or `subagentOnlyExtensions` and allow `subagent_wait` in its tools. For non-interactive fleets, launch N workers, wait for the next completion, react, and replace as needed; use `all: true` only when intentionally draining the fleet. If `PI_SUBAGENT_WAIT_TOOL_ENABLED` disables blocking, direct waits return immediately, but headless `agent_end` auto-drain still surfaces provider, reconciliation, or timeout failures.
143
+ triaging useful async work and let Pi wake the session on completion; ordinary
144
+ async subagents already have native completion notifications, so do not call
145
+ `bg_wait()` merely to wait. A run-to-completion user request is not by itself a
146
+ reason to use foreground children. Override the normal yield-and-wake flow
147
+ only when this exact turn cannot safely end without the result of provider,
148
+ detached, or other background work without a native notification, such as a
149
+ headless provider flow or a skill contract that must produce a same-turn
150
+ artifact. Use `bg_wait()`, not `async:false`, for that current-turn dependency.
151
+ Never substitute sleep or status-polling loops.
152
+
153
+ `bg_wait()` returns when the next initially active async run or registered
154
+ provider item finishes or a subagent needs attention. Use it for background,
155
+ provider, or detached work without native completion delivery; ordinary async
156
+ subagent runs notify the parent automatically. Use `bg_wait({ all: true })` for
157
+ all work active at call time, `bg_wait({ id: "..." })` for one async or
158
+ remembered detached foreground run, and `bg_wait({ timeoutMs })` to cap the
159
+ block; active work keeps running if it elapses. `bg_wait({ stopOnAttention:
160
+ false })` keeps a blocking wait through idle or long-thinking attention, but
161
+ supervisor/contact requests still stop it. In a long-lived interactive parent
162
+ session, use `bg_wait({ id: "...", nonBlocking: true })` only for a known
163
+ detached or otherwise non-notifying run to resolve the prefix to one exact run,
164
+ persist an armed subscription, return immediately, and wake later on
165
+ completion, failure, attention, reconciliation failure, or timeout. Ordinary
166
+ status lists armed subscriptions separately from active children. This differs
167
+ from disabling `waitTool`, which returns immediately without arming a future
168
+ wake. If a foreground child detaches for supervisor coordination, reply first,
169
+ then wait on its id; do not resume or launch a replacement while it remains
170
+ detached. Headless sessions also auto-drain exact current-session work at
171
+ `agent_end` as a final safeguard.
172
+
173
+ Providers are discovered through the `pi-subagents/background-work` registry and
174
+ must expose a stable item id and owning session id. Load a provider through the
175
+ child’s `extensions` or `subagentOnlyExtensions` and allow `bg_wait` in its
176
+ tools. For
177
+ non-interactive fleets, launch N workers, wait for the next completion, react,
178
+ and replace as needed; use `all: true` only when intentionally draining the
179
+ fleet. If `PI_SUBAGENT_WAIT_TOOL_ENABLED` disables blocking, direct waits return
180
+ immediately, but headless `agent_end` auto-drain still surfaces provider,
181
+ reconciliation, or timeout failures.
154
182
 
155
183
  ```typescript
156
184
  subagent({
@@ -455,6 +483,8 @@ history as a baseline contract.
455
483
 
456
484
  Use `oracle` as a smart-friend escalation when the parent needs help with trajectory rather than diff inspection: architectural boundaries, model capability routing, merge conflicts, reviewer disagreement, context drift after long work, a worker about to invent a pattern, or fixes that require product/scope tradeoffs. Ask broad questions when the right concern is unclear, and let `oracle` point out missing context or files the parent should inspect before asking again. Keep `oracle` advisory unless it has been explicitly assigned the single writer role.
457
485
 
486
+ Do not use `oracle` or Sol-high models to satisfy routine fresh-review gates, ordinary follow-up reviews, or ordinary performance crit passes. Use the `reviewer` role for those reviews, then escalate only when normal review/bot/CI evidence exposes an unresolved invariant, root-cause, model-routing, or product-tradeoff question.
487
+
458
488
  ## Subagent + Intercom Coordination
459
489
 
460
490
  `pi-subagents` includes native supervisor coordination. Child agents can use `contact_supervisor` to ask the exact parent session that spawned them; messages are scoped by parent session id and should not appear in other Pi sessions. Parents inspect or reply with `subagent_supervisor`. This path does not require `pi-intercom`.
@@ -34,7 +34,7 @@ While one lane waits, run safe independent preparation, validation, or fresh rea
34
34
 
35
35
  In an ordinary interactive session, completion wakes the parent; after useful
36
36
  async lanes are launched or triaged, yield rather than use
37
- `subagent_wait({ all: true })` as a barrier. “Continue/orchestrate/work until
37
+ `bg_wait({ all: true })` as a barrier. “Continue/orchestrate/work until
38
38
  done” means keep the board moving while safe immediate work remains. If only
39
39
  async lanes are running, record the revisit trigger and yield.
40
40
 
@@ -9,7 +9,7 @@ Parent extensions may register a session-scoped, out-of-band ceiling through `pi
9
9
  ## When to Use
10
10
 
11
11
  - **Complex work orchestration**: keep the parent on its ordinary strong default model. Delegate only when another child materially improves evidence, independent review, or isolated execution; omission failures are cheaper than unnecessary commissions. For hard orchestration or root-cause questions, use a top-reasoning model only as a bounded read-only critic/oracle escalation, never as an autonomous root. Complex means the task has multiple moving parts, unclear acceptance, cross-cutting code, meaningful user-visible impact, expensive or irreversible validation, broad review surface, or the user asks for orchestration. Lightweight one-off delegation can stay lightweight.
12
- - **Advisory review**: use fresh-context `reviewer` agents for adversarial code review, or fork to `oracle` when inherited decisions and drift matter
12
+ - **Advisory review**: use fresh-context `reviewer` agents for adversarial code review; fork to `oracle` only for rare escalation where inherited decisions, drift, model routing, root cause, or hard tradeoffs matter
13
13
  - **Implementation handoff**: have `oracle` advise, then `worker` implement only after an approved direction
14
14
  - **Recon and planning**: use `scout`, then write a plan when needed
15
15
  - **Parallel exploration**: run multiple non-conflicting tasks concurrently
@@ -178,8 +178,8 @@ and user/project agents override builtins with the same name.
178
178
  | `reviewer` | Review specialist | strong reviewer tier; high thinking for serious reviews | Default recipes are review-only; tools include edit/write when a fix pass is explicit |
179
179
  | `researcher` | Web research brief generator | inherits configured default | Writes `research.md` |
180
180
  | `delegate` | Lightweight generic delegate | inherits configured default | No fixed output; generic delegated work |
181
- | `oracle` | Decision-consistency advisory review | top-reasoning critic tier, bounded read-only; high thinking escalation only | Advisory review, intercom coordination |
182
- | `advisor` | Compatibility alias for `oracle` | top-reasoning critic tier, bounded read-only; high thinking escalation only | Same advisory role as `oracle` |
181
+ | `oracle` | Rare hard-decision/root-cause escalation | top-reasoning critic tier, bounded read-only; high thinking escalation only | Advisory trajectory review, not routine code review |
182
+ | `advisor` | Compatibility alias for `oracle` | top-reasoning critic tier, bounded read-only; high thinking escalation only | Same advisory escalation role as `oracle` |
183
183
 
184
184
  Builtin `worker` and `delegate` use strict tool allowlists and do not inherit ambient parent extension tools. To give a child an extension tool, name it in `tools` and load its provider via `extensions`, a path-like `tools` entry, or `subagentOnlyExtensions`. Custom agents without an `extensions` field follow `subagents.defaultExtensions` when set.
185
185
 
@@ -279,7 +279,7 @@ agent with the same name only when you want a substantially different agent.
279
279
 
280
280
  ### Recommended model tiering (optional)
281
281
 
282
- Keep the parent/orchestrator on the ordinary strong default model because omission failures are cheaper than unnecessary commissions. Route workers and scouts to a fast, capable worker tier, and keep serious reviews on the strong tier at high thinking. Use a top-reasoning model only for bounded, read-only critic/oracle/root-cause audits; critic-tier high thinking is escalation-only and never an autonomous root. Explicit parent/user model policy wins over these recommendations.
282
+ Keep the parent/orchestrator on the ordinary strong default model because omission failures are cheaper than unnecessary commissions. Route workers and scouts to a fast, capable worker tier, and keep serious reviews on the strong reviewer tier at high thinking. Do not use `oracle` or a top-reasoning model as the routine fresh-review default. Use that tier only for bounded, read-only critic/oracle/root-cause audits after ordinary review, CI, bot, or source evidence is insufficient; critic-tier high thinking is escalation-only and never an autonomous root. Explicit parent/user model policy wins over these recommendations.
283
283
 
284
284
  Examples are illustrative, not requirements. Map these tiers to concrete models in user/project settings or a profile. A non-OpenAI setup should choose comparable available models by capability.
285
285
 
@@ -25,7 +25,7 @@ Skip review ceremony for trivial wording, renames, or local-only probes when dir
25
25
  | Possible over-scope or needless complexity | same-writer challenge before fresh review |
26
26
  | Material design tradeoff | council mode |
27
27
 
28
- Reviewers are fresh-context by default. Forked reviewers are for parent-history, drift, or prior-decision evidence.
28
+ Reviewers are fresh-context by default. Use the ordinary `reviewer` role for routine code review. Forked oracle/advisor runs are escalation-only for parent-history, drift, root-cause, model-routing, or hard tradeoff evidence.
29
29
 
30
30
  ## Finding disposition
31
31