pi-subagents 0.24.3 → 0.25.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 (40) hide show
  1. package/CHANGELOG.md +26 -5
  2. package/README.md +19 -11
  3. package/package.json +4 -8
  4. package/prompts/review-loop.md +1 -1
  5. package/skills/pi-subagents/SKILL.md +46 -10
  6. package/src/agents/agent-management.ts +5 -0
  7. package/src/agents/agent-serializer.ts +2 -0
  8. package/src/agents/agents.ts +30 -6
  9. package/src/agents/skills.ts +25 -23
  10. package/src/extension/config.ts +16 -0
  11. package/src/extension/fanout-child.ts +170 -0
  12. package/src/extension/index.ts +13 -25
  13. package/src/intercom/intercom-bridge.ts +2 -1
  14. package/src/intercom/result-intercom.ts +108 -0
  15. package/src/runs/background/async-execution.ts +107 -7
  16. package/src/runs/background/async-job-tracker.ts +57 -14
  17. package/src/runs/background/async-resume.ts +28 -15
  18. package/src/runs/background/async-status.ts +60 -30
  19. package/src/runs/background/result-watcher.ts +111 -54
  20. package/src/runs/background/run-id-resolver.ts +83 -0
  21. package/src/runs/background/run-status.ts +79 -3
  22. package/src/runs/background/stale-run-reconciler.ts +46 -1
  23. package/src/runs/background/subagent-runner.ts +66 -18
  24. package/src/runs/foreground/chain-execution.ts +6 -0
  25. package/src/runs/foreground/execution.ts +21 -5
  26. package/src/runs/foreground/subagent-executor.ts +314 -18
  27. package/src/runs/shared/completion-guard.ts +23 -1
  28. package/src/runs/shared/mcp-direct-tool-allowlist.ts +365 -0
  29. package/src/runs/shared/nested-events.ts +819 -0
  30. package/src/runs/shared/nested-path.ts +52 -0
  31. package/src/runs/shared/nested-render.ts +115 -0
  32. package/src/runs/shared/parallel-utils.ts +1 -0
  33. package/src/runs/shared/pi-args.ts +67 -5
  34. package/src/runs/shared/run-history.ts +12 -7
  35. package/src/runs/shared/single-output.ts +12 -2
  36. package/src/runs/shared/subagent-prompt-runtime.ts +25 -5
  37. package/src/shared/artifacts.ts +2 -2
  38. package/src/shared/types.ts +95 -0
  39. package/src/shared/utils.ts +11 -1
  40. package/src/tui/render.ts +254 -153
package/CHANGELOG.md CHANGED
@@ -1,19 +1,40 @@
1
1
  # Changelog
2
2
 
3
- ## [0.24.3] - 2026-05-14
3
+ ## [Unreleased]
4
+
5
+ ## [0.25.0] - 2026-05-21
4
6
 
5
7
  ### Added
6
- - Show provider-free model and thinking labels in async subagent widgets and status views.
7
- - Added a packaged `/review-loop` prompt for parent-controlled worker, fresh-reviewer, and fix-worker cycles that can run as an initial async chain or as follow-up subagent runs after async worker completions, stopping when reviewers find no fixes worth doing now or the review-round cap is reached.
8
+ - Allow child agents whose resolved builtin tools explicitly include `subagent` to run child-safe nested fanout, with parent-visible nested status trees and nested `status`/`interrupt`/`resume` by id.
8
9
 
9
10
  ### Fixed
10
- - Let `async: true` chain tool calls run in the background when `clarify` is omitted, and avoid showing the async badge for explicit foreground clarify runs.
11
+ - Preserve compact nested child summaries in grouped result/intercom payloads and async completion metadata before ordinary result files are processed and deleted.
12
+ - Keep async result files retryable when nested registry enrichment temporarily fails, instead of marking them seen before a successful delivery pass.
13
+ - Require an explicit id for child-safe nested `status` when no local foreground run is active, preventing fanout children from listing unrelated top-level async runs.
14
+ - Keep fanout child control inbox polling alive across transient filesystem errors, and retain control requests for retry when control-result writes fail.
15
+ - Share nested path/env sanitization between child launch arguments and nested event projection.
11
16
 
12
- ## [Unreleased]
17
+ ## [0.24.4] - 2026-05-20
18
+
19
+ ### Fixed
20
+ - Treat provider-coerced single-run `output: "false"` the same as boolean `false`, preventing literal `false` output files in foreground and async runs.
21
+ - Include selected direct MCP tool names in explicit child `--tools` allowlists when metadata cache/config resolution is available.
22
+ - Honor `PI_CODING_AGENT_DIR` for runtime config, agent/chain/settings discovery, skills, run history, artifact cleanup, and intercom defaults.
23
+ - Hide nested child Pi process windows on Windows for both foreground and background subagent runs.
24
+ - Avoid completion-guard false positives for declared read-only agents, and add `completionGuard: false` for bash-enabled non-implementation agents that should not be required to edit files.
25
+ - Skip empty or whitespace-only assistant text parts when selecting subagent final output, so later meaningful text in the same or earlier assistant message is not masked.
26
+ - Declare `@earendil-works/pi-tui` as a runtime dependency so packaged installs can load the extension without relying on dev dependencies or optional peers.
27
+ - Treat recovered intermediate child tool/provider errors as successful when a later clean final assistant response is emitted, preventing false failed subagent results.
28
+ - Use progress-driven spinner frames in subagent result rows and async widgets, avoiding timer-driven off-screen redraw flicker in small terminals.
29
+
30
+ ## [0.24.3] - 2026-05-14
13
31
 
14
32
  ### Added
33
+ - Show provider-free model and thinking labels in async subagent widgets and status views.
34
+ - Added a packaged `/review-loop` prompt for parent-controlled worker, fresh-reviewer, and fix-worker cycles that can run as an initial async chain or as follow-up subagent runs after async worker completions, stopping when reviewers find no fixes worth doing now or the review-round cap is reached.
15
35
 
16
36
  ### Fixed
37
+ - Let `async: true` chain tool calls run in the background when `clarify` is omitted, and avoid showing the async badge for explicit foreground clarify runs.
17
38
 
