@henryqw/pi-subagent 27.0.0 → 27.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CONTEXT.md CHANGED
@@ -61,7 +61,7 @@ Own Role-configured Pi delegation through one `delegate_task` surface:
61
61
  - Promotion requires exact unchanged clean Main and a passing combined generation. Durable promotion evidence precedes selected-worker termination and cleanup; recovery never rolls back a proven promotion.
62
62
  - Rejected workers and superseded generations stay retained until Main explicitly releases their exact clean, proved-owned resources. At most two unreleased integration checkouts coexist.
63
63
  - Failures, ambiguity, interruption, review findings, conflicts, drift, unproved termination, or cleanup failure enter `needs_attention` and preserve evidence.
64
- - `subagent_status` is read-only, callable from `codemode` scripts, and returns the bounded public projection as structured content, never raw run state. `delegate_task`, `subagent_resume`, `subagent_stage`, `subagent_integrate`, and `subagent_abort` are model-only. `subagent_resume` accepts only strict `retry`, `verify`, or `finalize` continuations. `subagent_abort` terminates only exact owned workers.
64
+ - `subagent_status` is read-only, callable from `codemode` scripts, and returns the bounded public projection as structured content, never raw run state. `delegate_task`, `subagent_resume`, `subagent_stage`, `subagent_integrate`, and `subagent_abort` are model-only. `subagent_resume` accepts only strict `retry`, `verify`, or `finalize` continuations; only a correcting `retry` takes Main's optional instruction. `subagent_abort` terminates only exact owned workers.
65
65
  - The extension never pushes, opens a pull request, publishes, deploys, stashes, resets, force-cleans, or deletes unproved recoverable work.
66
66
 
67
67
  ### Resource and launch policy
@@ -91,4 +91,4 @@ Own Role-configured Pi delegation through one `delegate_task` surface:
91
91
  - Durable state: `<agent-dir>/config/pi-subagent/state/<repository-hash>/`
92
92
  - Shared model routes: `<agent-dir>/config/pi-task-models/config.json`
93
93
 
94
- State version 4 is strict. Older or malformed files are rejected rather than rewritten or migrated silently.
94
+ State version 5 is strict. Older or malformed files are rejected rather than rewritten or migrated silently.
package/README.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # `@henryqw/pi-subagent`
2
2
 