18
39
  ## [0.24.2] - 2026-05-10
19
40
 
package/README.md CHANGED
@@ -149,7 +149,7 @@ Foreground runs stream progress in the conversation while they run.
149
149
 
150
150
  Background runs keep working after control returns to you. Inspect active runs with `subagent({ action: "status" })`, or a specific run with `subagent({ action: "status", id: "..." })`.
151
151
 
152
- They also show a compact async widget and send completion notifications. Parallel background runs show per-agent progress instead of fake chain steps. Chains with parallel groups keep their grouped shape in progress and results, so failed or paused agents stay visible next to completed ones.
152
+ They also show a compact async widget and send completion notifications. Parallel background runs show per-agent progress instead of fake chain steps. Chains with parallel groups keep their grouped shape in progress and results, so failed or paused agents stay visible next to completed ones. When a child is explicitly allowed to fan out with `tools: subagent`, its nested runs appear under that parent child in the main status tree instead of being hidden inside the child process.
153
153
 
154
154
  You can also ask naturally:
155
155
 
@@ -181,7 +181,7 @@ Use the optional prompt shortcuts below when you want the pattern to be repeatab
181
181
 
182
182
  Packaged `planner`, `worker`, and `oracle` default to forked context when a launch omits `context`; pass `context: "fresh"` when you intentionally want a fresh child run.
183
183
 
184
- Child-safety boundaries are enforced at runtime. Spawned child sessions do not register the `subagent` tool, do not receive the bundled `pi-subagents` skill, and receive explicit boundary instructions that they are not the parent orchestrator and must not propose or run subagents. Forked child context filtering also removes parent-only subagent artifacts (including old hidden orchestration-instruction messages, slash/status/control messages, and prior parent `subagent` tool-call/tool-result history) while preserving ordinary prose and unrelated tool calls/results.
184
+ Child-safety boundaries are enforced at runtime. Spawned child sessions do not receive the bundled `pi-subagents` skill, and forked child context filtering removes parent-only subagent artifacts (including old hidden orchestration-instruction messages, slash/status/control messages, and prior parent `subagent` tool-call/tool-result history) while preserving ordinary prose and unrelated tool calls/results. By default, children do not register the `subagent` tool and receive boundary instructions that they are not the parent orchestrator and must not propose or run subagents. The explicit exception is an agent whose resolved builtin `tools` includes `subagent`; that child gets a child-safe `subagent` tool for the fanout work the parent assigned, still bounded by `maxSubagentDepth`.
185
185
 
186
186
  ## Optional shortcuts
187
187
 
@@ -223,7 +223,7 @@ The child can use one dedicated coordination tool:
223
223
 
224
224
  - `contact_supervisor`: the child contacts the parent/supervisor session that delegated the task. Use `reason: "need_decision"` for blocking decisions or clarification, and `reason: "progress_update"` for short non-blocking updates when a discovery changes the plan. Do not ask for clarification when the only conflict is review-only/no-edit versus progress-writing or artifact-writing instructions; no-edit wins.
225
225
 
226
- Child-side routine completion handoffs are still not expected. With the intercom bridge active, parent-side `pi-subagents` sends grouped completion results through `pi-intercom`: one grouped message per foreground parent `subagent` run and one per completed async result file. Acknowledged foreground delivery returns a compact receipt with artifact/session paths; if unacknowledged, the normal full output is preserved. Grouped messages include child intercom targets and full child summaries.
226
+ Child-side routine completion handoffs are still not expected. With the intercom bridge active, parent-side `pi-subagents` sends grouped completion results through `pi-intercom`: one grouped message per foreground parent `subagent` run and one per completed async result file. Acknowledged foreground delivery returns a compact receipt with artifact/session paths; if unacknowledged, the normal full output is preserved. Grouped messages include child intercom targets, full child summaries, and compact nested child summaries under the parent child that launched them.
227
227
 
228
228
  If a child appears stalled, needs-attention notices can show up in the parent session with useful next actions, such as checking `subagent({ action: "status" })`, interrupting the run, or nudging the child.
229
229
 
@@ -433,6 +433,7 @@ skills: safe-bash, chrome-devtools
433
433
  output: context.md
434
434
  defaultReads: context.md
435
435
  defaultProgress: true
436
+ completionGuard: false
436
437
  interactive: true
437
438
  maxSubagentDepth: 1
438
439
  ---
@@ -458,20 +459,22 @@ Important fields:
458
459
  | `output` | Default single-agent output file. |
459
460
  | `defaultReads` | Files to read before running in chain/parallel behavior. |
460
461
  | `defaultProgress` | Maintain `progress.md`. |
462
+ | `completionGuard` | Set `false` only for non-implementation agents that may mention implementation words while using mutation-capable tools such as `bash`. |
461
463
  | `interactive` | Parsed for compatibility but not enforced in v1. |
462
464
  | `maxSubagentDepth` | Tightens nested delegation for this agent’s children. |
463
465
 
464
466
  ### Tool and extension selection
465
467
 
466
- If `tools` is omitted, `pi-subagents` does not pass `--tools`, so the child gets Pi’s normal builtin tools. If `tools` is present, regular tool names become an explicit allowlist. `mcp:` entries are split out and forwarded as direct MCP selections. Path-like `tools` entries, such as extension paths or `.ts`/`.js` files, are treated as tool-extension paths rather than builtin tool names.
468
+ If `tools` is omitted, `pi-subagents` does not pass `--tools`, so the child gets Pi’s normal builtin tools. If `tools` is present, regular tool names become an explicit allowlist. `mcp:` entries are split out and forwarded as direct MCP selections. Path-like `tools` entries, such as extension paths or `.ts`/`.js` files, are treated as tool-extension paths rather than builtin tool names. Agents that declare only known read-only builtin tools skip the implementation completion guard, but `bash`, unknown tools, and MCP tools stay mutation-capable. Use `completionGuard: false` for bash-enabled validators or advisors that should never be judged as implementation agents.
467
469
 
468
470
  Examples:
469
471
 