3
- Delegate work to configured Pi Roles with [Herdr](https://herdr.dev/). Authorized direct tasks run in Herdr tabs in your current workspace; checked implementation runs in isolated Herdr worktrees. Both return a handle so Main can continue while they work.
3
+ Delegate work to configured Pi Roles with [Herdr](https://herdr.dev/). Authorized direct tasks run in Herdr tabs in your current workspace, and checked implementation runs in isolated Herdr worktrees. Both modes return a handle, so Main can continue while the work runs.
4
4
 
5
5
  ![Pi showing six delegated tasks running in parallel](./example.png)
6
6
 
7
7
  ## Install
8
8
 
9
- Requires Pi 1.1.0 or later in the 1.x series. Development and automated checks use Pi 1.1.0. Older Pi releases are no longer supported. Isolated work requires Herdr 0.9.3 or later, with a running Herdr server on API protocol 22 or later; Pi Subagent checks both before it starts.
9
+ Requires Pi 1.1.0 or later in the 1.x series. Development and automated checks use Pi 1.1.0. Older Pi releases are no longer supported. Isolated work requires Herdr 0.9.3 or later and a running Herdr server on API protocol 22 or later. Pi Subagent checks both before it starts.
10
10
 
11
11
  ```bash
12
12
  pi install npm:@henryqw/pi-task-models
@@ -17,7 +17,7 @@ Run `/task-models` and configure the `fast`, `balanced`, `frontier`, and `fav` r
17
17
 
18
18
  ### Upgrade from 26.x
19
19
 
20
- Version 27.0.0 has breaking changes: each direct task must declare `access`, Role files with unknown frontmatter keys are rejected, and the library replaces `loadRoles` with `loadRoleCatalog` and `findRole`. Read [Migrate from 26.x to 27.0.0](./docs/migration.md) before you upgrade.
20
+ Version 27.0.0 has breaking changes. Each direct task must declare `access`. Role files with unknown frontmatter keys are rejected. The library replaces `loadRoles` with `loadRoleCatalog` and `findRole`. Read [Migrate from 26.x to 27.0.0](./docs/migration.md) before you upgrade.
21
21
 
22
22
  ## Works with
23
23
 
@@ -27,18 +27,19 @@ Version 27.0.0 has breaking changes: each direct task must declare `access`, Rol
27
27
 
28
28
  ## Use
29
29
 
30
- Ask Main to delegate a bounded task, such as “Have a scout trace sign-in without editing files.” Main uses `delegate_task`; you receive progress and a result or an actionable failure while Main remains available.
30
+ Ask Main to delegate a bounded task, such as “Have a scout trace sign-in without editing files.” Main uses `delegate_task`. While Main stays available, you receive progress and a result or an actionable failure.
31
31
 
32
- Commands are for you. Delegation and state tools and the packaged skill are for Main. `git_read` is only for children whose Role declares it, not Main. You do not need to call agent tools or manage candidate identities yourself. Delegation and continuation tools use plain-object parameter schemas so providers that omit root-union tools can expose them; each mode and action still requires its exact arguments.
32
+ Commands are for you. Delegation and state tools and the packaged skill are for Main. `git_read` is only for children whose Role declares it. Main cannot use it. You do not need to call agent tools or manage candidate identities. Delegation and continuation tools use plain-object parameter schemas, so providers that omit root-union tools can expose them. Each mode and action still requires its exact arguments.
33
33
 
34
34
  | Surface | Type | Purpose |
35
35
  | --- | --- | --- |
36
36
  | `/subagent` | command | Browse work, Close/cancel-and-release locally owned direct workers, stop one running worker of a read workflow, launch isolated status inspection in a separate Herdr tab with the `fast` model, or send/edit pending instructions. Requires interactive TUI or RPC; inspection also requires a Herdr-managed pane and a configured `scout` Role. |
37
37
  | `/subagent roles` | command | List each Role with its source (built-in, user override, or user), resolved route model and thinking level or route error, tools, extensions, Skills, MCP servers, and per-file load errors. Read-only; no Herdr pane required. |
38
+ | `/subagent prune` | command | Review leftover worker and integration worktrees and branches in the current canonical repository, then remove the ones already merged after your confirmation. Without interactive UI it only reports. Requires a running Herdr server to prove that no pane uses a checkout. |
38
39
  | `/subagent recover` | command | Classify orphaned isolated work in the current canonical repository and send Main one follow-up with request IDs, blockers, and reported next actions. No UI or Herdr pane required. |
39
40
  | `delegate_task` | tool | Start authorized shared-checkout work or a durable isolated checked graph. |
40
- | `subagent_status` | tool | Read durable isolated state and the exact allowed continuation without replaying work. Declares an output schema, so `codemode` scripts receive the same bounded public projection as structured content. |
41
- | `subagent_resume` | tool | Perform the reported `retry`, `verify`, or `finalize` continuation; does not replace staging or integration. |
41
+ | `subagent_status` | tool | Read durable isolated state and the exact allowed continuation without replaying work. Declares an output schema, so `codemode` scripts receive the same bounded public projection as structured content. Failed review verdicts from tasks (including tasks whose siblings still run), the final gate, and integration generations appear in its text within one shared 32 KiB budget; omitted verdicts are counted. The structured projection carries each verdict up to 32 KiB, with `verdictTruncated` set when it is cut. |
42
+ | `subagent_resume` | tool | Perform the reported `retry`, `verify`, or `finalize` continuation; does not replace staging or integration. A `retry` that sends a same-worker correction accepts an optional `instruction` (at most 32000 characters), added to that prompt as Main's instruction after the saved failure. Other actions reject it. |
42
43
  | `subagent_stage` | tool | Stage or resolve an exact candidate, or reject/revise it; rejection of a staged candidate freezes that generation. |
43
44
  | `subagent_integrate` | tool | Advance staged dependents, refresh after clean Main drift or evidence-less validation failure, validate in the background, record one correction, promote, reconcile interrupted promotion, clean up proven promotion, or explicitly release rejected/superseded resources. |
44
45
  | `subagent_abort` | tool | Abort an isolated request only when no retained candidates or integration worktrees remain; cannot discard them. |
@@ -49,34 +50,34 @@ Commands are for you. Delegation and state tools and the packaged skill are for
49
50
 
50
51
  ![Main, its coordinator extension, and a Herdr subagent through checked work, worker failure, and coordinator interruption](./docs/worker-lifecycle.svg)
51
52
 
52
- `delegate_task`, `subagent_resume`, `subagent_stage`, `subagent_integrate`, and `subagent_abort` are model-only: Pi's `codemode` scripts cannot call them, because delegation and guarded state transitions need Main's visible reasoning. `subagent_status` stays callable from scripts. Main's potentially-writing calls can run together without writer ownership, including all Bash calls and nested `codemode` writes. Only direct requests with a task declaring `access: "write"` reserve the checkout. They can start only when no direct writer owns it and no Main potentially-writing call is in flight there. Each Main call is tracked separately until its result or execution end; a script's result does not release its active nested writes. Reads never wait. Use `mode: isolated` to keep Main free to write; this adds worktree checks and explicit integration.
53
+ `delegate_task`, `subagent_resume`, `subagent_stage`, `subagent_integrate`, and `subagent_abort` are model-only. Pi's `codemode` scripts cannot call them, because delegation and guarded state transitions need Main's visible reasoning. Scripts can still call `subagent_status`. Main's potentially-writing calls can run together without writer ownership. This includes all Bash calls and nested `codemode` writes. Only a direct request with a task that declares `access: "write"` reserves the checkout. Such a request can start only when no direct writer owns the checkout and no Main potentially-writing call is in progress there. The extension tracks each Main call separately until its result or execution end. The result of a script does not release its active nested writes. Reads never wait. To keep Main free to write, use `mode: isolated`. This mode adds worktree checks and explicit integration.
53
54
 
54
- The coordinator is extension code running in Main's Pi process, not another agent. It persists the request, launches the Herdr subagent, checks its committed work, and reports saved results; Main chooses what to stage and promote. If a worker fails, the coordinator reports attention after the wave settles, not necessarily immediately while siblings work. If Main's Pi process stops, no follow-up is guaranteed: restart and run `/subagent recover` in the canonical repository to classify orphaned interruptions and send Main one recovery report. Main then uses `subagent_status` for exact evidence and deliberate next actions. A stale `running` state does not prove a worker is still active; interrupted or ambiguous actions are never replayed automatically.
55
+ The coordinator is extension code that runs in Main's Pi process. It is not another agent. It persists the request, launches the Herdr subagent, checks its committed work, and reports saved results. Main chooses what to stage and promote. If a worker fails, the coordinator reports attention after the wave settles. It does not always report immediately while siblings work. If Main's Pi process stops, a follow-up is not guaranteed. Restart Pi. Then run `/subagent recover` in the canonical repository. This command classifies orphaned interruptions and sends Main one recovery report. Main then uses `subagent_status` for exact evidence and deliberate next actions. A stale `running` state does not prove that a worker is still active. The extension never replays interrupted or ambiguous actions automatically.
55
56
 
56
- The TUI has two separate compact status widgets, each with one row per visible worker or task. `D` marks direct work; `I` marks isolated work. Badges such as `[S]` and `[I3]` identify the Role initial. Isolated badges also include the model class: `1` fast, `2` balanced, `3` frontier, `*` fav.
57
+ The TUI has two separate compact status widgets. Each widget has one row for each visible worker or task. `D` marks direct work, and `I` marks isolated work. Badges such as `[S]` and `[I3]` identify the Role initial. Isolated badges also include the model class: `1` fast, `2` balanced, `3` frontier, `*` fav.
57
58
 
58
- Each direct row starts with a saved reference such as `D7.1`. `D7` is the workflow number in this Pi session, and `.1` is the worker's position in the original request. `/subagent` uses the same reference and name; the Herdr tab label shows them at launch. Numbers continue after reload and are never reused in the session; if a session switch carries a workflow into a session that already uses its number, it gets a new number there. An existing Herdr tab keeps its original launch label. The direct widget shows the launched model, thinking level, elapsed time, and measured session tokens (`— tok` until usage is available). Failed rows appear before working and completed rows. When rows overflow, `… N more` includes counts by status. The isolated widget shows the recorded task state and any retained workspace, not reliable live model, thinking, or token readings from Herdr. Attention appears before ordinary work; overflow uses `+N more · /subagent`. An aborted request with visible tasks has its own `request aborted` row and does not change their recorded statuses. A committed worker without a checked candidate can still show attention rather than readiness. Status glyphs use the active TUI theme, while the text still names the status without color.
59
+ Each direct row starts with a saved direct reference such as `D7.1`. `D7` is the workflow number in this Pi session, and `.1` is the position of the worker in the original request. `/subagent` uses the same reference and name. The Herdr tab label shows them at launch. Numbers continue after reload, and the session never uses a number again. A session switch can move a workflow into a session that already uses its number. In that case, the workflow gets a new number there. An existing Herdr tab keeps its original launch label. The direct widget shows the launched model, thinking level, elapsed time, and measured session tokens (`— tok` until usage is available). Failed rows show before working and completed rows. When rows overflow, `… N more` includes counts by status. The isolated widget shows the recorded task state and any retained workspace. It does not show reliable live model, thinking, or token readings from Herdr. Attention shows before ordinary work, and overflow uses `+N more · /subagent`. An aborted request with visible tasks has its own `request aborted` row. This row does not change the recorded statuses of the tasks. A committed worker without a checked candidate can show attention, not readiness. Status glyphs use the active TUI theme. The text also names the status without color.
59
60
 
60
- A ready isolated candidate is **not promoted** to Main. Choose **Refresh** in `/subagent` to reload both the menu and isolated widget from saved state; it does not recover or change a request. Use `/subagent` or `subagent_status` for exact identities, failures, and recovery.
61
+ A ready isolated candidate is **not promoted** to Main. To reload the menu and the isolated widget from saved state, choose **Refresh** in `/subagent`. Refresh does not recover or change a request. Use `/subagent` or `subagent_status` for exact identities, failures, and recovery.
61
62
 
62
63
  ### Direct tool activity
63
64
 
64
- Direct rows can show `pending read, bash` and `last read 125ms recorded`. Pending means Pi saved the call but has no matching result in this task's session. It does not prove that execution started. Parallel calls remain pending until their own results arrive.
65
+ Direct rows can show `pending read, bash` and `last read 125ms recorded`. Pending means that Pi saved the call, but the session of this task has no matching result. It does not prove that execution started. Parallel calls stay pending until their own results arrive.
65
66
 
66
- Recorded time comes only from the latest matched tool result's `durationMs`. It is not calculated from assistant timestamps. If the latest result has no timing, no tool time is shown, even if an earlier result had timing. Older sessions and calls rejected before execution can have no recorded time. Task elapsed time remains a separate metric.
67
+ Recorded time comes only from the `durationMs` of the latest matched tool result. The extension does not calculate it from assistant timestamps. If the latest result has no timing, the row shows no tool time, even if an earlier result had timing. Older sessions, and calls rejected before execution, can have no recorded time. Task elapsed time is a separate metric.
67
68
 
68
- Activity uses the existing session sample and file-size cache. A partial trailing JSONL entry is ignored until it is complete. Tool names have terminal control characters removed for display. Narrow rows drop activity before other metrics to keep worker identity and failure state visible. Activity and time are display information, not proof of task completion, worker ownership, or a timeout.
69
+ Activity uses the existing session sample and file-size cache. The extension ignores a partial trailing JSONL entry until it is complete. For display, the extension removes terminal control characters from tool names. Narrow rows remove activity before other metrics, so worker identity and failure state stay visible. Activity and time are display information, not proof of task completion, worker ownership, or a timeout.
69
70
 
70
71
  ### How Main routes delegation
71
72
 
72
- Main chooses the mode from your request; you do not need to select one. Words like “direct” and “isolated” can signal your intent, but the work determines the route. Main declares the chosen mode in every `delegate_task` call, and the extension never falls back between modes.
73
+ Main chooses the mode from your request. You do not need to select one. Words such as “direct” and “isolated” can show your intent, but the work sets the route. Main declares the selected mode in each `delegate_task` call. The extension never falls back between modes.
73
74
 
74
75
  | Mode | Request that triggers it | Checkout behavior |
75
76
  | --- | --- | --- |
76
77
  | `direct` | Bounded research, review, or explicitly authorized shared-checkout writes and commits | Opens non-focused Herdr tabs in Main's current workspace. No worktree is created. |
77
78
  | `isolated` | Implementation or a checked task graph | Runs changesets in owned Herdr worktrees; Main selects, validates, and promotes exact candidates. |
78
79
 
79
- Keep trivial mechanically verifiable work in Main. Keep tightly coupled changes under one owner rather than splitting by file count.
80
+ Keep trivial mechanically verifiable work in Main. Keep tightly coupled changes under one owner. Do not split them by file count.
80
81
 
81
82
  ### Direct delegation
82
83
 
@@ -100,29 +101,29 @@ Parallel: { mode: "direct", tasks: [{ role, name, task, access, ... }] }
100
101
  Chain: { mode: "direct", chain: [{ role, name, task, access, ... }] }
101
102
  ```
102
103
 
103
- Every direct task declares `access: "read"` or `access: "write"`; a missing or other value fails before launch. Main declares `write` when the task may change files, Git state, or external resources. `read` is not enforced: there is no sandbox, and a read task keeps all of its Role's tools. A request is a write request when any task declares `write`. Read requests skip checkout admission and return plain-text answers; their parallel `tasks` run concurrently up to `maxSubagents`, while a `chain` still runs in order. Use isolated mode for checked writes.
104
+ Every direct task declares `access: "read"` or `access: "write"`. A missing or other value fails before launch. Main declares `write` when the task can change files, Git state, or external resources. The extension does not enforce `read`. There is no sandbox, and a read task keeps all the tools of its Role. A request is a write request when any task declares `write`. Read requests skip checkout admission and return plain-text answers. Their parallel `tasks` run concurrently up to `maxSubagents`, but a `chain` still runs in order. Use isolated mode for checked writes.
104
105
 
105
- Direct tasks use the selected Role's declared tools, extensions, Skills, and MCP servers, including write-capable resources. Authorization comes from your request and Main's bounded task packet, not the Role's capabilities: name allowed paths/actions and exclusions, preserve unrelated edits and staged changes, and authorize commits or external actions explicitly. For example, a configured Git operator with `bash` can stage named pending files and commit them on the `fast` route. Use isolated mode for a checked changeset.
106
+ Direct tasks use the declared tools, extensions, Skills, and MCP servers of the selected Role, including write-capable resources. Authorization comes from your request and Main's bounded task packet, not from the capabilities of the Role. Name the permitted paths, actions, and exclusions. Preserve unrelated edits and staged changes. Authorize commits or external actions explicitly. For example, a configured Git operator with `bash` can stage named pending files and commit them on the `fast` route. Use isolated mode for a checked changeset.
106
107
 
107
- Direct writes affect Main's checkout immediately. No clean-checkout prerequisite, automatic checks, rollback, or isolated integration guarantee applies. Write requests can start only when no direct writer owns the checkout and no Main potentially-writing call is in flight there. They run their tasks serially and hold checkout admission after the initial handle returns; competing direct writers and Main writes fail until all owned workers are proved stopped. Use `mode: isolated` to keep Main free to write, with worktree checks and explicit integration instead of immediate shared-checkout changes. Parallel read requests still run concurrently. A failure in a write request stops further dispatch; an uncertain worker may still be running. Chains replace each literal `{previous}` with the preceding successful answer and stop on failure. Task outcome and worker termination are separate: after settlement the coordinator closes exact owned writer panes and releases admission only after pane absence and two empty private process-lease scans. Native `lsof` is required for this proof; no Git files, commits, or saved sessions are removed. Failure remains failure even when admission is released. If closing fails, identity changes, a leased child survives, or inspection is uncertain, admission stays held. Open `/subagent`, select the exact direct workflow, and choose **Close/cancel-and-release ALL owned workers in this workflow** to cancel dispatch and retry termination. It stops every owned worker of that workflow, not one selected worker. The action checks session/branch/epoch and live ownership; recorded-only tasks cannot unlock anything. Resolve the reported blocker (including any surviving owned process), then repeat the same action. Restarting Pi is not termination proof. Inspect partial changes before new writes; never blindly retry a commit or delete work to recover.
108
+ Direct writes change Main's checkout immediately. No clean-checkout prerequisite, automatic checks, rollback, or isolated integration guarantee applies. A write request can start only when no direct writer owns the checkout and no Main potentially-writing call is in progress there. A write request runs its tasks serially. It holds checkout admission after the initial handle returns. Competing direct writers and Main writes fail until the extension proves that all owned workers stopped. To keep Main free to write, use `mode: isolated`. This mode gives worktree checks and explicit integration, not immediate shared-checkout changes. Parallel read requests still run concurrently. A failure in a write request stops further dispatch, but an uncertain worker can still run. Chains replace each literal `{previous}` with the preceding successful answer and stop on failure. Task outcome and worker termination are separate. After settlement, the coordinator closes exact owned writer panes. It releases admission only after pane absence and two empty private process-lease scans. This proof requires native `lsof`. The coordinator removes no Git files, commits, or saved sessions. A failure stays a failure even when admission is released. Admission stays held if the close fails, identity changes, a leased child survives, or inspection is uncertain. In that case, open `/subagent` and select the exact direct workflow. Then choose **Close/cancel-and-release ALL owned workers in this workflow** to cancel dispatch and try termination again. This action stops every owned worker of that workflow, not one selected worker. The action checks session, branch, epoch, and live ownership. Recorded-only tasks cannot unlock anything. Resolve the reported blocker, including any surviving owned process. Then do the same action again. A restart of Pi is not termination proof. Inspect partial changes before new writes. To recover, never blindly retry a commit or delete work.
108
109
 
109
- If tab creation times out or returns incomplete or mismatched identity, `/subagent` retains the allocation intent: workspace, checkout, label, private lease/session paths, and any returned tab/pane IDs. No worker is started until creation identity is verified. These records are evidence, not ownership: Close is unavailable for unverified allocation, and admission remains held pending manual reconciliation against Herdr. Do not recreate a tab, guess missing IDs, or restart to bypass this blocker. A Close begun while a worker is active keeps its branch/session/epoch guards through cancellation settlement; stale dialogs cannot close panes or release admission.
110
+ Tab creation can time out or return an incomplete or mismatched identity. In that case, `/subagent` retains the allocation intent: workspace, checkout, label, private lease and session paths, and any returned tab and pane IDs. No worker starts until the extension verifies the creation identity. These records are evidence, not ownership. Close is not available for an unverified allocation. Admission stays held until you reconcile it manually against Herdr. Do not create the tab again, guess missing IDs, or restart to bypass this blocker. A Close that starts while a worker is active keeps its branch, session, and epoch guards until the cancellation settles. Stale dialogs cannot close panes or release admission.
110
111
 
111
- Tasks in a write request must return one JSON completion object, such as `{"outcome":"succeeded","answer":"Scoped commit completed."}`. `outcome` must be `succeeded`, `failed`, or `blocked`, and `answer` must be non-empty. A normal final Pi turn is not enough: failure, blocked work, plain text, or malformed completion stops later dispatch, but admission depends on proved termination, not the reported outcome. Success is the worker's explicit report, not independent validation of its changes.
112
+ Tasks in a write request must return one JSON completion object, such as `{"outcome":"succeeded","answer":"Scoped commit completed."}`. `outcome` must be `succeeded`, `failed`, or `blocked`, and `answer` must be non-empty. A normal final Pi turn is not sufficient. Failure, blocked work, plain text, or a malformed completion stops later dispatch. But admission depends on proved termination, not on the reported outcome. Success is the explicit report of the worker. It is not an independent validation of its changes.
112
113
 
113
- The tool returns a task ID and first Herdr tab after launch, not the answer. The extension observes each worker and sends one result to Main when the workflow finishes. If Main is busy, Pi queues it after the current turn; if idle, it starts a turn. A blocked, stalled, unknown, or truncated result is not reported as success. When a task fails after its worker produced text, the result keeps a bounded excerpt marked as partial output; it is never success. Every worker result delivered to Main starts with a header stating that the text is the worker's output and carries no user authority. Text that imitates harness framing, such as `<system-reminder>`-style tags or lines starting with `Human:` or `Assistant:`, gets an inserted backslash; nothing else changes. Isolated status and follow-up answer previews use the same header. Session switch or shutdown stops observation and preserves tab identities for recovery. Open `/subagent` on the session branch to inspect exact tabs, agents, and Pi session files, including tabs launched after the first. The menu has one row per workflow: a single worker shows its reference, Role, and name (`D7.1 · worker · Search audit`); a multi-worker workflow shows its reference and worker count (`D8 · 3 workers`), and selecting it lists each recorded worker by reference, Role, and name with its exact IDs. References and names help you match rows; they are never used to decide ownership. Older records without saved references show their saved allocation label or exact task ID. A `recorded only` row is not proof that its workers are running or finished; locally observed work is labelled separately.
114
+ The tool returns a task ID and the first Herdr tab after launch, not the answer. The extension observes each worker and sends one result to Main when the workflow finishes. If Main is busy, Pi queues the result after the current turn. If Main is idle, the result starts a turn. The extension does not report a blocked, stalled, unknown, or truncated result as success. In the result, the line of each launched worker shows its model, thinking level, measured session tokens, and elapsed time, such as `✓ [1/2] Check · worker · provider/model · thinking high · 1.5k tok · 42s`. If Pi recorded no usage, the line shows `tokens unknown`. Failed and stopped workers show the usage that the extension measured until they ended. A worker that the extension skipped before launch shows no usage. The result details contain the same values in each entry as `model`, `thinkingLevel`, `tokens`, and `elapsedMs`. When the token count is unknown, `tokens` is absent. If a task fails after its worker produced text, the result keeps a bounded excerpt marked as partial output. This excerpt is never success. Every worker result delivered to Main starts with a header. The header states that the text is the output of the worker and carries no user authority. Text that imitates harness framing gets an inserted backslash. Examples are `<system-reminder>`-style tags and lines that start with `Human:` or `Assistant:`. Nothing else changes. Isolated status and follow-up answer previews use the same header. A session switch or shutdown stops observation and preserves tab identities for recovery. To inspect exact tabs, agents, and Pi session files, open `/subagent` on the session branch. This includes tabs launched after the first. The menu has one row for each workflow. A single worker shows its reference, Role, and name (`D7.1 · worker · Search audit`). A multi-worker workflow shows its reference and worker count (`D8 · 3 workers`). When you select it, the menu lists each recorded worker by reference, Role, and name with its exact IDs. References and names help you match rows. The extension never uses them to decide ownership. Older records without saved references show their saved allocation label or exact task ID. A `recorded only` row does not prove that its workers run or finished. The menu labels locally observed work separately.
114
115
 
115
116
  ### Continue or stop one direct worker
116
117
 
117
- Main can continue a finished direct worker instead of starting a fresh one. It adds `continueFrom` with the worker's reference, such as `"continueFrom": "D7.1"`, to any single, parallel, or chain task, and uses the same `role` that the worker ran with. The worker resumes its saved Pi session in a new non-focused Herdr tab, so it keeps its earlier context. The new tab gets a new agent name and reference, and its record points to the same session and private lease file. The launch handle and the result say `continued from D7.1`. Access, admission, write rules, and model routes are the same as for a new task. A request can continue each worker only once, and only from the checkout and Herdr workspace where the worker ran. After a continuation, continue the newest reference; an older reference to the same session is refused because a later turn follows its own.
118
+ Main can continue a finished direct worker, so it does not have to start a fresh one. It adds `continueFrom` with the reference of the worker, such as `"continueFrom": "D7.1"`, to any single, parallel, or chain task. It uses the same `role` that the worker ran with. The worker resumes its saved Pi session in a new non-focused Herdr tab, so it keeps its earlier context. The new tab gets a new agent name and reference, and its record points to the same session and private lease file. The launch handle and the result say `continued from D7.1`. Access, admission, write rules, and model routes are the same as for a new task. A request can continue each worker only one time, and only from the checkout and Herdr workspace where the worker ran. After a continuation, continue the newest reference. The extension refuses an older reference to the same session, because a later turn follows its own turn.
118
119
 
119
- Before launch, the request fails if the reference is unknown or matches more than one record on this session branch, the Role is different, a locally active task still owns the worker's session, the session file is missing or larger than 16 MiB, or the session's last turn for the recorded prompt is not finished. If Herdr still lists the old agent, it must match the recorded name, pane, tab, workspace, and checkout, and be idle or done; a working or blocked agent is never closed. The old pane is closed, then pane absence and two empty lease scans are proved. Records saved before 27.0.0 have no turn identity and cannot be continued; start a fresh task.
120
+ Before launch, the request fails if the reference is unknown or matches more than one record on this session branch. It also fails if the Role is different or a locally active task still owns the session of the worker. It also fails if the session file is missing or larger than 16 MiB, or if the last turn of the session for the recorded prompt is not finished. If Herdr still lists the old agent, the agent must match the recorded name, pane, tab, workspace, and checkout. It must also be idle or done. The extension never closes a working or blocked agent. The extension closes the old pane, then proves pane absence and two empty lease scans. Records saved before 27.0.0 have no turn identity and cannot be continued. For these records, start a fresh task.
120
121
 
121
- In a running workflow that has only `read` tasks, `/subagent` shows **Stop this worker** for each running worker. It aborts only that worker, sends Ctrl-C, closes its exact pane, and proves pane absence and two empty lease scans. The worker's result becomes a failure, `Stopped by user from /subagent`, and keeps any partial output. Other workers continue; a chain stops its later steps. If termination of the stopped worker is unproved, the workflow keeps local ownership so **Close/cancel-and-release** stays available. Main receives one result when the workflow finishes. A workflow with a `write` task offers only **Close/cancel-and-release ALL owned workers in this workflow**. Both actions check session, branch, and epoch, so a stale dialog cannot act.
122
+ In a running workflow that has only `read` tasks, `/subagent` shows **Stop this worker** for each running worker. It aborts only that worker, sends Ctrl-C, closes its exact pane, and proves pane absence and two empty lease scans. The result of the worker becomes a failure, `Stopped by user from /subagent`, and keeps any partial output. Other workers continue, but a chain stops its later steps. If termination of the stopped worker is not proved, the workflow keeps local ownership. Thus, **Close/cancel-and-release** stays available. Main receives one result when the workflow finishes. A workflow with a `write` task offers only **Close/cancel-and-release ALL owned workers in this workflow**. Both actions check session, branch, and epoch, so a stale dialog cannot act.
122
123
 
123
124
  ### Isolated checked graphs
124
125
 
125
- An isolated request has one durable ID, one goal, and 1–8 typed tasks:
126
+ An isolated request has one durable ID, one goal, and 1 to 8 typed tasks:
126
127
 
127
128
  ```json
128
129
  {
@@ -165,39 +166,51 @@ An isolated request has one durable ID, one goal, and 1–8 typed tasks:
165
166
  }
166
167
  ```
167
168
 
168
- `dependsOn` controls scheduling. `contextFrom` also schedules dependencies and carries completed text outputs in the declared order. Do not repeat the same task in both fields. Changesets require focused task checks; a graph with changesets requires final checks. A canonical root `package.json` test script is required: the runner adds `pnpm test` to final checks if missing, and Main runs it on the combined integration checkout before promotion. Checks and judgments bind to exact Git identities; candidate drift, Main drift, mutation during validation, ambiguity, or conflicts stop promotion and retain evidence.
169
+ `dependsOn` controls scheduling. `contextFrom` also schedules dependencies and carries completed text outputs in the declared order. Do not repeat the same task in both fields. Changesets require focused task checks. A graph with changesets requires final checks. A canonical root `package.json` test script is required. If `pnpm test` is not in the final checks, the runner adds it. Main runs it on the combined integration checkout before promotion. Checks and judgments bind to exact Git identities. Candidate drift, Main drift, mutation during validation, ambiguity, or conflicts stop promotion and retain evidence.
169
170
 
170
- `delegate_task`, `subagent_resume`, and `subagent_integrate advance` return a durable request ID after the wave is saved; productive work continues while Main is free. Main can do other work or end its turn instead of sleeping or polling. Pi sends a follow-up in the launching session whenever a checked worker becomes ready or a worker needs attention, and again when the wave finishes. A worker whose exact turn ends without a changed clean commit—including a failed or aborted turn—needs attention, not an inferred candidate; use `subagent_status` for its saved evidence. Session replacement or shutdown suppresses stale delivery and logs the request ID and reason, without prompts or credentials. Synchronous delivery failures are reported without automatic replay; Pi reports asynchronous `send_message` failures through its native runtime. If delivery is missed, inspect `subagent_status`.
171
+ `delegate_task`, `subagent_resume`, and `subagent_integrate advance` return a durable request ID after the extension saves the wave. Productive work continues while Main is free. Main can do other work or end its turn. It does not need to sleep or poll. Pi sends a follow-up in the launching session when a checked worker becomes ready or a worker needs attention. Pi sends another follow-up when the wave finishes. A worker can end its exact turn without a changed clean commit, for example after a failed or aborted turn. That worker needs attention, and the extension does not infer a candidate. Use `subagent_status` for its saved evidence. If the turn ends with tracked or untracked changes in the worktree, the coordinator first sends the same worker one saved reminder for each attempt. The reminder tells the worker to commit the changes or explain why not. The reminder does not use a correction or a dispatch attempt. The coordinator never commits, stages, stashes, or discards the changes itself. If the worktree is still dirty after the reminder, the task needs attention, and the changes stay in place. Follow-ups show only a short preview of a failed review verdict. They tell Main to run `subagent_status` for the full text. Session replacement or shutdown suppresses stale delivery. The extension then logs the request ID and reason, without prompts or credentials. The extension reports synchronous delivery failures and does not replay them automatically. Pi reports asynchronous `send_message` failures through its native runtime. If delivery is missed, inspect `subagent_status`.
171
172
 
172
- Completed text answers are saved independently of cleanup. Status and terminal follow-ups include a bounded accepted-answer preview and cleanup evidence. Ignored automatic artifacts can retain a checkout without violating the read-only contract; no ignored files are deleted to make cleanup pass. Tracked or untracked changes, changed checkout identity, or unproved inspection still require attention: the completed answer remains saved but is not accepted as dependency context or retried automatically. Retained text checkouts require deliberate manual inspection and cleanup; a retained checkout alone is not evidence that the Role edited source.
173
+ The extension saves completed text answers independently of cleanup. Status and terminal follow-ups include a bounded accepted-answer preview and cleanup evidence. Ignored automatic artifacts can retain a checkout, and this does not violate the read-only contract. The extension deletes no ignored files to make cleanup pass. Tracked or untracked changes, a changed checkout identity, or unproved inspection still require attention. The completed answer stays saved. But the extension does not accept it as dependency context or retry it automatically. Retained text checkouts require deliberate manual inspection and cleanup. A retained checkout alone is not evidence that the Role edited source.
173
174
 
174
- Isolated workers can start while Main has staged, unstaged, or untracked changes: they use only Main's pinned committed branch, HEAD, and tree. Those local edits are neither copied into worker worktrees nor stashed, staged, reset, or merged automatically. Main must remain at that committed identity while workers run; branch or HEAD drift blocks new waves. The worker produces a committed candidate without tracked or untracked changes, then runs focused preliminary checks and optional judgment. Ignored generated files do not block candidacy, but prevent automatic worktree cleanup; inspect them before deleting retained work. The runner records each ready candidate immediately but **does not integrate it automatically**. Main can inspect `subagent_status` and use `subagent_stage` to select the exact candidate in an owned integration worktree while siblings are still working; conflicts are resolved there. Stage and resolve are available during an active wave, but rejection and revision wait for the wave to settle. `subagent_integrate` can advance dependent tasks from a staged snapshot, validate the combined tip with full root checks and optional final judgment after the wave settles, and promote it to Main only after exact validation. A definitive failed combined check/review allows one committed correction in the integration checkout followed by another validation. Main must explicitly promote; promotion and cleanup never silently discard retained work.
175
+ Isolated workers can start while Main has staged, unstaged, or untracked changes. They use only Main's pinned committed branch, HEAD, and tree. The extension does not copy those local edits into worker worktrees. It also does not stash, stage, reset, or merge them automatically. Main must stay at that committed identity while workers run. Branch or HEAD drift blocks new waves. The worker produces a committed candidate without tracked or untracked changes. Then it runs focused preliminary checks and optional judgment. Ignored generated files do not block candidacy, but they prevent automatic worktree cleanup. Inspect them before you delete retained work. The runner records each ready candidate immediately but **does not integrate it automatically**. Main can inspect `subagent_status`. While siblings still work, Main can use `subagent_stage` to select the exact candidate in an owned integration worktree. Main resolves conflicts there. Stage and resolve are available during an active wave, but rejection and revision wait until the wave settles. `subagent_integrate` can advance dependent tasks from a staged snapshot. After the wave settles, it can validate the combined tip with full root checks and optional final judgment. It promotes the tip to Main only after exact validation. A definitive failed combined check or review permits one committed correction in the integration checkout, followed by another validation. Main must explicitly promote. Promotion and cleanup never silently discard retained work.
175
176
 
176
- Open `/subagent` without arguments to pick work by label. The menu shows direct records on this session branch and isolated requests in the current canonical Git checkout (including requests launched in other sessions). Pick an isolated request and **Inspect status and recovery** to launch a non-focused Herdr tab with the configured `scout` Role on the configured `fast` route, as a read-access task with the Role's own resources. Its prompt contains bounded saved status (including retained paths, but not raw launch environment or session transcripts); the analysis stays in that tab and does not queue a Main turn. The tab appears in the direct list with its own reference and name, such as `D9.1 · scout · Inspect <request ID>`. The tab and session file are recorded for recovery even if launch becomes uncertain. The read-only `subagent_status` tool remains Main's authority for exact identities before any stage or recovery action. If Herdr, the `scout` Role, or the fast route is unavailable, inspection fails without changing the request; Main can use `subagent_status`. Refresh or choose completed/history for older work. Invalid state IDs are warned about and preserved. Outside Git or when isolated inventory is unavailable, direct recovery still works. Browsing does not resume work or change resources; direct **Close/cancel-and-release** is an explicit termination action.
177
+ To select work by label, open `/subagent` without arguments. The menu shows direct records on this session branch and isolated requests in the current canonical Git checkout. This includes requests launched in other sessions. Select an isolated request and **Inspect status and recovery**. This launches a non-focused Herdr tab with the configured `scout` Role on the configured `fast` route. The tab runs a read-access task with the resources of the Role. Its prompt contains bounded saved status, including retained paths, but not the raw launch environment or session transcripts. The analysis stays in that tab and does not queue a Main turn. The tab shows in the direct list with its own reference and name, such as `D9.1 · scout · Inspect <request ID>`. The extension records the tab and session file for recovery, even if the launch becomes uncertain. Before any stage or recovery action, the read-only `subagent_status` tool stays Main's authority for exact identities. If Herdr, the `scout` Role, or the fast route is not available, inspection fails and does not change the request. Main can then use `subagent_status`. For older work, choose Refresh or completed/history. The extension shows a warning for invalid state IDs and preserves them. Direct recovery still works outside Git or when isolated inventory is not available. The browse action does not resume work or change resources. Direct **Close/cancel-and-release** is an explicit termination action.
177
178
 
178
- For a locally active unsealed isolated changeset, choose **Send follow-up instructions** to open a native multiline editor; submission queues the instruction, not a completed revision. Choose **Edit queued instructions** to withdraw *all still-pending* instructions for that selected task before the editor opens. Their full text appears in FIFO order separated by blank lines. Explicit submission queues **one replacement** at the tail. Cancel leaves the withdrawn instructions removed; already-claimed instructions, initial prompts, and automatic corrections cannot be recalled. A worker may seal during editing: failed submission reopens the full submitted text for deliberate editing or copying, never automatically requeues it. Combined text above the single-instruction limit is preserved in the editor but rejected until shortened. Cancelling before withdrawal makes no change. Neither action touches Main's editor or Pi's global Option+Up queue shortcut. No-UI sessions can run `/subagent recover` or use agent tools for individual inspection and recovery.
179
+ For a locally active unsealed isolated changeset, choose **Send follow-up instructions** to open a native multiline editor. A submission queues the instruction, not a completed revision. Choose **Edit queued instructions** to withdraw *all still-pending* instructions for that selected task before the editor opens. Their full text shows in FIFO order, with blank lines between them. An explicit submission queues **one replacement** at the tail. If you cancel, the withdrawn instructions stay removed. You cannot recall already-claimed instructions, initial prompts, or automatic corrections. A worker can seal while you edit. If the submission fails, the editor opens again with the full submitted text, so you can edit or copy it deliberately. The extension never queues it again automatically. The editor preserves combined text above the single-instruction limit, but rejects it until you make it shorter. A cancel before withdrawal makes no change. Neither action touches Main's editor or Pi's global Option+Up queue shortcut. No-UI sessions can run `/subagent recover` or use agent tools for individual inspection and recovery.
179
180
 
180
- A queued follow-up runs after the current turn settles and must produce a new clean commit that passes preliminary checks again. When no queued revision remains, the checked candidate seals immediately; there is no guaranteed post-completion editing window. Late follow-ups fail visibly.
181
+ A queued follow-up runs after the current turn settles. It must produce a new clean commit that passes preliminary checks again. When no queued revision remains, the checked candidate seals immediately. There is no guaranteed time to edit after completion. Late follow-ups fail visibly.
181
182
 
182
183
  ### Isolated recovery
183
184
 
184
- `subagent_integrate validate` acknowledges only after saving the exact generation's validation intent, then runs checks and any final judgment in the background. A later turn cancellation does not cancel acknowledged validation; session replacement or shutdown still stops it. Completion sends a follow-up to the launching session. Use `subagent_status` if delivery is missed; acknowledgement is not passing evidence.
185
+ `subagent_integrate validate` acknowledges only after it saves the validation intent of the exact generation. Then it runs checks and any final judgment in the background. A later turn cancellation does not cancel acknowledged validation. Session replacement or shutdown still stops it. Completion sends a follow-up to the launching session. If delivery is missed, use `subagent_status`. An acknowledgement is not passing evidence.
185
186
 
186
- If validation was interrupted, first prove the old coordinator/check processes have stopped; never take over a live productive lease. Use guarded `reconcile` on the saved generation and `combinedTip`. Missing checks/review never authorize `correct` or `promote`. For a `validation_failed` generation with **no recorded checks, review, or prior combined correction**, `refresh` also accepts identical `expectedMain` and `newMain`. Supply the exact old combined `expectedTip` and the next generation number. Both Main and the retained integration tip must remain exact and clean. This explicitly allocates a new checkout; it does not resume old checks. Restage selected immutable candidates and run fresh full-suite checks and required final judgment before promotion. The old generation stays retained, the two-checkout limit still applies, and correction allowances are not reset. Other failures retain their existing recovery requirements.
187
+ If validation was interrupted, first prove that the old coordinator and check processes stopped. Never take over a live productive lease. Use guarded `reconcile` on the saved generation and `combinedTip`. Missing checks or review never authorize `correct` or `promote`. For a `validation_failed` generation with **no recorded checks, review, or prior combined correction**, `refresh` also accepts identical `expectedMain` and `newMain`. Supply the exact old combined `expectedTip` and the next generation number. Both Main and the retained integration tip must stay exact and clean. This action explicitly allocates a new checkout. It does not resume old checks. Before promotion, restage the selected immutable candidates. Then run fresh full-suite checks and the required final judgment. The old generation stays retained, and the two-checkout limit still applies. Correction allowances do not reset. Other failures keep their existing recovery requirements.
187
188
 
188
- After a coordinator interruption, run `/subagent recover` once. It acquires the repository productive lease before changing any orphaned `pending` or `running` state, preserves unreadable state files and retained candidates, and reports blockers and existing continuations to Main without replaying prompts, merges, promotion, or cleanup. If another productive run holds the lease, it only reads status and reports the live owner; wait rather than taking over. Repeating the command is safe. Main uses `subagent_status` for exact identities and the recovery tools in the interface table above for deliberate decisions; the command never stages or promotes. Direct Herdr tab recovery remains in `/subagent`.
189
+ After a coordinator interruption, run `/subagent recover` one time. It acquires the repository productive lease before it changes any orphaned `pending` or `running` state. It preserves unreadable state files and retained candidates. It reports blockers and existing continuations to Main. It does not replay prompts, merges, promotion, or cleanup. If another productive run holds the lease, the command only reads status and reports the live owner. Wait, and do not take over. You can safely run the command again. Main uses `subagent_status` for exact identities. Main uses the recovery tools in the interface table above for deliberate decisions. The command never stages or promotes. Direct Herdr tab recovery stays in `/subagent`.
189
190
 
190
- If an isolated worker produced no candidate, `subagent_abort` terminates it and removes only proven unchanged, clean owned resources. Cleanup includes ignored-file checks and never discards unique commits. If productive work is still settling, termination is uncertain, or cleanup fails, the request stays aborted with retained-resource evidence. Inspect `subagent_status`, resolve the reported blocker, then repeat `subagent_abort` to reconcile cleanup; it does not replay worker prompts. Dirty work, unique commits, and uncertain allocations remain for deliberate manual recovery.
191
+ ### Prune leftover worktrees
192
+
193
+ Run `/subagent prune` to clean up leftover worker and integration checkouts under `.worktrees/`. Their branches have the name `pi-subagent/subagent-` plus 24 hex characters. The command also cleans up such branches that have no checkout. The review lists each item with its path, branch, and tip. For each item, it also shows why the command can remove the item, or it shows every exact blocker. The command can remove an item only when all of these conditions are true:
194
+
195
+ - Its branch tip is already in Main's HEAD or in the local `origin` default-branch ref. The command does not fetch.
196
+ - Its checkout has no tracked or untracked changes. No merge, rebase, cherry-pick, revert, or bisect is in progress.
197
+ - No unfinished isolated request, in any checkout of this repository, records it. An unreadable state file blocks every item that it mentions.
198
+ - No productive run holds a repository lease.
199
+ - No Herdr pane works inside the checkout. If the command cannot inspect Herdr, it refuses every checkout.
200
+
201
+ One confirmation removes every listed removable item. When the command removes a checkout, it also deletes the ignored files of the checkout, such as `node_modules` or `dist`. Thus, the command includes a checkout with ignored files only after a second confirmation. That confirmation names each ignored entry, and its approval covers all contents of each named entry. If a top-level ignored entry is new or changed, the command skips the checkout. If the full list of ignored entries of a checkout does not fit in that dialog, the command leaves the checkout out of the run. The command blocks checkouts with initialized submodules. It also blocks owned checkouts that are detached or on another branch. Remove them manually. The list shows removable items first. The command never cuts a long list silently. The list ends with `+N more not shown`, and the command does not remove the items that it did not show in that run. Run `/subagent prune` again to review them. The command checks each item again immediately before removal and skips an item that changed. If the session or branch changes during removal, the command skips the remaining steps. The command deletes a branch only if the branch still points at the reviewed tip. Then the command reconciles each aborted request that recorded a removed resource. It uses the same guarded path as a repeated `subagent_abort`, so the cleanup of that request no longer shows as pending. The report lists removed, skipped, and failed items, and each request that the command could not reconcile. Without interactive UI, the command only reports.
202
+
203
+ If an isolated worker produced no candidate, `subagent_abort` terminates it. It removes only proven unchanged, clean owned resources. Cleanup includes ignored-file checks and never discards unique commits. The request stays aborted with retained-resource evidence in these cases: productive work did not settle yet, termination is uncertain, or cleanup fails. Inspect `subagent_status`. Resolve the reported blocker. Then run `subagent_abort` again to reconcile cleanup. This does not replay worker prompts. Dirty work, unique commits, and uncertain allocations stay for deliberate manual recovery.
191
204
 
192
205
  The following details describe exact identity and retained-work requirements.
193
206
 
194
- A checked candidate stays retained if Main is dirty. Staging, combined validation, and promotion require Main to be clean at the pinned committed identity; clean your local edits deliberately before using those actions. No dirty work is replayed or merged automatically. Pass the exact `generation`, candidate (`taskId`, `attempt`, `candidate`) and `expectedTip` identities reported by status to stage; pass the generation and combined `expectedTip` to integrate. If Main advances cleanly before promotion, call `subagent_integrate refresh` with the recorded `expectedMain`, old `expectedTip`, and new clean `newMain`. Main must restage selected immutable candidates into the new generation and rerun combined checks. Dirty or divergent Main blocks refresh. Stale identities fail. If integration allocation was uncertain, `subagent_stage resolve` first proves the owned checkout is still at its base before starting the selected merge; otherwise it retains the intent. At most two integration worktrees may remain unreleased per request; a superseded generation is read-only. To release a superseded *clean* integration checkout, call `subagent_integrate` with `{ id, action: "release", generation, expectedTip }`, using that generation's last staged tip (or integration base when no stage completed). To release a rejected candidate after its worker has terminated and all generations using it are released, pass `{ id, action: "release", generation: <latest generation number, or 1>, taskId, attempt, expectedTip: <candidate tip> }`. Release persists each host/checkout/branch step, verifies ownership and cleanliness (including ignored files), and deletes the branch only if its ref still names the exact tip. Dirty, conflicted, changed, or uncertain resources remain for manual inspection; retry the same release after resolving the obstacle. Released checkouts free a retained slot; abort is available after every retained resource is released. Rejection can invalidate dependent work; if fresh dependent execution is impossible, start a new request instead of forcing promotion. Productive requests have no whole-run wall-clock deadline. Resume does not reset the recorded policy or correction count. Text tasks have two dispatch attempts; inspection failures before dispatch do not consume one. Verification requires passing preliminary checks and any required judgment; it cannot override failed review. Child limits, abort signals, process I/O, status inspection, exact termination, and cleanup retain finite safety bounds.
207
+ A checked candidate stays retained if Main is dirty. Staging, combined validation, and promotion require Main to be clean at the pinned committed identity. Clean your local edits deliberately before you use those actions. The extension does not replay or merge dirty work automatically. To stage, pass the exact `generation`, candidate (`taskId`, `attempt`, `candidate`), and `expectedTip` identities that status reports. To integrate, pass the generation and the combined `expectedTip`. If Main advances cleanly before promotion, call `subagent_integrate refresh` with the recorded `expectedMain`, the old `expectedTip`, and the new clean `newMain`. Main must restage selected immutable candidates into the new generation and run combined checks again. Dirty or divergent Main blocks refresh. Stale identities fail. If integration allocation was uncertain, `subagent_stage resolve` first proves that the owned checkout is still at its base. Then it starts the selected merge. Otherwise, it retains the intent. Each request can keep at most two unreleased integration worktrees. A superseded generation is read-only. To release a superseded *clean* integration checkout, call `subagent_integrate` with `{ id, action: "release", generation, expectedTip }`. As `expectedTip`, use the last staged tip of that generation, or the integration base when no stage completed. A rejected candidate can be released after its worker terminates and all generations that use it are released. To release it, pass `{ id, action: "release", generation: <latest generation number, or 1>, taskId, attempt, expectedTip: <candidate tip> }`. Release persists each host, checkout, and branch step. It verifies ownership and cleanliness, including ignored files. It deletes the branch only if its ref still names the exact tip. Dirty, conflicted, changed, or uncertain resources stay for manual inspection. After you resolve the obstacle, do the same release again. Each released checkout frees a retained slot. Abort is available after every retained resource is released. Rejection can invalidate dependent work. If fresh dependent execution is not possible, start a new request. Do not force promotion. Productive requests have no whole-run wall-clock deadline. Resume does not reset the recorded policy or correction count. Text tasks have two dispatch attempts. Inspection failures before dispatch do not use an attempt. Verification requires passing preliminary checks and any required judgment. It cannot override a failed review. Child limits, abort signals, process I/O, status inspection, exact termination, and cleanup keep finite safety bounds.
195
208
 
196
209
  The extension never pushes, opens a pull request, publishes, deploys, force-cleans recoverable work, or silently falls back to Main.
197
210
 
198
211
  ## Config
199
212
 
200
- pi-subagent owns `~/.pi/agent/config/pi-subagent/config.json`. A missing file silently uses defaults.
213
+ pi-subagent owns `~/.pi/agent/config/pi-subagent/config.json`. If the file is missing, the extension silently uses the defaults.
201
214
 
202
215
  | Name | Description | Values | Default |
203
216
  | --- | --- | --- | --- |
@@ -209,13 +222,13 @@ pi-subagent owns `~/.pi/agent/config/pi-subagent/config.json`. A missing file si
209
222
  | `timeout.maxMinutes` | Ephemeral child hard runtime | Greater than idle and within Node's timer range | `30` |
210
223
  | `roleDefaults` | Optional `extensions`, `tools`, and `skills` arrays merged into every built-in and user Role, with the same validation as Role fields | Object; unknown keys rejected | None |
211
224
 
212
- `roleDefaults` entries come first in each merged list, followed by the Role's own entries, with duplicates removed; merging happens before resource resolution. Use it for the provider extension that every child needs. Limits come only from this global file. Request fields cannot override or replenish them. Existing durable requests keep their recorded policy, while a lower current correction limit can tighten recovery. There is intentionally no whole-run timeout setting.
225
+ In each merged list, `roleDefaults` entries come first, and the entries of the Role follow. The merge removes duplicates and occurs before resource resolution. Use `roleDefaults` for the provider extension that every child needs. Limits come only from this global file. Request fields cannot override or replenish them. Existing durable requests keep their recorded policy, but a lower current correction limit can tighten recovery. Intentionally, there is no whole-run timeout setting.
213
226
 
214
- Malformed or unreadable JSON, unknown keys, and invalid values block delegation with one actionable warning. The file is preserved and never rewritten automatically.
227
+ Malformed or unreadable JSON, unknown keys, and invalid values block delegation with one actionable warning. The extension preserves the file and never rewrites it automatically.
215
228
 
216
229
  ### Roles
217
230
 
218
- Role Markdown lives in `~/.pi/agent/config/pi-subagent/` and requires frontmatter plus a Markdown system prompt.
231
+ Role Markdown is in `~/.pi/agent/config/pi-subagent/`. It requires frontmatter and a Markdown system prompt.
219
232
 
220
233
  | Field | Requirement |
221
234
  | --- | --- |
@@ -227,11 +240,11 @@ Role Markdown lives in `~/.pi/agent/config/pi-subagent/` and requires frontmatte
227
240
  | `mcps` | Optional exact server names from `~/.pi/agent/mcp.json` in Pi's native `mcpServers` format; omitted or `[]` denies MCP access |
228
241
  | body | Required system instructions |
229
242
 
230
- Unknown frontmatter keys are rejected with the file and key named. Each invalid file is reported on its own: valid Roles still load, delegating to the Role an invalid file claims fails with that file's error, and duplicate names make that name unavailable. `/subagent roles` lists the errors. Roles describe responsibility and capabilities, not write access or isolation; each task declares access and each request chooses its mode. A same-named user Role overrides a built-in Role. The package ships `implementer`, `reviewer`, and `scout`. The built-in reviewer includes `git_read`; add it to an existing user reviewer's `tools` array to opt in. No additional extension or Bash access is needed.
243
+ The extension rejects unknown frontmatter keys and names the file and key. It reports each invalid file separately. Valid Roles still load. Delegation to the Role that an invalid file claims fails with the error of that file. Duplicate names make that name unavailable. `/subagent roles` lists the errors. Roles describe responsibility and capabilities, not write access or isolation. Each task declares access, and each request chooses its mode. A same-named user Role overrides a built-in Role. The package ships `implementer`, `reviewer`, and `scout`. The built-in reviewer includes `git_read`. To opt in, add `git_read` to the `tools` array of an existing user reviewer. No additional extension or Bash access is necessary.
231
244
 
232
245
  ### Reviewer Git inspection
233
246
 
234
- `git_read` is an internal child tool, loaded and registered only for Roles that declare it. It accepts structured parameters, not shell commands or arbitrary Git options:
247
+ `git_read` is an internal child tool. The extension loads and registers it only for Roles that declare it. It accepts structured parameters, not shell commands or arbitrary Git options:
235
248
 
236
249
  | `operation` | Parameters and evidence |
237
250
  | --- | --- |
@@ -242,19 +255,19 @@ Unknown frontmatter keys are rejected with the file and key named. Each invalid
242
255
  | `blame` | Required `path`, optional `revision` and paired `startLine`/`endLine` |
243
256
  | `rev-parse` | Optional `revision`; resolves one commit ID |
244
257
 
245
- Revisions accept commit IDs or named refs with optional `~N`/`^N` ancestry, not ranges or reflog expressions. Paths are literal and relative to the assigned cwd; absolute paths, traversal, `.git` components, and option-like paths are rejected. Use named candidate refs rather than assuming Main's checkout contains an isolated candidate. In isolated judgment, the supplied exact patch remains authoritative; Git inspection supplies only referenced context, not a replacement diff. Reviewers still cannot run tests, write, commit, or push, and approve only with exact `PASS`.
258
+ Revisions accept commit IDs or named refs with optional `~N`/`^N` ancestry, not ranges or reflog expressions. Paths are literal and relative to the assigned cwd. The tool rejects absolute paths, traversal, `.git` components, and option-like paths. Use named candidate refs. Do not assume that Main's checkout contains an isolated candidate. In isolated judgment, the supplied exact patch stays authoritative. Git inspection supplies only referenced context, not a replacement diff. Reviewers still cannot run tests, write, commit, or push. They approve only with exact `PASS`.
246
259
 
247
- Each call is cancellable and limited to 30 seconds. Results retain at most the last 32 KiB of UTF-8 output with an explicit `truncated` flag and notice; narrow the path, log count, or blame lines rather than treating truncated evidence as complete. Missing refs/files, non-repositories, invalid UTF-8, timeouts, and Git failures fail visibly. No full-output artifact is written. Git must support `--no-lazy-fetch`; older versions fail rather than allowing network access. Lazy fetch, pager, optional locks, hooks, fsmonitor, external diffs, textconv, and clean/process filters are disabled. Working-tree comparisons therefore use unfiltered bytes and can differ from filter-aware Git output (for example Git LFS). Submodule contents are not inspected. Concurrent edits can still make direct inspection stale.
260
+ You can cancel each call, and each call has a limit of 30 seconds. Results keep at most the last 32 KiB of UTF-8 output, with an explicit `truncated` flag and notice. If output is truncated, make the path, log count, or blame lines narrower. Do not treat truncated evidence as complete. Missing refs or files, non-repositories, invalid UTF-8, timeouts, and Git failures fail visibly. The tool writes no full-output artifact. Git must support `--no-lazy-fetch`. Older versions fail and do not permit network access. The tool disables lazy fetch, pager, optional locks, hooks, fsmonitor, external diffs, textconv, and clean and process filters. Thus, working-tree comparisons use unfiltered bytes and can be different from filter-aware Git output (for example Git LFS). The tool does not inspect submodule contents. Concurrent edits can still make direct inspection stale.
248
261
 
249
262
  ### Child resources
250
263
 
251
- Children run with `--no-extensions`, so a Role must list the extension that registers its route model's provider, and only declared resources plus required internal policy adapters load. `builtin:<name>` entries load Pi built-in extensions (`builtin:codemode`, `builtin:tool-search`, `builtin:llama.cpp`) explicitly; `builtin:mcp` is rejected because it would connect every configured server and bypass `mcps`. Extension tools registered with `codemode` or `deferred` exposure stay callable from scripts but are declared to the model only when `tools` names them.
264
+ Children run with `--no-extensions`. Thus, a Role must list the extension that registers the provider of its route model. Only declared resources and required internal policy adapters load. `builtin:<name>` entries explicitly load Pi built-in extensions (`builtin:codemode`, `builtin:tool-search`, `builtin:llama.cpp`). The extension rejects `builtin:mcp`, because it would connect every configured server and bypass `mcps`. Scripts can still call extension tools registered with `codemode` or `deferred` exposure. But the model gets their declarations only when `tools` names them.
252
265
 
253
- Main's nested calls pass admission by their own tool names, not by script or ancestor ownership. Each potentially-writing call, including Bash, prevents a direct writer from starting until that call returns a result or execution end. Main calls do not block each other. A script's completion does not release its active nested calls; a nested call can still pass admission after its parent ends if no direct writer owns the checkout.
266
+ Main's nested calls pass admission by their own tool names, not by script or ancestor ownership. Each potentially-writing call, including Bash, prevents the start of a direct writer until that call returns a result or execution end. Main calls do not block each other. The completion of a script does not release its active nested calls. A nested call can still pass admission after its parent ends if no direct writer owns the checkout.
254
267
 
255
- To let a Role run `codemode` scripts, list `builtin:codemode` in `extensions` and `codemode` in `tools`; both are required, because the built-in registers the tool inactive and the Role tool policy activates it. `codemode` alone in `tools` fails the launch as an unavailable tool. Pi's `codemode.mode` and `codemode.inlineBudget` settings apply in the child unchanged, so the Role never forces codemode-only operation, and MCP configuration never activates `codemode` in a child. Scripts can call only the Role's active tools plus `codemode`/`deferred` extension and MCP tools; inactive base tools such as `bash` stay unreachable.
268
+ To let a Role run `codemode` scripts, list `builtin:codemode` in `extensions`. Also list `codemode` in `tools`. Both are required, because the built-in registers the tool as inactive and the Role tool policy activates it. If `tools` has `codemode` alone, the launch fails with an unavailable tool. Pi's `codemode.mode` and `codemode.inlineBudget` settings apply in the child without change. Thus, the Role never forces codemode-only operation. MCP configuration never activates `codemode` in a child. Scripts can call only the active tools of the Role, plus `codemode` and `deferred` extension and MCP tools. Inactive base tools such as `bash` stay unreachable.
256
269
 
257
- The servers named in `mcps` come from the global `~/.pi/agent/mcp.json` only. In a Role that activates `codemode`, each selected server keeps its configured `exposure` and `toolExposure` (Pi's default `codemode` exposure makes tools callable from scripts without declaring them, `direct` declares them, `deferred` tools stay reachable from scripts, `hidden` tools stay unreachable), and an invalid exposure value fails the launch. In every other Role, selected servers are forced to `direct` exposure and `toolExposure` is ignored, because that child cannot reach undeclared tools. Declared tools appear to the model as `mcp__<server>__<tool>` once the server connects. Pi replaces `-` in server and tool names with `_` and appends a hash suffix when two tool names collide or a name exceeds 64 characters. Each selected entry is checked against Pi's documented `mcpServers` rules before launch, in Main and again in the child: a disabled, malformed, or unknown-transport entry, an invalid server name, two selected names that differ only in `-` and `_`, `auth.provider` over plain HTTP outside `localhost`, `127.0.0.1`, or `[::1]`, or a non-HTTPS `oauth.authServerMetadataUrl` outside those hosts fails the launch with the file path and field. The file is never modified; fix the entry or choose another server. Valid `auth`, `oauth`, and `description` fields are passed through so Pi's native provider auth and OAuth apply. `oauth.clientRegistration` must be `dcr` or `cimd`. A `cimd` entry cannot use `clientId` or `clientName`, and any `callbackUrl` must use localhost or 127.0.0.1 with path `/callback`. Project MCP overrides remain ignored: a project cannot change the Role's global server allowlist or exposure. Missing Skills, tools, MCP servers, Roles, or routes fail before productive work starts. Main-only delegation and recovery tools plus `ask_question` are excluded from children.
270
+ The servers named in `mcps` come only from the global `~/.pi/agent/mcp.json`. In a Role that activates `codemode`, each selected server keeps its configured `exposure` and `toolExposure`. Pi's default `codemode` exposure makes tools callable from scripts without declarations. `direct` declares them. `deferred` tools stay reachable from scripts, and `hidden` tools stay unreachable. An invalid exposure value fails the launch. In every other Role, the extension forces selected servers to `direct` exposure and ignores `toolExposure`, because that child cannot reach undeclared tools. Declared tools show to the model as `mcp__<server>__<tool>` after the server connects. Pi replaces `-` in server and tool names with `_`. Pi appends a hash suffix when two tool names collide or a name is longer than 64 characters. Before launch, the extension checks each selected entry against Pi's documented `mcpServers` rules, in Main and again in the child. The launch fails with the file path and field for each of these conditions: a disabled, malformed, or unknown-transport entry; an invalid server name; two selected names that differ only in `-` and `_`; `auth.provider` over plain HTTP outside `localhost`, `127.0.0.1`, or `[::1]`; or a non-HTTPS `oauth.authServerMetadataUrl` outside those hosts. The extension never modifies the file. Fix the entry or choose another server. The extension passes valid `auth`, `oauth`, and `description` fields through, so Pi's native provider auth and OAuth apply. `oauth.clientRegistration` must be `dcr` or `cimd`. A `cimd` entry cannot use `clientId` or `clientName`. Any `callbackUrl` must use localhost or 127.0.0.1 with path `/callback`. The extension still ignores project MCP overrides. A project cannot change the global server allowlist or exposure of the Role. Missing Skills, tools, MCP servers, Roles, or routes fail before productive work starts. Main-only delegation and recovery tools and `ask_question` are excluded from children.
258
271
 
259
272
  ## API
260
273
 
@@ -273,14 +286,14 @@ The servers named in `mcps` come from the global `~/.pi/agent/mcp.json` only. In
273
286
 
274
287
  Role route and launch helpers require only model routing fields and `isProjectTrusted()` from the context, plus `cwd` for configured package resources. They do not require UI methods.
275
288
 
276
- `createEphemeralSubagentExecutor` accepts global concurrency, turn/token, idle, and hard-runtime policy. Queued time consumes no child timeout. A run resolves resources only after receiving its permit and accepts abort, output, token, and activity callbacks. Output and diagnostics are bounded. A child that reports `agent_settled.aborted: true` returns a failure result with `stopReason: "aborted"`, even if its process exits with code 0. Collected output, usage, and child error text are kept. `aborted: false` does not prove provider success. A parent abort or timeout still rejects with `EphemeralSubagentError`.
289
+ `createEphemeralSubagentExecutor` accepts global concurrency, turn/token, idle, and hard-runtime policy. Queued time consumes no child timeout. A run resolves resources only after it receives its permit. It accepts abort, output, token, and activity callbacks. Output and diagnostics are bounded. A child that reports `agent_settled.aborted: true` returns a failure result with `stopReason: "aborted"`, even if its process exits with code 0. The executor keeps collected output, usage, and child error text. `aborted: false` does not prove provider success. A parent abort or timeout still rejects with `EphemeralSubagentError`.
277
290
 
278
291
  See [Orchestration and package-author API](./docs/orchestration.md) for the detailed contracts and recovery model.
279
292
 
280
293
  ## State and storage
281
294
 
282
- Durable state is private under `config/pi-subagent/state/`. Isolated worker Pi sessions are precreated with mode `0600` under `config/pi-subagent/leases/` without changing the worker's umask. Reads stop at the 16 MiB limit even if the session grows; an oversized session requires inspection rather than being accepted as completion evidence. Sessions are removed only after exact worker-tab cleanup; retained sessions may contain prompts and answers, so do not share them. State v5 rejects v4 and older files rather than migrating them; retain the old state and recover its work manually.
295
+ Durable state is private under `config/pi-subagent/state/`. The extension creates isolated worker Pi sessions in advance with mode `0600` under `config/pi-subagent/leases/`. It does not change the umask of the worker. Reads stop at the 16 MiB limit, even if the session grows. An oversized session requires inspection, and the extension does not accept it as completion evidence. The extension removes sessions only after exact worker-tab cleanup. Retained sessions can contain prompts and answers, so do not share them. State v5 rejects v4 and older files and does not migrate them. Keep the old state and recover its work manually.
283
296
 
284
297
  ## Limits and recovery
285
298
 
286
- Role extensions and MCP servers are trusted executable code, not a sandbox. Select the smallest resource set. Direct tasks may use declared write-capable resources in Main's checkout only within the authorized scope. Checkout admission coordinates this Main's Pi calls, not other Pi sessions, trusted extension lifecycle code, or external processes. Concurrent changes can make a direct worker's read stale. Retained-work reports identify exact resources for deliberate recovery.
299
+ Role extensions and MCP servers are trusted executable code, not a sandbox. Select the smallest resource set. Direct tasks can use declared write-capable resources in Main's checkout only in the authorized scope. Checkout admission coordinates the Pi calls of this Main. It does not coordinate other Pi sessions, trusted extension lifecycle code, or external processes. Concurrent changes can make the read of a direct worker stale. Retained-work reports identify exact resources for deliberate recovery.
@@ -922,7 +922,9 @@ export class HerdrHostRuntime {
922
922
  if (SETTLED_AGENT_STATES.has(lifecycle.status)) {
923
923
  try {
924
924
  if (await this.workerTurnFinished(allocation, prompt)) {
925
- return { outcome: "blocked", diagnostic: "Worker finished without an exact changed clean committed candidate." };
925
+ return inspection.valid && !inspection.clean
926
+ ? { outcome: "uncommitted", diagnostic: "Worker finished with uncommitted changes in its owned worktree." }
927
+ : { outcome: "blocked", diagnostic: "Worker finished without an exact changed clean committed candidate." };
926
928
  }
927
929
  }
928
930
  catch (error) {
@@ -3,7 +3,7 @@ import { tmpdir } from "node:os";
3
3
  import { basename, join } from "node:path";
4
4
  import { runGit } from "./git-process.js";
5
5
  import { CheckedGitRuntime } from "./git-runtime.js";
6
- import { createChildWorktree, inspectWorktreeDirty, WorktreeSetupError } from "./worktree.js";
6
+ import { createChildWorktree, inspectWorktreeDirty, OWNED_BRANCH_PATTERN, WORKTREES_DIRNAME, WorktreeSetupError } from "./worktree.js";
7
7
  import { checkBatchPasses, reviewEvidencePasses, sameIdentity, } from "./schema.js";
8
8
  const oidPattern = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
9
9
  const diagnostic = (value) => value.trim().slice(0, 500);
@@ -52,8 +52,8 @@ async function ownershipMetadata(root, info, signal) {
52
52
  const repoRoot = await realpath(primary.slice("worktree ".length));
53
53
  if (await realpath(await requireGit(["rev-parse", "--show-toplevel"], root, signal)) !== canonical
54
54
  || info.repoRoot !== repoRoot || info.cwd !== info.path
55
- || !/^pi-subagent\/subagent-[0-9a-f]{24}$/.test(info.branch)
56
- || info.path !== join(repoRoot, ".worktrees", basename(info.branch))) {
55
+ || !OWNED_BRANCH_PATTERN.test(info.branch)
56
+ || info.path !== join(repoRoot, WORKTREES_DIRNAME, basename(info.branch))) {
57
57
  throw new Error("Git worktree is not an exact owned checkout of Main.");
58
58
  }
59
59
  }
@@ -0,0 +1,50 @@
1
+ import { type GitRunner } from "./git-process.ts";
2
+ /** One reviewed pi-subagent branch, with its registered checkout when one exists. */
3
+ export interface PruneItem {
4
+ branch: string;
5
+ tip: string;
6
+ path?: string;
7
+ prunable: boolean;
8
+ /** Why the item is prunable, or every exact blocker. */
9
+ reason: string;
10
+ /** Ignored entries as Git collapses them; removing the checkout deletes them. */
11
+ ignored: readonly string[];
12
+ }
13
+ export interface PruneOwnership {
14
+ /** The request or state that may still own a resource name (`subagent-<hex>`), if any. */
15
+ owner(name: string): string | undefined;
16
+ /** The live productive lease that blocks pruning, if any. */
17
+ lease?: string;
18
+ }
19
+ export interface PruneGuards {
20
+ /** Ownership and lease evidence across the repository's existing checkouts. */
21
+ ownership(checkouts: readonly string[]): Promise<PruneOwnership>;
22
+ /** Every Herdr pane; rejects when Herdr cannot be inspected. */
23
+ panes(): Promise<readonly {
24
+ paneId: string;
25
+ cwds: readonly string[];
26
+ }[]>;
27
+ /** The confirming session still owns the command; checked immediately before each removal step. */
28
+ current?(): boolean;
29
+ }
30
+ export type PruneRemoval = {
31
+ outcome: "removed" | "skipped" | "failed";
32
+ item: PruneItem;
33
+ reason: string;
34
+ };
35
+ /** Every pane in every Herdr workspace with its shell and foreground working directories. */
36
+ export declare function listHerdrPanes(cwd: string): Promise<{
37
+ paneId: string;
38
+ cwds: string[];
39
+ }[]>;
40
+ /** Read-only review of every pi-subagent worker or integration branch and its checkout. */
41
+ export declare function inspectPruneCandidates(root: string, mainCwd: string, guards: PruneGuards, run?: GitRunner): Promise<PruneItem[]>;
42
+ /**
43
+ * Re-verify each reviewed prunable item immediately before removing its checkout, then delete its
44
+ * branch only at the reviewed tip. Ignored entries are deleted only when `allowIgnored` and all of
45
+ * them were in the review. Returns the repository checkouts that still exist.
46
+ */
47
+ export declare function removePruneCandidates(root: string, mainCwd: string, reviewed: readonly PruneItem[], allowIgnored: boolean, guards: PruneGuards, run?: GitRunner): Promise<{
48
+ removals: PruneRemoval[];
49
+ checkouts: string[];
50
+ }>;