470
472
  - `tools` omitted and `extensions` omitted: normal builtins and normal extensions.
471
473
  - `tools: mcp:chrome-devtools`: normal builtins plus direct Chrome DevTools MCP tools.
472
474
  - `tools: read, bash, mcp:chrome-devtools`: only `read` and `bash` as builtins, plus direct Chrome DevTools MCP tools.
475
+ - `tools: subagent, read`: a child-safe `subagent` tool is available inside that child so it can run explicitly assigned nested fanout.
473
476
 
474
- Direct MCP tools require [pi-mcp-adapter](https://github.com/nicobailon/pi-mcp-adapter). Subagents only receive direct MCP tools when `mcp:` entries are listed in their frontmatter; global `directTools: true` in `mcp.json` is not enough by itself. The generic `mcp` proxy tool can still be used for discovery when available. The adapter caches tool metadata at startup, so after connecting a new MCP server for the first time, restart Pi before relying on direct tools.
477
+ Direct MCP tools require [pi-mcp-adapter](https://github.com/nicobailon/pi-mcp-adapter). Subagents only receive direct MCP tools when `mcp:` entries are listed in their frontmatter; global `directTools: true` in `mcp.json` is not enough by itself. The generic `mcp` proxy tool can still be used for discovery when available. The adapter caches tool metadata at startup, so after connecting a new MCP server for the first time, restart Pi before relying on direct tools. An `mcp:` entry named `subagent` does not authorize nested fanout; only the builtin `subagent` tool name does.
475
478
 
476
479
  `extensions` controls child extension loading:
477
480
 
@@ -591,7 +594,7 @@ What the bundled skill covers:
591
594
  - **Delegation patterns**: when to launch which agent, whether to use single, parallel, chain, or async mode, and whether to use fresh or forked context
592
595
  - **Prompt workflow recipes**: how to apply the packaged techniques directly with `subagent(...)` when the user describes the workflow in natural language instead of invoking a slash command. This includes parallel review, review-loop, parallel research, parallel context-build, parallel handoff-plan, gather-context-and-clarify, and parallel cleanup
593
596
  - **Role-agent prompting guidance**: compact contract prompts instead of long scripts, what to include in role-specific meta prompts, and retrieval budgets for researchers
594
- - **Safety boundaries**: child agents must not run subagents, must not invent intercom targets, and must escalate unapproved decisions
597
+ - **Safety boundaries**: child agents must not run subagents unless their resolved builtin tools explicitly include `subagent`, must not invent intercom targets, and must escalate unapproved decisions
595
598
  - **Intercom conventions**: when to ask vs send, and how parent-side result delivery works with `pi-intercom`
596
599
  - **Control and diagnostics**: attention signals, soft interrupts, status, and the `doctor` action
597
600
 
@@ -735,13 +738,18 @@ Status and control actions:
735
738
  ```ts
736
739
  subagent({ action: "status" })
737
740
  subagent({ action: "status", id: "<run-id>" })
741
+ subagent({ action: "status", id: "<nested-run-id>" })
738
742
  subagent({ action: "interrupt", id: "<run-id>" })
743
+ subagent({ action: "interrupt", id: "<nested-run-id>" })
739
744
  subagent({ action: "resume", id: "<run-id>", message: "follow-up question" })
740
745
  subagent({ action: "resume", id: "<run-id>", index: 1, message: "follow-up for child 2" })
746
+ subagent({ action: "resume", id: "<nested-run-id>", message: "follow-up for a nested child" })
741
747
  subagent({ action: "doctor" })
742
748
  ```
743
749
 
744
- `resume` sends the follow-up directly when an async child is still reachable over intercom. After completion, it revives the child by starting a new async child from the stored child session file. Multi-child async runs and remembered foreground single, parallel, or chain runs can be revived by passing `index` to choose the child. Revive starts a new child process from the old session context; it does not restart the same OS process, and it requires the chosen child to have a persisted `.jsonl` session file.
750
+ `status` resolves exact foreground ids, top-level async ids, and nested run ids before falling back to prefix matching. Nested status shows the root/parent path, nested children, session/artifact paths when known, and nested control commands. Inside child-safe fanout mode, bare `status` requires an id when no local foreground run is active, so children cannot enumerate unrelated top-level async runs. Bare `interrupt` still targets only the visible top-level run; interrupting a nested run requires its explicit nested id.
751
+
752
+ `resume` sends the follow-up directly when an async child is still reachable over intercom. After completion, it revives the child by starting a new async child from the stored child session file. Multi-child async runs and remembered foreground single, parallel, or chain runs can be revived by passing `index` to choose the child. Nested runs can be resumed by nested id when their live route or persisted session metadata is available. Revive starts a new child process from the old session context; it does not restart the same OS process, and it requires the chosen child to have a persisted `.jsonl` session file.
745
753
 
746
754
  ## Worktree isolation
747
755
 
@@ -820,7 +828,7 @@ Session directory precedence is: `params.sessionDir`, then `config.defaultSessio
820
828
  { "maxSubagentDepth": 1 }
821
829
  ```
822
830
 
823
- Controls nested delegation when no inherited `PI_SUBAGENT_MAX_DEPTH` is already in effect. Per-agent `maxSubagentDepth` can tighten the limit for that agent’s child runs, but cannot relax an inherited stricter limit.
831
+ Controls nested delegation when no inherited `PI_SUBAGENT_MAX_DEPTH` is already in effect. Per-agent `maxSubagentDepth` can tighten the limit for that agent’s child runs, but cannot relax an inherited stricter limit. This applies even to children that explicitly declare `tools: subagent`; at the cap, execution fanout is blocked instead of silently hiding nested work.
824
832
 
825
833
  ### `intercomBridge`
826
834
 
@@ -896,7 +904,7 @@ Async runs write:
896
904
  subagent-log-<id>.md
897
905
  ```
898
906
 
899
- `status.json` powers the widget and `subagent({ action: "status" })` output. `events.jsonl` contains wrapper events plus child Pi JSON events annotated with run and step metadata. `output-<n>.log` is a live human-readable tail. Fallback information is persisted so background runs are debuggable after completion.
907
+ `status.json` powers the widget and `subagent({ action: "status" })` output. `events.jsonl` contains wrapper events plus child Pi JSON events annotated with run and step metadata. Nested fanout status is stored as compact sidecar event/registry metadata and merged into parent status views and result/intercom payloads; full recursive status snapshots are not embedded in parent result files. `output-<n>.log` is a live human-readable tail. Fallback information is persisted so background runs are debuggable after completion.
900
908
 
901
909
  ## Live progress
902
910
 
@@ -918,9 +926,9 @@ This is disabled by default. Session data may contain source code, paths, enviro
918
926
 
919
927
  ## Recursion guard
920
928
 
921
- Subagents can call `subagent`, which can get expensive and hard to observe. A depth guard prevents unbounded nesting.
929
+ Subagents can call `subagent` only when their resolved builtin tools explicitly include `subagent`. That is meant for delegated fanout agents, not ordinary worker/reviewer children. A depth guard prevents unbounded nesting.
922
930
 
923
- By default, nesting is limited to two levels: main session → subagent → sub-subagent. Deeper calls are blocked with guidance to complete the current task directly.
931
+ By default, nesting is limited to two levels: main session → subagent → sub-subagent. Deeper calls are blocked with guidance to complete the current task directly. Nested runs appear in the parent status widget and `status` output as a tree, and `status`, `interrupt`, and `resume` can target a nested run by its id.
924
932
 
925
933
  Configure the limit with:
926
934
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-subagents",
3
- "version": "0.24.3",
3
+ "version": "0.25.0",
4
4
  "description": "Pi extension for delegating tasks to subagents with chains, parallel execution, and TUI clarification",
5
5
  "author": "Nico Bailon",
6
6
  "license": "MIT",
@@ -54,8 +54,7 @@
54
54
  "peerDependencies": {
55
55
  "@earendil-works/pi-agent-core": "*",
56
56
  "@earendil-works/pi-ai": "*",
57
- "@earendil-works/pi-coding-agent": "*",
58
- "@earendil-works/pi-tui": "*"
57
+ "@earendil-works/pi-coding-agent": "*"
59
58
  },
60
59
  "peerDependenciesMeta": {
61
60
  "@earendil-works/pi-agent-core": {
@@ -66,19 +65,16 @@
66
65
  },
67
66
  "@earendil-works/pi-coding-agent": {
68
67
  "optional": true
69
- },
70
- "@earendil-works/pi-tui": {
71
- "optional": true
72
68
  }
73
69
  },
74
70
  "dependencies": {
71
+ "@earendil-works/pi-tui": "^0.74.0",
75
72
  "jiti": "^2.7.0",
76
73
  "typebox": "^1.1.24"
77
74
  },
78
75
  "devDependencies": {
79
76
  "@earendil-works/pi-agent-core": "^0.74.0",
80
77
  "@earendil-works/pi-ai": "^0.74.0",
81
- "@earendil-works/pi-coding-agent": "^0.74.0",
82
- "@earendil-works/pi-tui": "^0.74.0"
78
+ "@earendil-works/pi-coding-agent": "^0.74.0"
83
79
  }
84
80
  }
@@ -4,7 +4,7 @@ description: Review/fix loop until clean
4
4
 
5
5
  Run a parent-orchestrated review loop for the requested work.
6
6
 
7
- Use the `subagent` tool. Keep the parent session as the loop controller and final decision-maker. Child subagents must receive concrete role-specific tasks; they must not run subagents or manage the loop themselves.
7
+ Use the `subagent` tool. Keep the parent session as the loop controller and final decision-maker. Child subagents must receive concrete role-specific tasks; they must not run subagents or manage the loop themselves unless the parent intentionally selected an explicit fanout agent whose builtin `tools` includes `subagent` for that assigned fanout.
8
8
 
9
9
  Default to a maximum of 3 review rounds unless I specify a different cap. Count a review round each time fresh-context reviewers inspect the current diff after a worker pass. Stop early when reviewers find no blockers or fixes worth doing now.
10
10
 
@@ -10,7 +10,7 @@ description: |
10
10
 
11
11
  # Pi Subagents
12
12
 
13
- This skill is for the main parent orchestrator only. Do not inject or follow it inside spawned child subagents. The parent session owns delegation, orchestration, review fanout, and final fix-worker launches; child subagents should receive concrete role-specific tasks and should not run their own subagent workflows.
13
+ This skill is for the main parent orchestrator only. Do not inject or follow it inside spawned child subagents. The parent session owns delegation, orchestration, review fanout, and final fix-worker launches; child subagents should receive concrete role-specific tasks. Ordinary children should not run their own subagent workflows; the explicit exception is a delegated fanout child whose resolved builtin `tools` includes `subagent`, and that child may use `subagent` only for the fanout work the parent assigned.
14
14
 
15
15
  Use this skill when the parent orchestrator needs to launch a specialized subagent, compose multiple agents into a workflow, or create/edit agents and chains on demand.
16
16
 
@@ -108,7 +108,38 @@ Use this at the start of non-trivial work. Launch `scout` for local context and
108
108
 
109
109
  ### Parallel cleanup technique
110
110
 
111
- Use this after implementation when the user wants cleanup review or when a final pass would reduce AI-slop. Launch two fresh-context `reviewer` tasks with `output: false` and `progress: false`: one deslop pass and one verbosity pass. If the `deslop` or `verbosity-cleaner` skills are available, pass the relevant skill to that reviewer; otherwise inline the criteria. Both reviewers are review-only and should flag concrete issues with severity, file/line references, and smallest safe fixes. Review-only/no-edit beats progress-writing or artifact-writing instructions. The parent decides what to apply and asks before making changes unless cleanup was already authorized.
111
+ Use this after implementation when the user wants cleanup review or when a final pass would reduce AI-slop. Launch two fresh-context `reviewer` tasks with `output: false` and `progress: false`: one deslop pass and one verbosity pass. If the `deslop` or `verbosity-cleaner` skills are available, pass the relevant skill to that reviewer; otherwise inline the criteria. Both reviewers are review-only and should flag concrete issues with severity, file/line references, and smallest safe fixes. Phrase the constraint as “Do not modify project/source files; returning findings through the configured output artifact is allowed” when you use `output` or `outputMode: "file-only"`. The parent decides what to apply and asks before making changes unless cleanup was already authorized.
112
+
113
+ ### Staged fix orchestration technique
114
+
115
+ Use this when a broad diff has known reviewer findings across several items and the user wants the parent to “orchestrate subagents like a boss.” Keep the active worktree safe with a three-stage chain:
116
+
117
+ 1. A parallel read-only planning fanout, one planner/reviewer per issue cluster. Each child inspects the real diff and returns exact files, line refs, proposed fixes, and focused validation. They must not edit.
118
+ 2. One writer worker. It receives the planner summaries through `{previous}`, the parent’s accepted scope, stop rules, and verification contract. It is the only child allowed to edit the active worktree.
119
+ 3. A parallel read-only validation fanout. Validators inspect the worker diff from fresh context with distinct angles, report pass/fail, remaining blockers, and missing verification.
120
+
121
+ Prefer `async: true`, `context: "fresh"` for planners/validators, `outputMode: "file-only"` for large summaries, and per-stage output names that will not collide. Use this pattern instead of launching several writer workers into a dirty worktree. Include non-blocking suggestions in the writer prompt only when they are small, safe, and do not expand product scope; otherwise record them as deferred.
122
+
123
+ Example shape:
124
+
125
+ ```typescript
126
+ subagent({
127
+ async: true,
128
+ context: "fresh",
129
+ chain: [
130
+ { parallel: [
131
+ { agent: "reviewer", task: "Plan fixes for deploy docs/workflow. Inspect the current diff. Do not modify project/source files; returning findings via the configured output artifact is allowed.", output: "plans/deploy.md", outputMode: "file-only" },
132
+ { agent: "reviewer", task: "Plan fixes for scheduler contract. Inspect the current diff. Do not modify project/source files; returning findings via the configured output artifact is allowed.", output: "plans/scheduler.md", outputMode: "file-only" },
133
+ { agent: "reviewer", task: "Plan fixes for sandbox/security. Inspect the current diff. Do not modify project/source files; returning findings via the configured output artifact is allowed.", output: "plans/sandbox.md", outputMode: "file-only" }
134
+ ], concurrency: 3 },
135
+ { agent: "worker", task: "Apply only the accepted fixes from these planning summaries. You are the sole writer for the active worktree. Run focused validation and report changed files, commands, failures, and remaining issues.\n\nPlanning summaries:\n{previous}", output: "worker/fixes.md", outputMode: "file-only", progress: true },
136
+ { parallel: [
137
+ { agent: "reviewer", task: "Validate the post-worker diff for deploy and scheduler fixes. Do not modify project/source files; returning findings via the configured output artifact is allowed.", output: "validation/deploy-scheduler.md", outputMode: "file-only" },
138
+ { agent: "reviewer", task: "Validate the post-worker diff for sandbox/security fixes. Do not modify project/source files; returning findings via the configured output artifact is allowed.", output: "validation/sandbox.md", outputMode: "file-only" }
139
+ ], concurrency: 2 }
140
+ ]
141
+ })
142
+ ```
112
143
 
113
144
  ## Builtin Agents
114
145
 
@@ -144,7 +175,7 @@ A strong subagent prompt usually includes:
144
175
  - **Goal**: the concrete outcome the child should produce.
145
176
  - **Context/evidence**: relevant plan paths, files, diffs, decisions, or user constraints already approved.
146
177
  - **Success criteria**: what must be true before the child can finish.
147
- - **Hard constraints**: true invariants only, such as no edits for review-only tasks, one writer thread, child must not run subagents, or escalation for unapproved decisions.
178
+ - **Hard constraints**: true invariants only, such as no edits for review-only tasks, one writer thread, child must not run subagents unless it is an explicitly assigned `tools: subagent` fanout child, or escalation for unapproved decisions.
148
179
  - **Validation**: targeted checks to run, or the next-best check when validation is impossible.
149
180
  - **Output**: the expected summary shape, artifact path, or finding format.
150
181
  - **Stop rules**: when to ask via `intercom`, when to stop after enough evidence, and when not to keep searching.
@@ -245,7 +276,7 @@ subagent({
245
276
  })
246
277
  ```
247
278
 
248
- Avoid duplicate output paths in parallel tasks. Concurrent children should not write to the same file. For large saved outputs, set `outputMode: "file-only"` together with an `output` path. The parent result then contains only a compact reference like `Output saved to: /abs/report.md (48.2 KB, 2847 lines). Read this file if needed.` instead of the full saved content. Do not use `output: false` for this; `output: false` means no file output. Failed runs and save errors still return inline details for debugging.
279
+ Avoid duplicate output paths in parallel tasks. Concurrent children should not write to the same file. For large saved outputs, set `outputMode: "file-only"` together with an `output` path. The parent result then contains only a compact reference like `Output saved to: /abs/report.md (48.2 KB, 2847 lines). Read this file if needed.` instead of the full saved content. Do not use `output: false` for this; `output: false` means no file output. When a task is review-only, say “do not modify project/source files” rather than “do not write files” if you also configured `output`; otherwise the child may treat the output artifact as forbidden. Failed runs and save errors still return inline details for debugging.
249
280
 
250
281
  ### Chain execution
251
282
 
@@ -293,13 +324,14 @@ const run = subagent({
293
324
  // Continue local inspection, then later call status with the returned id.
294
325
  ```
295
326
 
296
- Inspect async runs with `subagent({ action: "status", id: "..." })` or `subagent({ action: "status" })` for active runs.
327
+ Inspect async runs with `subagent({ action: "status", id: "..." })` or `subagent({ action: "status" })` for active runs. If a delegated fanout child launches nested runs, the parent status view shows them as a tree and you can target a nested run directly with its nested id.
297
328
 
298
329
  Use `resume` for follow-up work after a delegated run:
299
330
 
300
331
  ```typescript
301
332
  subagent({ action: "resume", id: "run-id", message: "Follow up on this point." })
302
333
  subagent({ action: "resume", id: "run-id", index: 1, message: "Continue reviewer 2." })
334
+ subagent({ action: "resume", id: "nested-run-id", message: "Continue this nested reviewer." })
303
335
  ```
304
336
 
305
337
  Resume behavior:
@@ -307,6 +339,7 @@ Resume behavior:
307
339
  - If an async child has completed, `resume` revives it by starting a new async child from the persisted child session file.
308
340
  - Multi-child async runs require `index` unless only one running child is selectable.
309
341
  - Completed foreground single, parallel, and chain runs can also be revived by `index` while their run metadata remains in extension state.
342
+ - Nested runs can be resumed by nested id when a live route or persisted nested session metadata is available.
310
343
  - Revive starts a new child process from the old session context; it does not restart the same OS process.
311
344
  - If the chosen child has no persisted `.jsonl` session file, resume fails and reports that directly.
312
345
 
@@ -330,13 +363,14 @@ Use soft interrupt when a child is clearly blocked or drifting and the parent ne
330
363
  subagent({ action: "interrupt" })
331
364
  ```
332
365
 
333
- Pass `id` when targeting a specific controllable run:
366
+ Pass `id` when targeting a specific controllable run, including a nested run shown in the parent status tree:
334
367
 
335
368
  ```typescript
336
369
  subagent({ action: "interrupt", id: "abc123" })
370
+ subagent({ action: "interrupt", id: "nested-run-id" })
337
371
  ```
338
372
 
339
- A soft interrupt cancels the current child turn and leaves the run paused. It does not mean the delegated task succeeded or failed. After an interrupt, decide the next explicit action: resume with clearer instructions, replace the task, ask the user, or stop the workflow.
373
+ A soft interrupt cancels the current child turn and leaves the run paused. It does not mean the delegated task succeeded or failed. Bare `interrupt` does not target hidden nested descendants; use the explicit nested id. After an interrupt, decide the next explicit action: resume with clearer instructions, replace the task, ask the user, or stop the workflow.
340
374
 
341
375
  Per-run control thresholds can be overridden when a task legitimately runs without observable output for longer than usual:
342
376
 
@@ -430,7 +464,7 @@ Use `contact_supervisor` with `reason: "need_decision"` when:
430
464
  - a child needs clarification instead of guessing
431
465
  - an approval, product, API, or scope choice is required before continuing safely
432
466
 
433
- Do not use `contact_supervisor` just to resolve review-only/no-edit versus progress-writing or artifact-writing instructions. No-edit wins, and the child should return review findings without touching files.
467
+ Do not use `contact_supervisor` just to resolve review-only/no-project-edit versus progress-writing or output-artifact instructions. The child must not modify project/source files, but returning findings through its normal response or configured output artifact is allowed unless the parent explicitly set `output: false`.
434
468
 
435
469
  Use `contact_supervisor` with `reason: "progress_update"` when:
436
470
  - a child is explicitly asked for progress
@@ -440,7 +474,7 @@ Use `contact_supervisor` with `reason: "progress_update"` when:
440
474
  Message conventions:
441
475
  - `reason: "need_decision"` waits for the parent reply and returns it to the child.
442
476
  - `reason: "progress_update"` is non-blocking and should stay concise.
443
- - Child-side routine completion handoffs are not expected. With the intercom bridge active, parent-side `pi-subagents` sends grouped completion results through `pi-intercom`: one grouped message per foreground parent run and one per completed async result file. Acknowledged foreground delivery returns a compact receipt with artifact/session paths; if unacknowledged, the normal full output is preserved. Grouped messages include child intercom targets and full child summaries.
477
+ - Child-side routine completion handoffs are not expected. With the intercom bridge active, parent-side `pi-subagents` sends grouped completion results through `pi-intercom`: one grouped message per foreground parent run and one per completed async result file. Acknowledged foreground delivery returns a compact receipt with artifact/session paths; if unacknowledged, the normal full output is preserved. Grouped messages include child intercom targets, full child summaries, and compact nested summaries under the parent child that launched them.
444
478
 
445
479
  If bridge instructions provide the child-facing tool, a child can ask:
446
480
 
@@ -656,9 +690,11 @@ The first `worker` implements the approved plan. The parent continues with indep
656
690
 
657
691
  For complex work, risky changes, broad refactors, or many changed lines, increase review and validation fanout rather than trusting one reviewer. Use distinct angles such as correctness/regressions, tests/validation, simplicity/maintainability, security/privacy, performance, docs/API contracts, and user-flow behavior. When reviewers find non-trivial issues or the fix worker touches many lines, run another focused review round before final validation.
658
692
 
693
+ When review has already produced concrete findings across several independent areas, use staged fix orchestration: parallel read-only planners for each issue cluster, one sole writer worker for the active worktree, then parallel fresh-context validators. This is the safest way to handle a dirty worktree with many prior changes because it parallelizes judgment without parallelizing writes. Non-blocking suggestions may go into the writer prompt only if they are small, safe, and inside the approved scope; otherwise defer them explicitly.
694
+
659
695
  For very large work, split into serial milestones instead of launching a swarm of writers. Each milestone gets one writer, a validation contract, fresh-context review/validation, a fix pass, and parent acceptance before the next milestone starts. Use parallel subagents inside a milestone for read-only context, research, review, and validation only.
660
696
 
661
- Keep orchestration authority in the parent session. Child subagents should not launch more subagents, read this skill, or run their own orchestration loops. Spawned subagents do not receive the `pi-subagents` skill, parent-only status/control/slash messages, prior parent `subagent` tool-call/tool-result artifacts, or the `subagent` extension tool. Child context filtering also strips old hidden orchestration-instruction messages when they appear in inherited history. Every child also receives a boundary instruction that says the parent owns orchestration, the child must not propose or run subagents, and implementation children must call real edit/write tools instead of printing pseudo tool calls. Pass children concrete role-specific work instead.
697
+ Keep orchestration authority in the parent session. Child subagents should not launch more subagents, read this skill, or run their own orchestration loops unless the parent intentionally selected a fanout agent whose builtin `tools` includes `subagent`. Spawned subagents do not receive the `pi-subagents` skill, parent-only status/control/slash messages, or prior parent `subagent` tool-call/tool-result artifacts. Ordinary children also do not receive the `subagent` extension tool. Child context filtering strips old hidden orchestration-instruction messages when they appear in inherited history. Every child receives a boundary instruction: ordinary children are told the parent owns orchestration and they must not propose or run subagents; explicit fanout children are told to use `subagent` only for the assigned fanout work, with `maxSubagentDepth` still enforced. Implementation children must call real edit/write tools instead of printing pseudo tool calls. Pass children concrete role-specific work instead.
662
698
 
663
699
  1. Clarify first. This is mandatory. Gather code context with `scout` or `context-builder`, add `researcher` only when external evidence matters, then ask the user clarifying questions with `interview` until scope, acceptance criteria, constraints, and non-goals are clear.
664
700
  2. Define the validation contract. State what done means before implementation: expected behavior, checks to run, user flows to exercise, and evidence required in the worker handoff. For UI, CLI, integration, or workflow changes, include at least one validator angle that uses the product the way a user would rather than only reading code.
@@ -297,6 +297,10 @@ function applyAgentConfig(target: AgentConfig, cfg: Record<string, unknown>): st
297
297
  target.maxSubagentDepth = cfg.maxSubagentDepth;
298
298
  } else return "config.maxSubagentDepth must be an integer >= 0 or false when provided.";
299
299
  }
300
+ if (hasKey(cfg, "completionGuard")) {
301
+ if (typeof cfg.completionGuard !== "boolean") return "config.completionGuard must be a boolean when provided.";
302
+ target.completionGuard = cfg.completionGuard;
303
+ }
300
304
  return undefined;
301
305
  }
302
306
 
@@ -366,6 +370,7 @@ function formatAgentDetail(agent: AgentConfig): string {
366
370
  if (agent.defaultReads?.length) lines.push(`Reads: ${agent.defaultReads.join(", ")}`);
367
371
  if (agent.defaultProgress) lines.push("Progress: true");
368
372
  if (agent.maxSubagentDepth !== undefined) lines.push(`Max subagent depth: ${agent.maxSubagentDepth}`);
373
+ if (agent.completionGuard === false) lines.push("Completion guard: false");
369
374
  if (agent.systemPrompt.trim()) lines.push("", "System Prompt:", agent.systemPrompt);
370
375
  return lines.join("\n");
371
376
  }
@@ -21,6 +21,7 @@ export const KNOWN_FIELDS = new Set([
21
21
  "defaultProgress",
22
22
  "interactive",
23
23
  "maxSubagentDepth",
24
+ "completionGuard",
24
25
  ]);
25
26
 
26
27
  function joinComma(values: string[] | undefined): string | undefined {
@@ -69,6 +70,7 @@ export function serializeAgent(config: AgentConfig): string {
69
70
  if (Number.isInteger(config.maxSubagentDepth) && config.maxSubagentDepth >= 0) {
70
71
  lines.push(`maxSubagentDepth: ${config.maxSubagentDepth}`);
71
72
  }
73
+ if (config.completionGuard === false) lines.push("completionGuard: false");
72
74
 
73
75
  if (config.extraFields) {
74
76
  for (const [key, value] of Object.entries(config.extraFields)) {
@@ -7,6 +7,7 @@ import * as os from "node:os";
7
7
  import * as path from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
9
  import type { OutputMode } from "../shared/types.ts";
10
+ import { getAgentDir } from "../shared/utils.ts";
10
11
  import { KNOWN_FIELDS } from "./agent-serializer.ts";
11
12
  import { parseChain } from "./chain-serializer.ts";
12
13
  import { mergeAgentsForScope } from "./agent-selection.ts";
@@ -45,6 +46,7 @@ export interface BuiltinAgentOverrideBase {
45
46
  skills?: string[];
46
47
  tools?: string[];
47
48
  mcpDirectTools?: string[];
49
+ completionGuard?: boolean;
48
50
  }
49
51
 
50
52
  interface BuiltinAgentOverrideConfig {
@@ -59,6 +61,7 @@ interface BuiltinAgentOverrideConfig {
59
61
  systemPrompt?: string;
60
62
  skills?: string[] | false;
61
63
  tools?: string[] | false;
64
+ completionGuard?: boolean;
62
65
  }
63
66
 
64
67
  interface BuiltinAgentOverrideInfo {
@@ -91,6 +94,7 @@ export interface AgentConfig {
91
94
  defaultProgress?: boolean;
92
95
  interactive?: boolean;
93
96
  maxSubagentDepth?: number;
97
+ completionGuard?: boolean;
94
98
  disabled?: boolean;
95
99
  extraFields?: Record<string, string>;
96
100
  override?: BuiltinAgentOverrideInfo;
@@ -131,7 +135,7 @@ interface AgentDiscoveryResult {
131
135
  }
132
136
 
133
137
  function getUserChainDir(): string {
134
- return path.join(os.homedir(), ".pi", "agent", "chains");
138
+ return path.join(getAgentDir(), "chains");
135
139
  }
136
140
 
137
141
  function splitToolList(rawTools: string[] | undefined): { tools?: string[]; mcpDirectTools?: string[] } {
@@ -182,6 +186,7 @@ function cloneOverrideBase(agent: AgentConfig): BuiltinAgentOverrideBase {
182
186
  skills: agent.skills ? [...agent.skills] : undefined,
183
187
  tools: agent.tools ? [...agent.tools] : undefined,
184
188
  mcpDirectTools: agent.mcpDirectTools ? [...agent.mcpDirectTools] : undefined,
189
+ completionGuard: agent.completionGuard,
185
190
  };
186
191
  }
187
192
 
@@ -200,6 +205,7 @@ function cloneOverrideValue(override: BuiltinAgentOverrideConfig): BuiltinAgentO
200
205
  ...(override.systemPrompt !== undefined ? { systemPrompt: override.systemPrompt } : {}),
201
206
  ...(override.skills !== undefined ? { skills: override.skills === false ? false : [...override.skills] } : {}),
202
207
  ...(override.tools !== undefined ? { tools: override.tools === false ? false : [...override.tools] } : {}),
208
+ ...(override.completionGuard !== undefined ? { completionGuard: override.completionGuard } : {}),
203
209
  };
204
210
  }
205
211
 
@@ -217,7 +223,7 @@ function findNearestProjectRoot(cwd: string): string | null {
217
223
  }
218
224
 
219
225
  function getUserAgentSettingsPath(): string {
220
- return path.join(os.homedir(), ".pi", "agent", "settings.json");
226
+ return path.join(getAgentDir(), "settings.json");
221
227
  }
222
228
 
223
229
  function getProjectAgentSettingsPath(cwd: string): string | null {
@@ -336,6 +342,14 @@ function parseBuiltinOverrideEntry(
336
342
  }
337
343
  }
338
344
 
345
+ if ("completionGuard" in input) {
346
+ if (typeof input.completionGuard === "boolean") {
347
+ override.completionGuard = input.completionGuard;
348
+ } else {
349
+ throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'completionGuard'; expected a boolean.`);
350
+ }
351
+ }
352
+
339
353
  if ("systemPrompt" in input) {
340
354
  if (typeof input.systemPrompt === "string") override.systemPrompt = input.systemPrompt;
341
355
  else throw new Error(`Builtin override '${name}' in '${filePath}' has invalid 'systemPrompt'; expected a string.`);
@@ -408,6 +422,7 @@ function applyBuiltinOverride(
408
422
  next.tools = tools;
409
423
  next.mcpDirectTools = mcpDirectTools;
410
424
  }
425
+ if (override.completionGuard !== undefined) next.completionGuard = override.completionGuard;
411
426
 
412
427
  return next;
413
428
  }
@@ -447,7 +462,7 @@ function applyBuiltinOverrides(
447
462
 
448
463
  export function buildBuiltinOverrideConfig(
449
464
  base: BuiltinAgentOverrideBase,
450
- draft: Pick<AgentConfig, "model" | "fallbackModels" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritSkills" | "defaultContext" | "disabled" | "systemPrompt" | "skills" | "tools" | "mcpDirectTools">,
465
+ draft: Pick<AgentConfig, "model" | "fallbackModels" | "thinking" | "systemPromptMode" | "inheritProjectContext" | "inheritSkills" | "defaultContext" | "disabled" | "systemPrompt" | "skills" | "tools" | "mcpDirectTools" | "completionGuard">,
451
466
  ): BuiltinAgentOverrideConfig | undefined {
452
467
  const override: BuiltinAgentOverrideConfig = {};
453
468
 
@@ -465,6 +480,9 @@ export function buildBuiltinOverrideConfig(
465
480
  const baseTools = joinToolList(base);
466
481
  const draftTools = joinToolList(draft);
467
482
  if (!arraysEqual(draftTools, baseTools)) override.tools = draftTools ? [...draftTools] : false;
483
+ if ((draft.completionGuard !== false) !== (base.completionGuard !== false)) {
484
+ override.completionGuard = draft.completionGuard !== false;
485
+ }
468
486
 
469
487
  return Object.keys(override).length > 0 ? override : undefined;
470
488
  }
@@ -630,6 +648,11 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
630
648
  }
631
649
 
632
650
  const parsedMaxSubagentDepth = Number(frontmatter.maxSubagentDepth);
651
+ const completionGuard = frontmatter.completionGuard === "false"
652
+ ? false
653
+ : frontmatter.completionGuard === "true"
654
+ ? true
655
+ : undefined;
633
656
 
634
657
  agents.push({
635
658
  name: runtimeName,
@@ -658,6 +681,7 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
658
681
  Number.isInteger(parsedMaxSubagentDepth) && parsedMaxSubagentDepth >= 0
659
682
  ? parsedMaxSubagentDepth
660
683
  : undefined,
684
+ completionGuard,
661
685
  extraFields: Object.keys(extraFields).length > 0 ? extraFields : undefined,
662
686
  });
663
687
  }
@@ -723,7 +747,7 @@ function resolveNearestProjectChainDirs(cwd: string): { readDirs: string[]; pref
723
747
  const BUILTIN_AGENTS_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "agents");
724
748
 
725
749
  export function discoverAgents(cwd: string, scope: AgentScope): AgentDiscoveryResult {
726
- const userDirOld = path.join(os.homedir(), ".pi", "agent", "agents");
750
+ const userDirOld = path.join(getAgentDir(), "agents");
727
751
  const userDirNew = path.join(os.homedir(), ".agents");
728
752
  const { readDirs: projectAgentDirs, preferredDir: projectAgentsDir } = resolveNearestProjectAgentDirs(cwd);
729
753
  const userSettingsPath = getUserAgentSettingsPath();
@@ -762,7 +786,7 @@ export function discoverAgentsAll(cwd: string): {
762
786
  userSettingsPath: string;
763
787
  projectSettingsPath: string | null;
764
788
  } {
765
- const userDirOld = path.join(os.homedir(), ".pi", "agent", "agents");
789
+ const userDirOld = path.join(getAgentDir(), "agents");
766
790
  const userDirNew = path.join(os.homedir(), ".agents");
767
791
  const userChainDir = getUserChainDir();
768
792
  const { readDirs: projectDirs, preferredDir: projectDir } = resolveNearestProjectAgentDirs(cwd);
@@ -802,7 +826,7 @@ export function discoverAgentsAll(cwd: string): {
802
826
  ...Array.from(chainMap.values()),
803
827
  ];
804
828
 
805
- const userDir = fs.existsSync(userDirNew) ? userDirNew : userDirOld;
829
+ const userDir = process.env.PI_CODING_AGENT_DIR ? userDirOld : fs.existsSync(userDirNew) ? userDirNew : userDirOld;
806
830
 
807
831
  return { builtin, user, project, chains, userDir, projectDir, userChainDir, projectChainDir, userSettingsPath, projectSettingsPath };
808
832
  }