@nredd/pi-subagents 0.22.0-nredd.8 → 0.22.0-nredd.9

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 (50) hide show
  1. package/README.md +136 -55
  2. package/package.json +14 -11
  3. package/src/abortable.ts +1 -1
  4. package/src/agent-file-toggle.ts +0 -1
  5. package/src/agent-manager.ts +360 -103
  6. package/src/agent-runner.ts +121 -63
  7. package/src/child-context.ts +19 -1
  8. package/src/completion-outbox.ts +22 -3
  9. package/src/cross-extension-rpc.ts +10 -6
  10. package/src/custom-agents.ts +2 -1
  11. package/src/default-agents.ts +19 -17
  12. package/src/detached-results.ts +18 -2
  13. package/src/enterprise-local.ts +2 -1
  14. package/src/error-message.ts +11 -0
  15. package/src/git-path.ts +66 -0
  16. package/src/group-join.ts +4 -10
  17. package/src/index.ts +731 -231
  18. package/src/invocation-config.ts +1 -5
  19. package/src/mention-clone.ts +4 -3
  20. package/src/model-resolver.ts +51 -14
  21. package/src/model-selection.ts +17 -6
  22. package/src/model-tiers.ts +5 -0
  23. package/src/nested-tools.ts +80 -21
  24. package/src/notification-result.ts +118 -0
  25. package/src/plan-mode-children.ts +176 -0
  26. package/src/prompts.ts +7 -2
  27. package/src/quota-admission.ts +16 -6
  28. package/src/resource-loader.ts +20 -6
  29. package/src/schedule.ts +65 -19
  30. package/src/settings.ts +21 -1
  31. package/src/skill-loader.ts +244 -94
  32. package/src/status-note.ts +28 -5
  33. package/src/types.ts +52 -2
  34. package/src/ui/agent-mention.ts +5 -2
  35. package/src/ui/agent-widget.ts +41 -22
  36. package/src/ui/conversation-viewer.ts +4 -2
  37. package/src/ui/fallback-flag.ts +28 -4
  38. package/src/ui/fleet-list.ts +89 -54
  39. package/src/ui/notification-view.ts +4 -1
  40. package/src/ui/workflow-card.ts +1 -8
  41. package/src/ui/workflow-dialog.ts +31 -18
  42. package/src/workflow/host.ts +4 -5
  43. package/src/workflow/meta.ts +7 -4
  44. package/src/workflow/progress.ts +4 -3
  45. package/src/workflow/runtime.ts +23 -7
  46. package/src/workflow/saved.ts +2 -4
  47. package/src/workflow/task.ts +12 -7
  48. package/src/workflow/tool-description.ts +2 -2
  49. package/src/workflow/worker-source.ts +6 -2
  50. package/src/worktree.ts +309 -19
package/README.md CHANGED
@@ -14,14 +14,14 @@ https://github.com/user-attachments/assets/8685261b-9338-4fea-8dfe-1c590d5df543
14
14
 
15
15
  ## Features
16
16
 
17
- - **Claude Code look & feel** — same tool names, calling conventions, and UI patterns (`Agent`, `get_subagent_result`, `steer_subagent`) — feels native
17
+ - **Claude Code look & feel** — same tool names, calling conventions, and UI patterns (`Agent`, `get_subagent_result`, `steer_subagent`, `stop_subagent`) — feels native
18
18
  - **Parallel background agents** — spawn multiple agents that run concurrently with automatic queuing (configurable concurrency limit, default 10) and completion notifications that reach the parent at its next boundary, several at once when they finish together
19
19
  - **Live widget UI** — persistent above-editor widget with animated spinners, live tool activity, token counts, and colored status icons. Configurable via `/agents → Settings → Widget`: `all` (every agent), `background` (default — hides foreground runs, which already render inline as the `Agent` tool result), or `off`
20
- - **FleetView** — one-line summary below the editor by default; `ctrl+shift+a` expands the Claude Code-style navigable list of `main` + every running subagent (earliest-launched first). Press `↓` (or `←`) at an empty prompt to jump in, `↑`/`↓` to move the selection, `Enter` to open the selected agent's live, auto-updating conversation, `Esc` to return. Finished agents linger briefly before dropping out, and a viewer stays open through completion so you can read the final output. Toggle via `/agents → Settings → Fleet view`
20
+ - **FleetView** — one-line summary below the editor by default; `ctrl+shift+a` expands the Claude Code-style navigable list of `main` + every running subagent (earliest-launched first). `ctrl+shift+k` twice within 3s stops every running agent and workflow. Press `↓` (or `←`) at an empty prompt to jump in, `↑`/`↓` to move the selection, `Enter` to open the selected agent's live, auto-updating conversation, `Esc` to return. Finished agents linger briefly before dropping out, and a viewer stays open through completion so you can read the final output. Toggle via `/agents → Settings → Fleet view`
21
21
  - **Conversation viewer** — select any agent in `/agents` to open a live-scrolling overlay of its full conversation (auto-follows new content, scroll up to pause). In pi's fullscreen mode it is a borderless, full-terminal observer with mouse support. Steer a running agent inline by pressing `Enter` to open a composer, typing, then `Enter` to send (`Esc` or an empty submit returns) — the message appears as a user message and redirects the agent after its current tool. Press `b` on a running foreground `Agent` call to hand that run to the background (see [Sending a foreground agent to the background](#sending-a-foreground-agent-to-the-background)). Stop a still-running agent by pressing `x` (then `x` again to confirm) — steer and stop both work for background agents too. Assistant text renders as Markdown; `m` cycles that between off, assistant-only and everything (see [Viewer markdown](#persistent-settings))
22
22
  - **Custom agent types** — define agents in `.pi/agents/<name>.md` or `.agents/agents/<name>.md` (project) or globally, with YAML frontmatter: custom system prompts, model selection, thinking levels, tool restrictions, and Claude Code-compatible colored name badges
23
23
  - **Package-shipped agents** — a pi package can declare its own subagents (and workflow scripts) with `"pi": { "subagents": { "agents": ["./agents"] } }` in its `package.json`, so `pi install` is the whole setup: no copying files into `.pi/agents/` on every machine or container start. Only packages pi has actually installed are read (never `node_modules`), package definitions rank below every agent you wrote, and `/agents` shows them read-only with Eject and Disable. Narrow or switch it off with `packageAgents` / `packageWorkflows`. **[Full guide](https://github.com/tintinweb/pi-subagents/blob/master/docs/packages.md)**
24
- - **Nested subagents** — opt-in, default-off delegation: a custom agent that sets `allowed_subagents` gets its own ownership-scoped `Agent`, `get_subagent_result`, and `steer_subagent` tools, depth-capped from the main session (default 2). It can control only its own children, they are stopped when it finishes, and their transcripts and token spend roll up to it. The allowlist is a privilege boundary — a child runs with its own tools, so pick it as carefully as `tools:` itself
24
+ - **Nested subagents** — opt-in, default-off delegation: a custom agent that sets `allowed_subagents` gets its own ownership-scoped `Agent`, `get_subagent_result`, `steer_subagent`, and `stop_subagent` tools, depth-capped from the main session (default 2). It can control only its own children, they are stopped when it finishes, and their transcripts and token spend roll up to it. The allowlist is a privilege boundary — a child runs with its own tools, so pick it as carefully as `tools:` itself
25
25
  - **Agent mentions** — subagents are first-class: type `@explore also check the RPC path` at the prompt and it goes to that agent instead of the main model, without a word of it entering the chat. One syntax covers the whole lifecycle — message it while it runs, resume it once it has finished, reopen its session from disk long after that, or start it if it never ran. Mentioning an agent that isn't running spawns it through an off-screen clone of the conversation, so it gets Claude Code's context-written prompt and a real `Agent` tool call without a word of it reaching the chat; `direct` mode starts it here from your text instead, with no model call at all. The orchestrator can `name` an agent so you address it as `@auth-audit`, and handles work in `steer_subagent`/`get_subagent_result` too. `@` completes live agents, resumable ones, and startable types alongside pi's file completion; `@main` forces text back to the main model. Toggle via `/agents → Settings → Agent mentions`
26
26
  - **Scripted workflows** — a `SubagentWorkflow` tool that runs a deterministic JavaScript script orchestrating many subagents: `agent()`, `parallel()`, `pipeline()`, `phase()`, `log()` and `args`, with a pure-literal `meta` block declaring the phases. `pipeline()` has no barrier between stages, so one item can be in a later stage while another is still in the first — unlike `parallel()`, which idles every fast agent until the slowest finishes. Runs in the background with a live card, inspectable via `/agents → Workflows` or by selecting the run in FleetView. `agent()` also takes `gate: "npm test"` to verify a child by running a command (inside its worktree, when isolated) rather than asking another model, and `resume: "<label>"` to continue a child instead of re-paying its context. Scripts run in a `node:vm` sandbox on a worker thread where `Date.now()`, `Math.random()` and `eval` throw. On by default, but it stands down for company: if another extension already provides a `Workflow` or `SubagentWorkflow` tool, this one warns and disables itself for the session rather than offering the model two orchestrators. Pin it either way with `"workflowsEnabled"` in `subagents.json` or `/agents → Settings → Workflows`. A script written for Claude Code's `Workflow` tool runs here unchanged: same globals, `schema` returns a validated object exactly as it does there, `budget` is present and always reports no token target (pi has no such directive) so its `budget.total`-guarded patterns still take the branch they were written for, and nested `workflow()` composes saved workflows one level deep. **[Full guide](https://github.com/tintinweb/pi-subagents/blob/master/docs/workflows.md)**
27
27
  - **Mid-run steering** — inject messages into running agents to redirect their work without restarting
@@ -32,8 +32,9 @@ https://github.com/user-attachments/assets/8685261b-9338-4fea-8dfe-1c590d5df543
32
32
  - **Context inheritance** — optionally fork the parent conversation into a sub-agent so it knows what's been discussed
33
33
  - **Persistent agent memory** — three scopes (project, local, user) with automatic read-only fallback for agents without write tools
34
34
  - **Git worktree isolation** — run agents in isolated repo copies; changes auto-committed to branches on completion
35
- - **Skill preloading** — inject named skills into agent system prompts, discovered from `.pi/skills/`, `.agents/skills/`, and global locations (Pi-standard `<name>/SKILL.md` directory layout supported)
35
+ - **Skill preloading** — inject named skills into agent system prompts through Pi's configured resource discovery, including installed package skills; project resources participate only after project trust and package/user precedence stays Pi-native
36
36
  - **Tool denylist** — block specific tools via `disallowed_tools` frontmatter
37
+ - **Stops are reported** — a run that settles while the session's own agents are still running or queued shows the count and what is armed to wake the parent, e.g. `Run ended with 3 agents running or queued, armed: 2 agent completion notices wake the parent; 1 foreground or RPC-spawned agent will not`. Background agents (`Agent` with `run_in_background`, `@handle` mentions, scheduled jobs) wake it in every join mode; foreground agents and RPC spawns without `isBackground` notify nobody, and when only those are pending the notice is a warning with `armed: no agent completion notice wakes the parent`. It speaks for agents only, beside the monitor and todo notices. Two edges: after a session switch the notice is spooled and lands when that session is live again, and a held notice can be dropped when `pi -p` tears down. Nested and workflow children are not counted
37
38
  - **Styled completion notifications** — background agent results render as themed, compact notification boxes (icon, stats, result preview) instead of raw XML. Expandable to show full output. Group completions render each agent individually
38
39
  - **Event bus** — lifecycle events (`subagents:created`, `started`, `completed`, `failed`, `steered`, `compacted`) and a per-message spend ledger (`subagents:usage`) emitted via `pi.events`, enabling other extensions to react to sub-agent activity
39
40
  - **Cross-extension RPC** — other pi extensions can spawn, stop, steer, and join subagents via the `pi.events` event bus (`subagents:rpc:ping`, `subagents:rpc:spawn`, `subagents:rpc:stop`, `subagents:rpc:consume`, `subagents:rpc:steer`). Standardized reply envelopes with protocol versioning. Emits `subagents:ready` on session start. **[Full reference](https://github.com/tintinweb/pi-subagents/blob/master/docs/rpc.md)**
@@ -103,14 +104,26 @@ holds, workflow execution timers, child-shutdown watchdogs, detached-spool filen
103
104
  auto-hide and schedule-store lock retries retain native time. Queued result waits use the manager's
104
105
  start gate when present; legacy queued resumes retain the clock-driven fallback.
105
106
 
106
- `npm run check` runs Biome, TypeScript, the full Vitest suite and `test:bun`. The four session scenario
107
+ `npm test` and `test:coverage` launch Vitest through the shared
108
+ `scripts/run-vitest.ts` launcher. It gives the child a temporary `HOME`, `USERPROFILE`,
109
+ `XDG_CONFIG_HOME`, `PI_CODING_AGENT_DIR` and `PI_SUBAGENTS_CONFIG_DIR` before imports,
110
+ then removes that home on exit. Operator `agentModels`/model-tier policy must not affect
111
+ fixture dispatch; runtime tier defaults and operator files remain unchanged. The command-level
112
+ regression runs the real `npm test` against a hostile fixture home with an unavailable
113
+ `@default` tier and checks the existing Agent boundary assertions. `test:watch` and
114
+ `test:e2e` retain their operator environment. The explicit live smoke command
115
+ `PI_E2E_LIVE=1 npm run test:e2e` needs the operator's configured Pi login; it is not a
116
+ hermetic verification gate. Without that opt-in, `test:e2e` runs the scripted faux suite.
117
+
118
+ `npm run check` runs Biome, TypeScript, the full Vitest suite and `test:bun`, the same full suite on Bun 1.4.4 through
119
+ `scripts/run-vitest.ts bun` (no native `bun test` subset). The four session scenario
107
120
  files (`test/{session-scenarios,fleet-session,viewer-session,worktree-isolation-e2e}.test.ts`) run under
108
- both Vitest and Bun 1.4.4 on core `v1.3.0-nredd.8` release tarballs. They use exported `createHarness`
121
+ both engines on core `v1.3.0-nredd.10` release tarballs. They use exported `createHarness`
109
122
  and `createInteractiveHarness`/`VirtualTerminal`, real child sessions and git, one injected extension
110
123
  clock, promise-held tools/streams and raw SGR mouse input. Homes, settings and provider credentials
111
124
  are fixtures; no installed pi, tmux or real-clock sleeps are needed. Existing old-host strict-mode
112
125
  coverage explicitly removes the optional SDK export; the shared faux runtime uses the host's real
113
- `withBeforeProviderRequest` facade. The full older suite remains Vitest-only. Maintainer guidance on public index callback
126
+ `withBeforeProviderRequest` facade. Maintainer guidance on public index callback
114
127
  coverage and retained safety guards is [here](../../docs/subagents-index-continuation.md).
115
128
 
116
129
  ### Other hosts
@@ -134,7 +147,7 @@ Agent({
134
147
  })
135
148
  ```
136
149
 
137
- Agents run in the background by default: the call returns an ID immediately and notifies you on completion, carrying a preview of the result (use `get_subagent_result` for the full text). Pass `run_in_background: false` to block until the agent finishes and get its full output inline; while it blocks, `ctrl+b` hands it to the background (see below).
150
+ Agents run in the background by default: the call returns an ID immediately and notifies you on completion, carrying the whole result up to [`notificationResultChars`](#completion-notice-results) (16k chars by default; past that the head and tail, with `get_subagent_result` for the rest). Pass `run_in_background: false` to block until the agent finishes and get its full output inline; while it blocks, `ctrl+b` hands it to the background (see below).
138
151
 
139
152
  ### Sending a foreground agent to the background
140
153
 
@@ -170,7 +183,7 @@ Schedule formats:
170
183
 
171
184
  When a schedule fires, the spawn runs in background and its completion notification arrives the same way as a manually-spawned background agent's — your parent agent reasons about the result the same way. Model selection runs at fire time with the manual-dispatch precedence: stored `model` request (including a captured configured tier), current `agentModels[type]`, `agentModels["*"]`, definition, parent. A stored explicit model that no longer resolves or is out of scope fails the job instead of being replaced. Tier jobs retain the `@tier` request, including a configured tier, and re-resolve its binding at fire time. Other quota-parked jobs retain the original explicit input, not a snapshot of the configured or parent model. The job stores the resolved thinking level when you create it: an explicit `Agent({ thinking })` wins over the agent definition then, while an omitted value keeps the definition's default. Editing the agent file later does not replace that stored level.
172
185
 
173
- **Session switches.** `/new`, `/resume`, `/fork` and `/reload` do not stop running background agents. They keep showing in the fleet list of the reloaded session while they run (open, stop and steer work from there), finish on their own, and the session that spawned them gets the completion notification when you return to it (immediately if it is already open). Held notices survive the switch too: unread results are transferred to the session spool and re-enter the same boundary outbox on return, without repeating their history entries. The preview is in the notification; the full output is in the transcript file it names, since `get_subagent_result` cannot reach an agent from a previous session. Quitting pi still aborts everything. A step that routes through the old session's virtual model (`router/*`) while the switch is in flight is re-prompted up to three times with backoff; if it never recovers the agent ends `error` with `interrupted by a session reload` and its last tool calls and output as the result, not `No output`. Usage and compaction observers bound to the old session are dropped rather than failing the run (any other observer error is logged once and also kept from failing it), and the structured-output retry and forced final report are not attempted on a runtime that is still stale, so `subagents:usage` and `subagents:compacted` stop for an agent detached by a switch.
186
+ **Session switches.** `/new`, `/resume`, `/fork` and `/reload` do not stop running background agents. They keep showing in the fleet list of the reloaded session while they run (open, stop and steer work from there), finish on their own, and the session that spawned them gets the completion notification when you return to it (immediately if it is already open). Held notices survive the switch too: unread results are transferred to the session spool and re-enter the same boundary outbox on return, without repeating their history entries. The notification carries the result as a live one would, up to [`notificationResultChars`](#completion-notice-results); past that the full output is in the transcript file it names, since `get_subagent_result` cannot reach an agent that finished in a previous session. While a survivor is still running, `get_subagent_result` and `steer_subagent` reach it by id, handle or name from the reloaded session. Quitting pi still aborts everything. `router/*` routing is unaffected by the switch. A step that routes through another extension's virtual model bound to the old session is re-prompted up to three times with backoff; if it never recovers the agent ends `error` with `interrupted by a session reload` and a bounded summary of its latest tool activity, not `No output`. Earlier prose is labeled as progress if tools ran after it. The activity summary names only built-in tools (other names become `other tool`) and completion status, never tool arguments or output; inspect the transcript to recover artifacts. Usage and compaction observers bound to the old session are dropped rather than failing the run (any other observer error is logged once and also kept from failing it), and the structured-output retry and forced final report are not attempted on a runtime that is still stale, so `subagents:usage` and `subagents:compacted` stop for an agent detached by a switch.
174
187
 
175
188
  Schedules are **session-scoped**: they reset on `/new` and restore on `/resume`. List and cancel via `/agents → Scheduled jobs` (creation is the `Agent` tool's job — there is no parallel manual-create wizard). Storage at `<cwd>/.pi/subagent-schedules/<sessionId>.json` with PID-based file locking for cross-instance safety.
176
189
 
@@ -241,6 +254,8 @@ Acknowledged means the existing `resultConsumed` flag was set by inline result d
241
254
 
242
255
  Enabled quota-parked schedules awaiting dispatch count as parked even with no agent record, and clear when dispatched, disabled or removed.
243
256
 
257
+ **Stop all:** `ctrl+shift+k` while the prompt editor has focus (the list collapsed or expanded) shows `Press ctrl+shift+k again within 3s to stop every running agent and workflow.`; a second press inside those three seconds stops every running or queued top-level agent, background survivor and workflow run, and says how many. Nothing else is touched: a later press re-arms instead, an auto-repeat never confirms, and a scheduled job that has not fired is not running. Claude Code's chord is `ctrl+x ctrl+k`, but pi binds `ctrl+x` to copy. It is the same stop as `/agents -> Stop all running`, and needs FleetView enabled.
258
+
244
259
  Failed/parked/paused-workflow/fallback counts lead in a shared, uncut flag; at narrow widths these compact to `[E1 P2 B1 F1]` (failed, parked, blocked/paused, fallback). Compact counts above 99 read `99+`; spaces yield before alerts, so all four still fit at 20 columns (`[E99+P99+B99+F99+]`). Below 20 columns alerts yield in reverse priority (fallback, paused, parked), never ahead of the failure flag; if even its count cannot fit, the marker is `!`, never the misleading `[fb]`. Cost uses record-borne estimates only when `showCost` is on; it is the sum for the agents currently summarized, not a session ledger, and excludes workflow-container spend. Narrow lines drop trailing detail and the hint before leading alerts. An empty fleet still hides entirely; the standalone summary renderer has a one-line zero-count form.
245
260
 
246
261
  At an empty prompt, `↓` or `←` temporarily reveals the list and starts navigation. `Esc`, selecting `main`, or typing returns to the explicit disclosure choice; dialogs and conversation overlays keep their keys. `ctrl+shift+a` while navigating collapses and returns to the editor. The separate above-editor activity widget (`widgetMode`) and transcript/result/notice renderers are unchanged.
@@ -257,8 +272,8 @@ Expanded, the existing navigable list renders **below** the editor:
257
272
  ↓ 3 more: 2 running, 1 failed
258
273
  ```
259
274
 
260
- Running [workflows](#subagentworkflow) appear as a single `workflow` row above the agents, carrying their agent counts in place of a description. `Enter` on one opens the same two-pane inspector `/agents → Workflows` does, rather than a conversation overlay. A run's own agents are *not* listed separately — they belong to the run, which reports for them, so they are filtered out of the fleet list, the above-editor widget, the `/agents` menus and `@handle` resolution exactly as nested children are. They are also outside the `maxConcurrent` pool: the run has its own concurrency cap, and routing a fan-out through the session pool as well would let one workflow starve everything else. The agents are ordered earliest-launched first, and only agents you can actually open, or outstanding failures you can inspect, are shown (pending/queued agents with no session yet appear once they start). At an **empty prompt**, press `↓` (or `←`) to move focus from the prompt into the list — the selected row is marked `●`, the rest `○`. The row block stays at five lines once the roster reaches five rows; overflowing lists show three rows plus `↑ N more: 2 running` / `↓ N more: 1 queued, 1 failed` indicators that count the hidden rows by state (blank at an edge), without moving the content below it. Each agent row carries its state as a glyph and a word in a fixed first column (`▶ running`, `◦ queued`, `✓ done`, `✓ limit`, `✗ failed`, `✗ aborted`, `■ stopped`), so colour is never the only cue; a spawner's `name` leads the description (`pr-1412: Spec review ...`), the configured thinking level reads `think:high` so it cannot be mistaken for the live `thinking · 4s` activity, and columns are sized from every row, not the visible window. A configured-model fallback keeps its shared leading `[fallback]` flag even with `showModel` off; column fitting reserves space for flags on hidden rows too, so scrolling cannot shift the tail. On a narrow terminal the description truncates first, then the activity, thinking and model columns drop (in that order). If a fallback row still cannot fit its stats, the shared leading flag compacts to `[fb]` or `!`, never a sliced fragment. For every row, after optional columns and any flag compaction, state words shrink to glyphs, separators and column padding collapse to spaces, and navigation padding disappear as needed. Elapsed, tokens and cost stay whole whenever they physically fit beside the marker (if present); if space is still insufficient, the state glyph and then elapsed yield before tokens or cost may clip. The selected row renders in the theme's primary text color rather than the muted/dim treatment of the others; an agent with a configured `color` shows its badge there too, bolded. `↑`/`↓` move the selection, `Enter` opens the selected agent's live conversation overlay (it auto-updates as the agent works), and `Esc` (or `↑` above `main`) returns to the prompt. Selecting `main` returns to the normal view. Inside the overlay, press `Enter` to steer the running agent — type a message and `Enter` to send it (`Esc` or an empty submit returns), and it redirects the agent the same way the `steer_subagent` tool does. On a running foreground `Agent` call, press `b` to hand that run to the background; the hint only shows where the handoff is available. A viewer stays open when its agent finishes so you can read the final output, and finished agents linger in the list for a few seconds before dropping out. Typing anything at a non-empty prompt behaves normally — the list only captures arrow keys when the prompt is empty. Disable it entirely via `/agents → Settings → Fleet view`.
261
- With [`showModel`](#persistent-settings) on, each row names the model and thinking level it runs at (`▶ running · sonnet 4.6 · think:high · 11s · ↓ 13.1k tokens`; the above-editor widget spells the level `thinking: high`), and a running row says what its in-flight model call is doing (see [Live activity](#live-activity)). On a virtual model such as `router/default` the row reads `router default (routing)` until the first response, then the physical model that answered. The thinking level shown is the session's selected level; a router chain entry's own `thinking` override is not visible to it.
275
+ Running [workflows](#subagentworkflow) appear as a single `workflow` row above the agents, carrying their agent counts in place of a description. `Enter` on one opens the same two-pane inspector `/agents → Workflows` does, rather than a conversation overlay. A run's own agents are *not* listed separately — they belong to the run, which reports for them, so they are filtered out of the fleet list, the above-editor widget, the `/agents` menus and `@handle` resolution exactly as nested children are. They are also outside the `maxConcurrent` pool: the run has its own concurrency cap, and routing a fan-out through the session pool as well would let one workflow starve everything else. The agents are ordered earliest-launched first, and only agents you can actually open, or outstanding failures you can inspect, are shown (pending/queued agents with no session yet appear once they start). At an **empty prompt**, press `↓` (or `←`) to move focus from the prompt into the list — the selected row is marked `●`, the rest `○`. The row block stays at five lines once the roster reaches five rows; overflowing lists show three rows plus `↑ N more: 2 running` / `↓ N more: 1 queued, 1 failed` indicators that count the hidden rows by state, without moving the content below it. Unused indicator slots are padded below the content, so `main`, visible rows and nonempty indicators stay contiguous even at a scroll edge. Each agent row carries its state as a glyph and a word in a fixed first column (`▶ running`, `◦ queued`, `✓ done`, `✓ limit`, `✗ failed`, `✗ aborted`, `■ stopped`), so colour is never the only cue; a spawner's `name` leads the description (`pr-1412: Spec review ...`), the configured thinking level reads `think:high` so it cannot be mistaken for the live `thinking · 4s` activity, and columns are sized from every row, not the visible window. Model, thinking, activity, elapsed, token and cost cells all end on their shared column boundary, including when another row has no activity. A configured-model fallback keeps its shared leading `[fallback]` flag even with `showModel` off; column fitting reserves space for flags on hidden rows too, so scrolling cannot shift the tail. On a narrow terminal the description truncates first, then the activity, thinking and model columns drop (in that order). If a fallback row still cannot fit its stats, the shared leading flag compacts to `[fb]` or `!`, never a sliced fragment. For every row, after optional columns and any flag compaction, state words shrink to glyphs, separators and column padding collapse to spaces, and navigation padding disappear as needed. Elapsed, tokens and cost stay whole whenever they physically fit beside the marker (if present); if space is still insufficient, the state glyph and then elapsed yield before tokens or cost may clip. The selected row renders in the theme's primary text color rather than the muted/dim treatment of the others; an agent with a configured `color` shows its badge there too, bolded. `↑`/`↓` move the selection, `Enter` opens the selected agent's live conversation overlay (it auto-updates as the agent works), and `Esc` (or `↑` above `main`) returns to the prompt. Selecting `main` returns to the normal view. Inside the overlay, press `Enter` to steer the running agent — type a message and `Enter` to send it (`Esc` or an empty submit returns), and it redirects the agent the same way the `steer_subagent` tool does. On a running foreground `Agent` call, press `b` to hand that run to the background; the hint only shows where the handoff is available. A viewer stays open when its agent finishes so you can read the final output, and finished agents linger in the list for a few seconds before dropping out. Typing anything at a non-empty prompt behaves normally — the list only captures arrow keys when the prompt is empty. Disable it entirely via `/agents → Settings → Fleet view`.
276
+ With [`showModel`](#persistent-settings) on, each row names the model and thinking level it runs at (`▶ running · sonnet 4.6 · think:high · 11s · ↓ 13.1k tokens`, and the above-editor widget writes the level the same way), and a running row says what its in-flight model call is doing (see [Live activity](#live-activity)). On a virtual model such as `router/default` the row reads `router default (routing)` until the first response, then the physical model that answered. The thinking level shown is the session's selected level; a router chain entry's own `thinking` override is not visible to it.
262
277
 
263
278
  ### Fullscreen conversation observer
264
279
 
@@ -378,6 +393,8 @@ Individual agent results render Claude Code-style in the conversation:
378
393
 
379
394
  Completed results can be expanded (ctrl+o in pi) to show the full agent output inline.
380
395
 
396
+ For a new `Agent` call, transcript directory and initial entry creation must succeed before the child is queued or started. A setup error fails the call without launching a child; subsequent streaming writes remain best effort. Nested `Agent` calls report the setup error inline.
397
+
381
398
  By default, foreground and background agents each stream their full conversation to a per-subagent transcript — a JSON-lines file at `<os-tmpdir>/pi-subagents-<uid>/<cwd>/<session>/tasks/<agent-id>.output` (owner-only `0700`, cleared on reboot). Set `output_transcript: false` on a custom agent to write no transcript path or file for it, or set `outputTranscript: false` in `subagents.json` to make transcripts opt-in for the whole project (frontmatter overrides the project default). This governs **only** the transcript: it is independent of `persist_session` (the pi session on disk), and it does not affect `isolation: worktree` (which commits the agent's work to a git branch) or `memory:` (durable files) — set those accordingly if the goal is to keep a run off disk entirely. Background agent completion notifications collapse to one line, with pi 1.0 drawing the `▸`/`▾` marker and toggling on click (the renderers draw none of their own):
382
399
 
383
400
  ```
@@ -387,13 +404,15 @@ By default, foreground and background agents each stream their full conversation
387
404
 
388
405
  Expanded, a notice adds `↻3 · 3 tools · 12.4k token · 4.1s`, the result preview, and the transcript as an OSC 8 hyperlink labelled `transcript ↗` (the path follows it as plain text). A group expands to one block per agent, plus a total when `showCost` is on. The LLM receives structured `<task-notification>` XML for parsing, while the user sees the themed visual.
389
406
 
407
+ A failed agent whose session survived is resumable: a provider or credential error (e.g. a transient `!command` API key failure) ends its turn but keeps the whole transcript. Its collapsed notice reads `✗ desc · resume <id> to continue · ...`, and both the expanded notice and the `<task-notification>` `<summary>` say `transcript intact; resume agent <id> to continue`. A run that failed before it had a session carries no such hint.
408
+
390
409
  ## Default Agent Types
391
410
 
392
411
  | Type | Tools | Model | Prompt Mode | Description |
393
412
  |------|-------|-------|-------------|-------------|
394
413
  | `general-purpose` | all 7 | inherit | `append` (parent twin) | Inherits the parent's full system prompt — same rules, CLAUDE.md, project conventions |
395
- | `Explore` | read, bash, grep, find, ls | `@cheap` | `replace` (standalone) | Fast codebase exploration (read-only); an unbound/unavailable tier visibly inherits the parent by default, or refuses under `modelResolution` `error` |
396
- | `Plan` | read, bash, grep, find, ls | inherit | `replace` (standalone) | Software architect for implementation planning (read-only) |
414
+ | `Explore` | read, grep, find, ls | `@cheap` | `replace` (standalone) | Fast codebase exploration (read-only); an unbound/unavailable tier visibly inherits the parent by default, or refuses under `modelResolution` `error` |
415
+ | `Plan` | read, grep, find, ls | inherit | `replace` (standalone) | Software architect for implementation planning (read-only) |
397
416
 
398
417
  The `general-purpose` agent is a **parent twin** — it receives the parent's entire system prompt plus a sub-agent context bridge, so it follows the same rules the parent does. Explore and Plan use standalone prompts tailored to their read-only roles.
399
418
 
@@ -468,7 +487,7 @@ All fields are optional — sensible defaults for everything.
468
487
  | `persist_session` | `subagents.json` `rememberAgents` (default `true`) | Persist this subagent as a normal pi session instead of keeping the session in memory only; overrides the `rememberAgents` project default in both directions. It records its spawning session as parent, so it nests under it in `/resume`. The subagent's `.output` transcript is still written either way unless `output_transcript: false` |
469
488
  | `output_transcript` | `true` (or `subagents.json` `outputTranscript`) | Write this subagent's `.output` transcript; when set, overrides the `subagents.json` `outputTranscript` default. Set `false` to write no transcript file or path. Governs only the transcript — independent of `persist_session`, `isolation: worktree`, and `memory:` |
470
489
  | `session_dir` | pi default | Optional session directory when `persist_session: true`; omitted uses pi's normal session location, and relative paths resolve from the agent cwd. A session outside the parent's session directory (this override, or `isolation: worktree`) is listed separately, so it shows as a root instead of nesting |
471
- | `allowed_subagents` | none | Opt in to scoped nested `Agent`, `get_subagent_result`, and `steer_subagent` tools. Omitted / empty / `none` / `false` = no nesting; `all` (or `"*"` / `true`) = any enabled agent; comma-separated list = only those agent types |
490
+ | `allowed_subagents` | none | Opt in to scoped nested `Agent`, `get_subagent_result`, `steer_subagent`, and `stop_subagent` tools. Omitted / empty / `none` / `false` = no nesting; `all` (or `"*"` / `true`) = any enabled agent; comma-separated list = only those agent types |
472
491
  | `prompt_mode` | `replace` | `replace`: body is the full system prompt (no AGENTS.md / CLAUDE.md inheritance). `append`: body appended to parent's prompt (agent acts as a "parent twin" — inherits parent's AGENTS.md / CLAUDE.md) |
473
492
  | `inherit_context` | `false` | Fork parent conversation into agent |
474
493
  | `run_in_background` | — | Pin this agent to background (`true`) or foreground (`false`). Omit to follow `backgroundByDefault` |
@@ -481,7 +500,9 @@ Frontmatter is authoritative for `max_turns`, `inherit_context`, `run_in_backgro
481
500
  - `isolation` holds on every path except workflows: as in Claude Code, only the script's `agent()` option decides there.
482
501
  - `inherit_context` applies only to `Agent` tool and nested spawns, including default-mode `@handle` mentions, which spawn through the `Agent` tool. As in Claude Code, where only a fork inherits the conversation, every other spawn starts fresh unless its caller asks: workflow children and scheduled runs never inherit, and an RPC caller opts in with `inheritContext`.
483
502
 
484
- **Explicit non-tier tool inputs can use fuzzy names.** These pick the newest model; configured pins and tier/alias members remain exact. A bare family name such as `"opus"` matches several ids; exact ids and [aliases](#machine-local-model-aliases) still win first, otherwise the candidates are narrowed to pi's `enabledModels` (when any candidate is enabled), then ranked by parsed version, newest first, then by match score. Versions are read from `claude-<family>-<n>[-<n>]` (`claude-opus-4-6`, `claude-opus-5`; a trailing `-YYYYMMDD` is a date, not a minor) and `gpt-<n>[.<n>]-<variant>` (`gpt-6.1-sol`); ids with no recognised scheme rank after every versioned one. Previously the shortest id won, so `"opus"` resolved to `claude-opus-5` even with `claude-opus-4-6` enabled and `claude-opus-5` not.
503
+ **Explicit non-tier tool inputs can use fuzzy names.** These pick the newest matching model; configured pins and tier/alias members remain exact. Numeric version runs retain their order and multiplicity before scope filtering or ranking: `sonnet-5-5` can match `claude-5-5-sonnet`, never `claude-4-5-sonnet`, `claude-5-sonnet` or `claude-5-6-sonnet`. A missing requested version is an error, not a neighbouring generation. Dots and dashes are equivalent; a major-only query can still select the newest minor within that major. A bare family name such as `"opus"` matches several ids; exact ids and [aliases](#machine-local-model-aliases) still win first, otherwise the candidates are narrowed to pi's `enabledModels` (when any candidate is enabled), then ranked by parsed version, newest first, then by match score. Ties use the lexicographically smallest case-sensitive `provider/model` identity, independently of registry order; exact id ties use the same ordering without scope filtering. An unresolved explicit input lists that same narrowed candidate set, so it does not suggest models the dispatch path would reject. Versions are read from `claude-<family>-<n>[-<n>]` (`claude-opus-4-6`, `claude-opus-5`; a trailing `-YYYYMMDD` is a date, not a minor) and `gpt-<n>[.<n>]-<variant>` (`gpt-6.1-sol`); ids with no recognised scheme rank after every versioned one. Previously the shortest id won, so `"opus"` resolved to `claude-opus-5` even with `claude-opus-4-6` enabled and `claude-opus-5` not.
504
+
505
+ Agent spawn/results and completion notices echo the resolved `provider/model` identity from the run's retained selection, including nested results and resumes. Scheduled and quota-parked results echo none: they reselect at fire time. A literal virtual model is labelled as a routing alias with an unknown physical response model, not as proof of a physical leaf. Tier notes retain their existing admitted/answered provenance. Only identity strings are echoed, never registry model objects or credentials; identity alone does not trigger a fallback warning.
485
506
 
486
507
  **Exact configuration, convenient tool inputs.** Agent definitions, `agentModels`, tier members and legacy alias candidates resolve exactly, without version ranking or provider retry. An unavailable configured pin inherits the parent with a visible note by default; `modelResolution.onUnavailable: "error"` refuses it instead. Explicit literal/fuzzy tool inputs retain fuzzy matching and unresolved names remain errors; `@tier` and legacy aliases follow `modelResolution`. Built-in Explore uses `@cheap`; bind it globally or in the enterprise manifest. Unbound tiers inherit with disclosure by library default; personal configurations should set both resolution policies to `error`. During enterprise migration only, built-in Explore's unbound `@cheap` can use the existing `modelAliases["claude-haiku-4-5"]` exact candidates as a static cheap tier. An explicit `cheap` binding always wins, including when unavailable; no neighbouring tier or newer model is inferred. User-pinned legacy Explore definitions retain the exact `anthropic/claude-haiku-4-5` compatibility binding. Users who pin Explore explicitly through `agentModels` (e.g. the owner's `agentModels.Explore`) are unaffected. The router's shipped `examples/subscription-router.json` supplies an opt-in ordered `cheap` profile (Haiku then Luna); no profiles are registered implicitly.
487
508
 
@@ -499,7 +520,7 @@ allowed_subagents: support-file-finder, support-callsite-tracer # or `all`
499
520
 
500
521
  **The allowlist is a privilege boundary, not just a routing hint.** A child runs with *its own* `tools:`, `extensions:`, and `isolated:` — the parent's restrictions are not inherited — so delegation grants the parent the union of what the listed agents can do. The read-only agent above can write and run commands through any listed agent that can, and `all` reaches every enabled agent including `general-purpose`. Choose the list as carefully as you would choose `tools:` itself; that is the main reason this is default-off.
501
522
 
502
- `allowed_subagents` is runtime-enforced. A comma-separated list restricts nesting to those types; `all` (or `"*"` / `true`, matching how `extensions:` and `skills:` take booleans) allows any enabled agent; omitted, empty, `none`, or `false` means no nested tools are injected at all. Unknown, disabled, and out-of-list types are rejected rather than falling back — regardless of the project's [fallback agent](#persistent-settings) setting, so a configured fallback can never hand a nested caller an agent outside its allowlist — and a nested `model:` is validated against [Model Scope](#model-scope) exactly like a top-level spawn. Result, resume, and steering operations are ownership-scoped, so a parent can control only its own children. Nested records remain internal to that parent and do not appear in top-level tools, lifecycle events, or agent UI — so when a parent finishes, is stopped, or ends a resumed turn, its nested children are stopped with it. They do write their own `.output` transcript (subject to the same `output_transcript` gate), filed under the root session's directory alongside their ancestors', so a nested run can still be inspected after the fact. Their token usage is folded into every ancestor's totals up to the top-level agent (lifecycle events, completion notifications, `/agents`), so nested spend stays attributable at any depth even though the children themselves stay hidden. A nested result that ends `stopped`, `aborted`, or `steered` is labelled as partial, the same guarantee top-level results carry.
523
+ `allowed_subagents` is runtime-enforced. A comma-separated list restricts nesting to those types; `all` (or `"*"` / `true`, matching how `extensions:` and `skills:` take booleans) allows any enabled agent; omitted, empty, `none`, or `false` means no nested tools are injected at all. The same holds when a listed allowlist resolves to no enabled agent (every name missing or disabled, or `all` over an empty registry): the child gets no nested tools and the prompt says nesting is off, instead of an `Agent` tool whose only advertised type is `none`. Unknown, disabled, and out-of-list types are rejected rather than falling back — regardless of the project's [fallback agent](#persistent-settings) setting, so a configured fallback can never hand a nested caller an agent outside its allowlist — and a nested `model:` is validated against [Model Scope](#model-scope) exactly like a top-level spawn. Result, resume, steering, and stop operations are ownership-scoped, so a parent can control only its own children. Nested records remain internal to that parent and do not appear in top-level tools, lifecycle events, or agent UI — so when a parent finishes, is stopped, or ends a resumed turn, its nested children are stopped with it. They do write their own `.output` transcript (subject to the same `output_transcript` gate), filed under the root session's directory alongside their ancestors', so a nested run can still be inspected after the fact. Their token usage is folded into every ancestor's totals up to the top-level agent (lifecycle events, completion notifications, `/agents`), so nested spend stays attributable at any depth even though the children themselves stay hidden. A nested result that ends `stopped`, `aborted`, or `steered` is labelled as partial, the same guarantee top-level results carry.
503
524
 
504
525
  **No background work inside a subagent.** A nested `Agent` call always blocks and returns the child's result inline. `run_in_background: true` returns an error (a frontmatter `run_in_background: true` on the nested type is ignored and the child runs in the foreground), and the `workflow` / `Workflow` / `SubagentWorkflow` tools, including another extension's, return an error explaining why when a subagent calls them. A child gets a refusing `SubagentWorkflow` stand-in for this (one short tool definition), since the real tool is excluded and core would otherwise answer "Tool SubagentWorkflow not found" before the refusal could run; an isolated or `extensions: false` child gets no workflow tool at all. Both would deliver their completion to a session that ends the moment the subagent returns its answer, and a detached child is aborted with its owner anyway.
505
526
 
@@ -547,6 +568,8 @@ tools: "*, ext:mcp/search"
547
568
  isolated: true # hermetic: built-ins only, no extensions/skills/context
548
569
  ```
549
570
 
571
+ The embedded `Explore` and `Plan` definitions use only `read`, `grep`, `find`, and `ls`, with `extensions: false`. They deliberately omit `bash`: a prompt cannot make an unrestricted shell read-only. Their tool restriction is an enforced child-tool surface, not an operating-system or filesystem sandbox; custom overrides must choose any extension selectors deliberately.
572
+
550
573
  A few rules the examples don't make obvious:
551
574
 
552
575
  - `extensions:` is the sole loading authority. `ext:foo` in `tools:` narrows what surfaces; it can't load `foo` on its own. Mismatches fire `extension-error:…` warnings.
@@ -577,7 +600,7 @@ When the effective built-in set lacks `bash` (including `disallowed_tools: bash`
577
600
 
578
601
  `Agent` has no per-call tool-override flag. Use a shell-capable type for command work; do not ask a restricted researcher to acquire `bash`.
579
602
 
580
- The last two rows are separate because zero built-ins is not zero tools: `tools: none` alongside `extensions:` still surfaces every extension tool, so calling it `none` would understate what the agent can do. Without the `+ extension tools` flag an agent that can search the web through an extension (e.g. `Plan`) read as web-less.
603
+ The last two rows are separate because zero built-ins is not zero tools: `tools: none` alongside `extensions:` still surfaces every extension tool, so calling it `none` would understate what the agent can do. Without the `+ extension tools` flag, a custom agent configured to search the web through an extension would read as web-less.
581
604
 
582
605
  ### Shipping agents in a pi package
583
606
 
@@ -628,7 +651,7 @@ Launch a sub-agent.
628
651
  | `thinking` | string | no | Thinking level: off, minimal, low, medium, high, xhigh, max (availability depends on pi version and model) |
629
652
  | `max_turns` | number | no | Max agentic turns. Omit for unlimited (default) |
630
653
  | `run_in_background` | boolean | no | Defaults to `true`; `false` blocks and returns the result inline, until [`ctrl+b`](#sending-a-foreground-agent-to-the-background) hands it to the background |
631
- | `resume` | string | no | Agent ID to resume a previous session. The resumed run gets a fresh `max_turns` budget; its existing model and thinking level remain in effect, so this call's `model` and `thinking` are ignored |
654
+ | `resume` | string | no | Agent ID to resume a previous session. The resumed run gets a fresh `max_turns` budget; its existing model and thinking level remain in effect, so this call's `model` and `thinking` are ignored. An evicted agent whose session was persisted ([`rememberAgents`](#persistent-settings)) is reopened from disk like `@handle`, under a NEW agent id with the model selected anew and this call's `max_turns` applied. It runs only in the background: with `run_in_background: false` the call is refused and says so. The old id keeps resolving to the newest id of that conversation, so a second resume while it runs is answered with that id and status instead of reopening the file twice, and the chain survives the new record's own eviction. The result names the reopened session file, which is the transcript; no `.output` file is written for it |
632
655
  | `isolated` | boolean | no | No extension/MCP tools |
633
656
  | `isolation` | `"off"` \| `"worktree"` | no | `worktree` runs in an isolated git worktree; `off` (the default) does not. Absent from the schema entirely when `worktreeIsolation: false` |
634
657
  | `cwd` | string | no | Working directory for the agent: absolute, `~/`-prefixed, or relative to the session cwd. Default: the session cwd. With `isolation: "worktree"`, the worktree comes from the repo containing it; see [Worktree Isolation](#worktree-isolation). Not combinable with `schedule` |
@@ -687,13 +710,25 @@ Cancelling a `wait: true` call (for example, with `Esc`) stops only the wait. Th
687
710
 
688
711
  ### `steer_subagent`
689
712
 
690
- Send a steering message to a running agent. The message interrupts after the current tool execution. A queued agent (waiting on `maxConcurrent`) accepts it too: the result says it was queued, and it is delivered into the agent's first turn when it starts.
713
+ Send a message to an agent, like Claude Code's `SendMessage`. A running agent is interrupted after the current tool execution. A queued agent (waiting on `maxConcurrent`) accepts it too: the result says it was queued, and it is delivered into the agent's first turn when it starts.
714
+
715
+ A finished agent (`completed`, `steered`, `aborted` at the turn limit, or `error`) is resumed instead: the call runs `Agent` with `resume` set to that agent, `run_in_background: true` and `message` as the prompt, so the result is a background resume's (`Agent resumed in background.`, the agent id, a completion notice to follow) and its full history is kept. That includes reopening an evicted agent's persisted session under a new id (see `resume`). A stopped agent (`stopped`, by the user or by [`stop_subagent`](#stop_subagent)) is refused, not resumed. An agent whose own file sets `run_in_background: false` resumes in the foreground, as `Agent` would. Nested agents keep their own scoped `steer_subagent`, which still only steers a running child.
691
716
 
692
717
  | Parameter | Type | Required | Description |
693
718
  |-----------|------|----------|-------------|
694
719
  | `agent_id` | string | yes | Agent ID to steer |
695
720
  | `message` | string | yes | Message to inject into agent conversation |
696
721
 
722
+ ### `stop_subagent`
723
+
724
+ Stop a running or queued background agent, like Claude Code's `TaskStop`. A steer asking an agent to stop is read only after its current tool call and can be ignored; this cannot. It goes through the same stop as a viewer's `x`: the run is aborted at once, its current tool call gets the abort signal, and it starts no further tool call, so the record ends `stopped`. The tool returns that state (type, tool uses, tokens). A running agent then sends one completion notice, flagged `stopped by the parent with stop_subagent` rather than `STOPPED BY THE USER`, carrying its partial output and, under worktree isolation, the branch its changes were saved to. It counts as read like any `stopped` notice (see [Completion notice results](#completion-notice-results)). A queued agent never starts, frees its place in the queue, and sends no notice; it still emits `subagents:failed` with `status: "stopped"` so event listeners see it end.
725
+
726
+ `agent_id` takes an id, handle or name, like `steer_subagent`, and reaches a background agent that survived a session switch. A `SubagentWorkflow` Task ID stops that run and its agents, and its notice says `Stopped by the parent with stop_subagent`. An unknown id, or one that is no longer running, is an error that lists every running or queued agent (id, `@handle`, status, description) and workflow run. Ownership is unchanged: a nested or workflow child is not reachable from the top-level tool. A nested parent gets its own scoped `stop_subagent` for its own children. Like the other orchestration tools, it is excluded from a child's tools unless the child sets `allowed_subagents`. A stopped agent is not resumed by `steer_subagent`. The tool declares `readOnlyHint: true`, so [Plan mode](../plan-mode/README.md) admits it by default: it edits nothing and starts nothing, it only ends work already running, so a planning session can still stand a fan-out down. A second stop of the same agent says it is already `stopped`, and of a workflow still unwinding, that it is already stopping. Runs of another extension's `workflow` tool are outside this package, and the tool description says so: `@quintinshaw/pi-dynamic-workflows` stops its own with `workflow_control` action `stop`.
727
+
728
+ | Parameter | Type | Required | Description |
729
+ |-----------|------|----------|-------------|
730
+ | `agent_id` | string | yes | Agent ID, handle or name to stop, or a `SubagentWorkflow` Task ID |
731
+
697
732
  ## Commands
698
733
 
699
734
  | Command | Description |
@@ -741,12 +776,14 @@ The `/agents` command opens an interactive menu:
741
776
 
742
777
  ```
743
778
  Running agents (2) — 1 running, 1 done ← only shown when agents exist
779
+ Stop all running (1) ← only shown while something runs or is queued
744
780
  Agent types (6) ← unified list: defaults + custom
745
781
  Create new agent ← manual wizard or AI-generated
746
782
  Settings ← max concurrency (background + foreground), max turns, grace turns, join mode
747
783
  ```
748
784
 
749
785
  - **Running agents** — select one to open its live conversation viewer. While it's still running, press `Enter` to open the steering composer, then `Enter` again to send a message that redirects the agent (same mechanism as the `steer_subagent` tool; `Esc` or an empty submit returns). On a live foreground run, press `b` to hand the same session and in-flight work to the background without stopping or restarting it — the blocked `Agent` call returns the agent ID and the run notifies on completion like any background agent. Press `x` (then `x` again to confirm) to stop/abort it — including **background** agents, which a global Esc can't unambiguously target (Esc still stops a blocking foreground `Agent` call, but no longer reaches one that has been handed off — `x` here does). A stopped agent reports its partial output flagged as incomplete, not as a completion. `m` cycles how much of the transcript renders as Markdown — see [Viewer markdown](#persistent-settings).
786
+ - **Stop all running** — after a confirmation, stops every running or queued top-level agent, background survivor of a session switch, and workflow run (with its agents), the same as the FleetView stop-all chord. A scheduled job that has not fired yet is not running and is left alone.
750
787
  - **Agent types** — unified list with source indicators: `•` (project), `◦` (global), `▪` ([package](docs/packages.md)), `✕` (disabled), explained by a legend naming only the badges actually on screen. Each row shows the agent's model, and the highlighted agent's full description appears below the list — prefixed with the declaring package's name for a `▪` agent, since the badge alone doesn't say *which* package. The model column flags `(unavailable, fallback: inherit)` when a configured model can't be resolved (it would silently inherit the parent model), and shows `(→ provider/id)` when it resolves to a different provider or version than configured. Select an agent to manage it:
751
788
  - **Default agents** (no override): Eject (export as `.md`), Disable
752
789
  - **Default agents** (ejected/overridden): Edit, Disable, Reset to default, Delete
@@ -759,7 +796,7 @@ Settings ← max concurrency (background + fo
759
796
 
760
797
  ## Record retention
761
798
 
762
- A finished agent's record (and the session behind `get_subagent_result` and `steer_subagent`) is kept for **10 minutes once its result was consumed** (fetched with `get_subagent_result`, or delivered inline to a blocking caller) and **60 minutes from completion while nobody has read it**. After that the record is evicted, and the manager keeps an in-memory note for the rest of the session: both tools answer `Agent "<id>" (<description>) completed, evicted after N min; transcript: <path>` instead of `Agent not found`. The path is the `.output` transcript when one was written, else the session file. Notes are cleared on `/new` and `/resume`, like the records. For tests, `PI_SUBAGENTS_CONSUMED_TTL_MIN` and `PI_SUBAGENTS_UNREAD_TTL_MIN` override the two windows in minutes (fractions and `0` allowed; read when the manager starts); the sweep then runs as often as the shorter window needs, but not more than once a second.
799
+ A finished agent's record (and the session behind `get_subagent_result` and `steer_subagent`) is kept for **10 minutes once its result was consumed** (fetched with `get_subagent_result`, or delivered inline to a blocking caller) and **60 minutes from completion while nobody has read it**. After that the record is evicted, and the manager keeps an in-memory note for the rest of the session: `get_subagent_result` answers `Agent "<id>" (<description>) completed, evicted after N min; transcript: <path>` instead of `Agent not found`, and an `Agent` `resume` or `steer_subagent` that has no persisted session to reopen prefixes that note with `Cannot resume agent "<id>": no saved session is left to reopen.` A `resume` or `steer_subagent` of an evicted agent whose session was persisted reopens it instead (see `resume` above). The path is the `.output` transcript when one was written, else the session file. A nested or workflow child leaves no note, so it stays `Agent not found` to these top-level tools after eviction as before it. When the sweep evicts a top-level agent whose result was **never read**, the parent also gets a `subagent-reaped` message on its next turn (no turn is triggered) carrying that note, so the loss is announced instead of discovered by a later lookup; a read result, a failure whose whole text the completion notice already carried, an agent whose latest run never started (stopped while queued, refused on an already-aborted signal, or failed before the runner began; a resumed one included), a result handed to a blocking caller inline (a foreground resume included), a nested or workflow child, and a record cleared at a session boundary send nothing, and neither does a sweep on a manager a session switch left behind for its running survivors (its `pi` is stale, so the send is dropped; not announced from there, and the new session's manager does not know those records). Running and queued agents are never evicted. Notes are cleared on `/new` and `/resume`, like the records. A background agent that finishes while its session is switched away (`/new`, `/resume`, `/reload`) leaves the same note, kept once its result is delivered to the reopened session, so the id in its notice still resolves. When it also has a saved session, the delivery registers it as reopenable too, so `resume` or `steer_subagent` on that id continues the conversation instead of reporting that no session is left; a name already held by a live agent, an evicted conversation or a reserved survivor stays with its holder, and the id then answers `Cannot resume agent "<id>": its name "<handle>" now belongs to another agent, so its saved session was not kept reopenable.` For tests, `PI_SUBAGENTS_CONSUMED_TTL_MIN` and `PI_SUBAGENTS_UNREAD_TTL_MIN` override the two windows in minutes (fractions and `0` allowed; read when the manager starts); the sweep then runs as often as the shorter window needs, but not more than once a second.
763
800
 
764
801
  ## Graceful Max Turns
765
802
 
@@ -825,6 +862,16 @@ When background agents complete, they notify the main agent. As in Claude Code,
825
862
  **Configuration:**
826
863
  - Configure join mode in `/agents` → Settings → Join mode
827
864
 
865
+ ### Completion notice results
866
+
867
+ A completion notice carries the agent's whole result, as Claude Code's does, up to `notificationResultChars` per agent (default `16000`). For a failed agent that is its error plus any partial output, the same text `get_subagent_result` returns. A result delivered whole counts as read, like a foreground result: no `get_subagent_result` is needed and the record gets the 10-minute read retention instead of 60 minutes unread. **A failure (`error` or `aborted`) never counts as read from a notice**, however short: delivering a completion notice does not acknowledge a failure in [FleetView](#fleetview), so it stays flagged, with the 60-minute unread retention, until `get_subagent_result`, RPC consume or closing its FleetView conversation acknowledges it. A `stopped` agent's partial output is not a failure and counts as read like any other.
868
+
869
+ The cap counts the raw result characters, before XML escaping (`<` is one char, not the four of `&lt;`); the truncation marker is extra. Past the cap the notice keeps the head and the tail of the result around one marker line, `...(truncated: N of M chars omitted; full result in <output-file>, or use get_subagent_result)...`, and the result stays unread. Notices that land in one message, a `group` batch or completions coalesced at one boundary, share a budget of four caps (64k chars by default): each result takes what it needs up to an even split of what is left, so a short result is never cut to make room for a long one. A notice spooled across a session switch carries the same body and is clipped when it is delivered, so survivors that land together after `/resume` share that budget too. Their records live in the orphaned instance, so they are not marked read.
870
+
871
+ **Trust model:** the `<result>` is the subagent's output, and a subagent reads files, web pages and tool output written by anyone, so the result can carry text crafted to look like instructions. It is XML-escaped, so it cannot close its own `<result>` or forge another notice's tags, and a `<result-note>` line before it tells the parent to evaluate it as data, not follow it. That is framing, not a sandbox: a full result puts more third-party text in the parent's context than a 500-char preview did, so treat agents that read untrusted input accordingly, or set `notificationResultChars: 0`.
872
+
873
+ Only the model-facing `<result>` grows; the rendered notification box keeps its 500-char preview. `notificationResultChars: 0` restores the old result: a 500-char preview (300 per `group` member) with a pointer to `get_subagent_result`, and nothing is marked read. The `<result-note>` line stays in either mode. Set it from `/agents` → Settings → Notice result chars (0 to 1000000).
874
+
828
875
  ## Model tiers
829
876
 
830
877
  Use `model: "@cheap"` in an agent, `agentModels`, `Agent({ model: "@cheap" })`, RPC or workflow `agent()` options. Explicit tool models still win. Tiers are independent of `enabledModels`, fuzzy matching, model versions and provider naming conventions.
@@ -835,7 +882,7 @@ Routed personal setup (configure the router profiles separately):
835
882
 
836
883
  ```json
837
884
  {
838
- "modelTiers": { "cheap": ["router/cheap"], "default": ["router/default"], "deep": ["router/deep"], "review": ["router/review"] },
885
+ "modelTiers": { "cheap": ["router/cheap"], "default": ["router/default"], "deep": ["router/deep"], "frontier": ["router/frontier"], "review": ["router/review"] },
839
886
  "modelResolution": { "onUnmapped": "error", "onUnavailable": "error" },
840
887
  "agentModels": { "*": "@default", "Explore": "@cheap", "code-reviewer": "@review" }
841
888
  }
@@ -855,6 +902,14 @@ Enterprise manifest (machine-local, permissions `0600`):
855
902
  { "version": 1, "subagents": { "modelTiers": { "cheap": ["example-provider/example-wire-model"] }, "agentModels": { "Explore": "@cheap" } } }
856
903
  ```
857
904
 
905
+ Enterprise `frontier` binding, two exact flagship members in order (placeholders):
906
+
907
+ ```json
908
+ { "version": 1, "subagents": { "modelTiers": { "frontier": ["example-provider/example-flagship-a", "example-provider/example-flagship-b"] } } }
909
+ ```
910
+
911
+ `@frontier` is the tier for the hardest design and architecture work. Skills and agents name `@frontier` on every profile; only this binding differs. Leave it unbound when no flagship is served: an unbound `@frontier` follows `modelResolution.onUnmapped`, so the work default inherits the parent model with a visible note, and the personal `"error"` refuses it. It never falls back to `@deep` or another tier.
912
+
858
913
  Ordered static list:
859
914
 
860
915
  ```json
@@ -865,6 +920,8 @@ Static tiers choose the first credentialed, admitted member and retain skipped m
865
920
 
866
921
  `modelResolution.onUnmapped` and `onUnavailable` each accept `error` or `inherit`; both library defaults are `inherit`, with a visible note. Neither policy applies to quota exhaustion. Whitespace-only model inputs mean unset; `@` alone and unknown/case-mismatched tier names are visibly unmapped.
867
922
 
923
+ The `Agent` tool guideline and its `model` parameter list the configured tier keys, e.g. `Configured tiers: @cheap, @deep, @default, @frontier, @review. Prefer a tier over a model id.`, never their members. With no tiers configured they keep the generic tier / `provider/modelId` / fuzzy-name text.
924
+
868
925
  Results disclose `Model: @cheap -> provider/id via router/cheap` before the first response using admission's `resolvedModel`, then follow the answering model. Mid-run changes say `switched ... within tier`. Shared leading `[@cheap]` flags identify healthy tiers neutrally; warning flags and grouped alert counts cover only actual switches, parent inheritance, unavailable or unmapped outcomes. Structured `modelTier` outcomes survive records, events, transcripts, eviction and workflow journals. Reopened evicted sessions select/admit anew and retain old outcomes separately as `previousModelTier` and `previousModelFallback`; live resumes keep the existing session's selection. Scheduled tier jobs store `@cheap`, not today's physical pick, and resolve it again when they fire.
869
926
 
870
927
  ## Machine-local model aliases
@@ -896,7 +953,7 @@ Precedence, highest first: the `Agent` tool's `model` parameter, `agentModels[ty
896
953
 
897
954
  - **Intentional blank-input change:** empty and whitespace-only model strings mean unset at every precedence level and on every entry path, falling through to the next default without recording a `param` or `definition` request. Previously RPC/workflow explicit blanks and runner-based configured blanks reached fuzzy resolution (and could select an available model); Agent/nested/scheduled paths skipped `""` but resolved whitespace. Nonblank inputs retain their original spelling and matching.
898
955
 
899
- **Known limits.** At terminal widths below about 60 the workflow dialog's agent-level list pane may show only a fragment of the `[fallback]` flag (every other row shows it first and never clips it, and `withLeadingFlag` degrades to `[fb]` then `!`), and a very long fallback note in the dialog's detail pane is clamped to the pane capacity. The full note stays in the result text, the record, the run log and `get_subagent_result`. Tracked in [#158](https://github.com/nredd/pi-harness/issues/158).
956
+ At narrow terminal widths, the workflow dialog reserves its agent-list fallback disclosure: `[fallback]` compacts to `[fb]`, then `!`, rather than showing a clipped fragment. The detail pane middle-elides unusually long fallback notes so both the configured input and final diagnosis stay visible. The full note remains available in the result text, the record, the run log and `get_subagent_result`.
900
957
 
901
958
  ## Model Scope
902
959
 
@@ -923,12 +980,12 @@ When on, each subagent spawn's effective model is validated against pi's own `en
923
980
 
924
981
  ## Persistent Settings
925
982
 
926
- Runtime tuning values set via `/agents` → Settings (max concurrency, max foreground concurrency, default max turns, grace turns, nested depth, fallback agent, default join mode, scheduling on/off, scope models on/off, disable defaults on/off, strict agent files on/off, package agents on/off, package workflows on/off, agent mentions on/off, output transcript on/off, tool description full/compact/custom, widget all/background/off, usage reporting on/off, cost display on/off, model display on/off, viewer markdown off/assistant/all) persist across pi restarts. Two files, merged on load:
983
+ Runtime tuning values set via `/agents` → Settings (max concurrency, max foreground concurrency, default max turns, grace turns, nested depth, notice result chars, fallback agent, default join mode, scheduling on/off, scope models on/off, disable defaults on/off, strict agent files on/off, package agents on/off, package workflows on/off, agent mentions on/off, output transcript on/off, tool description full/compact/custom, widget all/background/off, usage reporting on/off, cost display on/off, model display on/off, viewer markdown off/assistant/all) persist across pi restarts. Two files, merged on load:
927
984
 
928
985
  - **Global:** `~/.pi/agent/subagents.json` — your machine-wide defaults. Edit by hand; the `/agents` menu never writes here.
929
986
  - **Project:** `<cwd>/.pi/subagents.json` — per-project overrides. Written by `/agents` → Settings.
930
987
 
931
- **Precedence:** project overrides global on any field present in both. Missing fields fall back to the hardcoded defaults (max concurrency `10`, max foreground concurrency `0` = unlimited, default max turns unlimited, grace turns `5`, nested depth `2`, join mode `smart`, defaults enabled).
988
+ **Precedence:** project overrides global on any field present in both. Missing fields fall back to the hardcoded defaults (max concurrency `10`, max foreground concurrency `0` = unlimited, default max turns unlimited, grace turns `5`, nested depth `2`, notice result chars `16000`, join mode `smart`, defaults enabled).
932
989
 
933
990
  **Process budget** (`maxActiveSubagents`, default `0` = off; `nestedSubagentReserve`, default `2`): the process-wide ceiling described under [Process budget](#process-budget). Global configuration only — not written by `/agents`, and ignored with a warning in a project file.
934
991
 
@@ -944,7 +1001,7 @@ Runtime tuning values set via `/agents` → Settings (max concurrency, max foreg
944
1001
 
945
1002
  **Agent mentions** (`agentMentions`, default `"model"`): whether [`@handle message`](#agent-mentions) at the prompt addresses that subagent instead of the main model — messaging, resuming or starting it — and whether `@` offers agents alongside pi's file completion. `"model"` and `"direct"` differ only in [who starts an agent that isn't running](#starting-a-new-agent): an off-screen clone of this conversation, via a `<system-reminder>` and a real `Agent` call, or this extension, immediately and with no model call. Messaging and resuming are direct in both. `"off"` gates all three actions plus the suggestion list, so `@` means only "attach a file" again and every `@…` prompt reaches the main model verbatim. Toggle via `/agents → Settings → Agent mentions`; applied live. The booleans this setting used to take are still read — `true` as `"model"`, `false` as `"off"`.
946
1003
 
947
- **Background by default** (`backgroundByDefault`, default `true`): what an `Agent` call that doesn't say means. On — following Claude Code — the agent runs detached, the call returns its ID immediately, and a completion notification carries a preview of the result (`get_subagent_result` for the full text). Set `false` to restore the previous behaviour, where an unqualified spawn blocked the turn and returned its output inline. An explicit `run_in_background` on the call, or in an agent file's frontmatter, overrides this in both directions; the setting only decides what "unspecified" means. **Top-level only** — a nested spawn (an agent spawning its own) always defaults to foreground, because a detached child is stopped when its parent settles and has no notification path of its own. Toggle via `/agents → Settings → Background by default`; applied live.
1004
+ **Background by default** (`backgroundByDefault`, default `true`): what an `Agent` call that doesn't say means. On — following Claude Code — the agent runs detached, the call returns its ID immediately, and a completion notification carries the result up to [`notificationResultChars`](#completion-notice-results) (`get_subagent_result` for anything it had to cut). Set `false` to restore the previous behaviour, where an unqualified spawn blocked the turn and returned its output inline. An explicit `run_in_background` on the call, or in an agent file's frontmatter, overrides this in both directions; the setting only decides what "unspecified" means. **Top-level only** — a nested spawn (an agent spawning its own) always defaults to foreground, because a detached child is stopped when its parent settles and has no notification path of its own. Toggle via `/agents → Settings → Background by default`; applied live.
948
1005
 
949
1006
  **Remember agents** (`rememberAgents`, default `true`): whether subagents persist their pi session, which is what lets [`@handle`](#agent-mentions) reopen an agent's conversation after its in-memory record has been evicted. Two visible consequences of the default: top-level subagents write a session file, and they nest under the session that spawned them in pi's `/resume`. Agents spawned by another agent are excluded — they get no handle, so nothing could reopen their transcript. A custom agent's `persist_session` frontmatter overrides this per agent, in both directions. Toggle via `/agents → Settings → Remember agents`; with it off, handles expire with their record (roughly ten minutes past completion) and `@explore` then starts a fresh agent rather than resuming — the behaviour before this setting existed.
950
1007
 
@@ -969,10 +1026,10 @@ The `~` marks it as pi's estimate rather than a billed figure. **A cost is shown
969
1026
 
970
1027
  Independent of `reportUsage`: this one is what you read, that one is what your session counts. Toggle via `/agents → Settings → Show cost`; applied live.
971
1028
 
972
- **Show model** (`showModel`, default `false`): whether the widget's running rows and the [FleetView](#fleetview) rows name the model driving each agent and the thinking level it is running at. Toggling it refreshes both. The widget row, then the FleetView row, which writes the level `think:high` so it cannot read as a duplicate of the live `thinking · 4s` activity beside it:
1029
+ **Show model** (`showModel`, default `false`): whether the widget's running rows and the [FleetView](#fleetview) rows name the model driving each agent and the thinking level it is running at. Toggling it refreshes both. The widget row, then the FleetView row. Both write the level `think:high` so it cannot read as a duplicate of the live `thinking · 4s` activity:
973
1030
 
974
1031
  ```text
975
- ├─ ⠹ Explore inspect code · sonnet 4.6 · thinking: high · ↻3 · 8.2k token · 4.1s
1032
+ ├─ ⠹ Explore inspect code · sonnet 4.6 · think:high · ↻3 · 8.2k token · 4.1s
976
1033
  ○ Explore inspect code ▶ running · sonnet 4.6 · think:high · thinking · 4s · 41s · ↓ 8.2k tokens
977
1034
  ```
978
1035
 
@@ -1004,6 +1061,8 @@ Turn `all` on for tools that genuinely emit Markdown, and off again for a diff o
1004
1061
 
1005
1062
  Leaving it unset is not quite the same as `true`. Unset means *auto*: on, unless another extension already provides a workflow tool, in which case this one warns and stands down for the session. Two orchestrators in one tool spec is a worse default than none — the model has to guess which to call and pays for both descriptions to find out — and the extension that was installed on purpose is the one that should survive. Setting `workflowsEnabled` explicitly pins the answer: `true` keeps ours whatever else is loaded, `false` is off regardless.
1006
1063
 
1064
+ Child sessions do not start a second subagents manager, even when their extension loader imports a separate copy of the package. Their SDK refusal tool still explains why nested workflows are unavailable; the parent keeps its own workflow tool and manager. This holds across a child session's `reload()` too.
1065
+
1007
1066
  The match is on the exact tool names `Workflow` (Claude Code's) and `SubagentWorkflow` (ours), never a substring, so a `list_workflows` or `github_workflow_run` from some CI integration does not silently take the feature down. The check runs at `session_start` and nowhere earlier, because `getAllTools` throws during extension loading and load order means a check at registration time could not see an extension that has not loaded yet — so the tool is registered first and withdrawn from the active set through `setActiveTools`, which rebuilds the system prompt before any turn runs. When the other extension took the `SubagentWorkflow` name itself, pi's first-registration-wins rule already dropped ours, so there is nothing to withdraw and only the menu and the CLI flag come down.
1008
1067
 
1009
1068
  **Tool description** (`toolDescriptionMode`, default `"full"`): which Agent tool description the LLM sees. `"full"` is the rich Claude Code-style prompt (~1,400 tokens with the default agents); `"compact"` is ~75% smaller — one-line agent type list, terse usage notes — for small/local models where tool-spec tokens are expensive. Per-option details stay in the parameter descriptions in every mode (the parameter schema is never customizable). Applies on the next pi session.
@@ -1039,24 +1098,29 @@ Every project now starts with concurrency 16 and grace 10, without ever touching
1039
1098
 
1040
1099
  ## Events
1041
1100
 
1042
- Agent lifecycle events are emitted via `pi.events.emit()` so other extensions can react:
1101
+ Agent lifecycle events are emitted via `pi.events.emit()` so other extensions can react. A queued background resume's `subagents:created` includes `queuedAfterSettleSeq` (the preceding terminal event's `settleSeq`) and `queuedResumeSeq` (the new queue entry's sequence). A listener can match the preceding settle before marking an existing agent queued and reject late announcements after a later settle. Lifecycle events also carry `wakesParent` for completion-notice eligibility. A foreground handoff's `subagents:created` carries `backgroundHandoff` and the same `runSeq` as its `started`, so listeners can update eligibility without accepting a stale handoff from an earlier run.
1043
1102
 
1044
1103
  | Event | When | Key fields |
1045
1104
  |-------|------|------------|
1046
- | `subagents:created` | `Agent`-tool background spawn, a detached resume, or a foreground `Agent` call handed to the background (after its `started`) — **not** cross-extension RPC, scheduler, or `@handle` spawns, which are first seen at `subagents:started` | `id`, `type`, `description`, `isBackground` (always `true`); plus `modelFallback`/`modelNote` when already known, see below |
1047
- | `subagents:started` | Agent transitions to running (including queued→running) | `id`, `type`, `description`; plus `modelFallback`/`modelNote` only when the spawn path already knew it, see below |
1048
- | `subagents:completed` | Agent finished successfully (background and foreground) | `id`, `type`, `description`, `status`, `durationMs`, `tokens` (display total, `{ input, output, total }` — see the note below), `usage` (the run's spend as a pi `Usage`: token components including `cacheRead`, plus `cost.total` in USD; absent when nothing was spent), `toolUses`, `result`; plus `modelFallback` (`{ input, used, also?, via? }`: `also` and `via` retain older disclosure details; `via` is `"parent"` or `"agent"`) and `modelNote` when a configured model did not resolve and the agent ran on another |
1049
- | `subagents:failed` | Agent errored, stopped, or aborted (background and foreground) | identical payload to `subagents:completed` — both are built by the same formatter, so `error` and `status` are present on that row too, just empty |
1105
+ | `subagents:created` | `Agent`-tool background spawn, a detached resume, or a foreground `Agent` call handed to the background (after its `started`) — **not** cross-extension RPC, scheduler, or `@handle` spawns, which are first seen at `subagents:started`, or at `subagents:failed` when stopped while still queued | `id`, `type`, `description`, `handle`/`name`/`nameBase`/`previousId`/`spawnSeq` (see below), `isBackground` (always `true`); plus `modelFallback`/`modelNote` when already known, see below |
1106
+ | `subagents:started` | Agent transitions to running (including queued→running) | `id`, `type`, `description`, `handle`/`name`/`nameBase`/`previousId`/`spawnSeq` (see below); plus `modelFallback`/`modelNote` only when the spawn path already knew it, see below |
1107
+ | `subagents:completed` | Agent finished successfully (background and foreground) | `id`, `type`, `description`, `handle`/`name`/`nameBase`/`previousId`/`spawnSeq` (see below), `status`, `durationMs`, `tokens` (display total, `{ input, output, total }` — see the note below), `usage` (the run's spend as a pi `Usage`: token components including `cacheRead`, plus `cost.total` in USD; absent when nothing was spent), `toolUses`, `result`; plus `modelFallback` (`{ input, used, also?, via? }`: `also` and `via` retain older disclosure details; `via` is `"parent"` or `"agent"`) and `modelNote` when a configured model did not resolve and the agent ran on another |
1108
+ | `subagents:failed` | Agent errored, stopped, or aborted (background and foreground), including a top-level agent stopped while still queued, which never emitted `started` | identical payload to `subagents:completed` — both are built by the same formatter, so `error` and `status` are present on that row too, just empty |
1050
1109
  | `subagents:steered` | Steering message accepted from any path (`steer_subagent`, `@handle`, the viewers, `subagents:rpc:steer`) — fires for a *queued* steer as well as a delivered one | `id`, `message` |
1051
1110
  | `subagents:usage` | Every assistant message any agent spends on — top-level, nested and workflow children alike, live while it runs (not for an agent detached by `/new`, `/resume`, `/fork` or `/reload`: its observers are dropped after the switch) | Always: `id`, `type`, `description`, `depth`, `usage` (that one message's spend as a pi `Usage`; never emitted for a message that spent nothing). Omitted when unknown, never `undefined`: `model` (`provider/id`) and `thinking` (the effective level) until the session has reported them; `requestedThinking`, only when the agent did not get the level it asked for; `parentAgentId`, nested agents only; `workflowId`, a workflow's agents only; `modelFallback` and `modelNote` when a configured model did not resolve |
1052
- | `subagents:compacted` | Agent's session successfully compacted (not emitted for an agent detached by a session switch) | `id`, `type`, `description`, `reason` (`"manual"` / `"threshold"` / `"overflow"`), `tokensBefore`, `compactionCount`; plus `modelFallback`/`modelNote` when known |
1111
+ | `subagents:compacted` | Agent's session successfully compacted (not emitted for an agent detached by a session switch) | `id`, `type`, `description`, `handle`/`name`/`nameBase`/`previousId`/`spawnSeq` (see below), `reason` (`"manual"` / `"threshold"` / `"overflow"`), `tokensBefore`, `compactionCount`; plus `modelFallback`/`modelNote` when known |
1053
1112
  | `subagents:disposed` | After actual shutdown disposal of this activation's manager and child sessions. Not emitted when a session switch detaches surviving background work. Those detached agents no longer emit `subagents:usage` or `subagents:compacted` either (their `pi` is stale, so the observers are dropped), so a spend ledger under-counts them; their completion still arrives through the session spool. On quit, flush **synchronously** (`writeFileSync`, not `fs.promises`): `emit` does not await listeners and pi exits once shutdown handlers return | `{}` |
1054
1113
  | `subagents:scheduled` | Schedule lifecycle change | `{ type: "added" \| "removed" \| "updated" \| "fired" \| "error", … }` (job/agentId/error fields per type) |
1055
1114
  | `subagents:scheduler_ready` | Scheduler bound to session, enabled jobs armed | `sessionId`, `jobCount` |
1115
+ | `subagents:started` (survivor) | Re-announcement of each background agent still running under this session's file (after `/reload`, or a `/resume` back into the session that spawned them), at every `session_start` and whenever `subagents:announce_survivors` (`{}`) is emitted; `spawnSeq`/`runSeq` are omitted because the orphaned manager counts its own sequence. A survivor's later `subagents:completed` / `:failed` is emitted when its result is delivered, without `settleSeq` | same as `subagents:started` |
1116
+ | `subagents:announce_survivors` | Request, not a notification: a listener that bound after `session_start` (pi-todo sends it once its session is bound) asks this activation to re-emit its survivor announcements | `{}` |
1117
+ | `subagents:other_session_survivor` | At every `session_start` and on `subagents:announce_survivors`: each background agent still running under a session file other than the current one, whatever switch left it (`/fork`, a fork of a fork, `/resume` of another session, `/new`). Its result spools to that file, so it wakes that session, not this one; no `wakesParent`, and no `completed` / `failed` follows here unless that session is later resumed. A listener re-asks to learn that one finished. Listeners must not treat it as armed | same as `subagents:started`, minus `wakesParent`, `spawnSeq` and `runSeq`, plus `sessionFile` (the session it wakes) |
1056
1118
  | `subagents:ready` | RPC handlers registered and armed — fired on session start; not emitted in a session that excludes pi-subagents | `{}` (empty object) |
1057
1119
  | `subagents:settings_loaded` | Persisted settings applied at extension init | `settings` (merged global + project) |
1058
1120
  | `subagents:settings_changed` | `/agents` → Settings mutation was applied | `settings`, `persisted` (`boolean` — `false` on write failure) |
1059
1121
 
1122
+ **Agent identity on events.** `created`, `started`, `completed`, `failed` and `compacted` carry `handle`, the agent's type-derived `@handle` (`explore`, `explore-2`), and `name`, the second handle drawn from the spawner's `name` (`auth-audit` for `name: "Auth Audit"`), and `nameBase`, that name before collision numbering: a retry spawned as `Auth Audit` while the first try still holds `auth-audit` gets `name: "auth-audit-2"` and `nameBase: "auth-audit"`. An evicted agent reopened under a new id also carries `previousId`, the id it continues. Each is omitted when the agent has none; `previousId` cannot be set through `subagents:rpc:spawn`. `spawnSeq` orders the agent's creation, before any time queued, and `completed`/`failed` add `settleSeq` from the same counter (one per extension activation), so a listener can tell an agent spawned after another settled from one queued behind it. A listener can then match an agent by what the parent typed instead of only its opaque `id`; pi-todo uses them to show a todo `owner`'s live agent state ([owner match rule](https://github.com/nredd/pi-harness/tree/main/packages/todo/docs/overlay.md#owner-match-rule)).
1123
+
1060
1124
  **Tier provenance on events.** Payloads that disclose fallback provenance also carry `modelTier` and `modelNote` for tier selections. Outcomes are `routed`, `static`, `unmapped`, `unavailable` or `exhausted`; successful outcomes include the tier, bound profile/member, admitted model and latest answering model, and static outcomes list skipped members and whether a quota signal was used. `subagents:record` history entries and detached completion spools retain the same outcome. Persisted `modelTier.model` contains identity only (`provider`, `id`, `name`, `api`), never provider URLs, headers or other credential-bearing model overlays.
1061
1125
 
1062
1126
  **Model fallback on events.** When a configured model did not resolve, `modelFallback` (`{ input, used, also?, via? }`) and a ready-made `modelNote` ride on `created`, `started`, `compacted`, `usage` and `completed`/`failed` payloads. The shared spawn path records selection before emitting `started`, including RPC, `@mention` and schedules with no explicit model. All later events read the same retained provenance.
@@ -1122,7 +1186,7 @@ pi.events.emit("subagents:rpc:spawn", {
1122
1186
  });
1123
1187
  ```
1124
1188
 
1125
- `options` is the manager's spawn-option object, not the `Agent` tool's parameter schema — the background flag is `isBackground`, and the tool's snake_case `run_in_background` is forwarded verbatim and ignored. Every RPC spawn returns its id immediately and runs detached either way; `isBackground: true` is what makes the agent occupy one of the `maxConcurrent` slots (and queue behind them when they are full). It does not affect `subagents:created`, which is never emitted for an RPC spawn at all — the first event you see for one is `subagents:started`. Leaving it unset starts the agent immediately regardless of that limit, though not of the [process budget](#process-budget) when it is on. `maxConcurrentForeground` never applies here whatever `isBackground` says: it bounds only spawns a caller is blocking on inline, and every RPC spawn is detached. A top-level RPC spawn renders in the widget and FleetView while it runs, with the same live tool activity and turn counter an `Agent`-tool spawn gets — only an explicit `isBackground: false` is dropped by the widget's default `background` mode, the way a foreground `Agent` call is. Nested spawns stay hidden from both.
1189
+ `options` is the manager's spawn-option object, not the `Agent` tool's parameter schema — the background flag is `isBackground`, and the tool's snake_case `run_in_background` is forwarded verbatim and ignored. Every RPC spawn returns its id immediately and runs detached either way; `isBackground: true` is what makes the agent occupy one of the `maxConcurrent` slots (and queue behind them when they are full). It does not affect `subagents:created`, which is never emitted for an RPC spawn at all — the first event you see for one is normally `subagents:started`; an agent stopped while still queued emits only `subagents:failed` (see [docs/rpc.md](docs/rpc.md)). Leaving it unset starts the agent immediately regardless of that limit, though not of the [process budget](#process-budget) when it is on. `maxConcurrentForeground` never applies here whatever `isBackground` says: it bounds only spawns a caller is blocking on inline, and every RPC spawn is detached. A top-level RPC spawn renders in the widget and FleetView while it runs, with the same live tool activity and turn counter an `Agent`-tool spawn gets — only an explicit `isBackground: false` is dropped by the widget's default `background` mode, the way a foreground `Agent` call is. Nested spawns stay hidden from both.
1126
1190
 
1127
1191
  `options.model` accepts either a `Model` object (e.g. `ctx.model`) or a `"provider/modelId"` string — strings are validated against `ctx.modelRegistry` at the RPC boundary and selected by the shared spawn path, which records the original input and source. Cross-extension callers can forward serializable values without losing auth context. Resolution is fuzzy, so a bare `"sonnet"` can land on a provider you never named: with [Model Scope](#model-scope) on, an override that resolves outside `enabledModels` is refused with an error envelope listing the allowed models, exactly as a caller-supplied `Agent({ model })` is. `null` means unset — the agent inherits, same as omitting the field.
1128
1192
 
@@ -1185,6 +1249,31 @@ Memory uses a `MEMORY.md` index file and individual memory files with frontmatte
1185
1249
 
1186
1250
  The `disallowed_tools` field is respected when determining write capability — an agent with `tools: write` + `disallowed_tools: write` correctly gets read-only memory.
1187
1251
 
1252
+ ## Plan mode
1253
+
1254
+ A child started while the parent session is in [`pi-plan-mode`](../plan-mode/README.md)'s Plan mode plans too, whatever its own tools say, as Claude Code keeps a subagent spawned in plan mode in plan mode. Before every spawn the manager asks Plan mode on the parent's `pi.events` (`pi-plan-mode:state-query`, answered synchronously); while Plan is active the answer carries Plan's tool policy and the child runs under it:
1255
+
1256
+ - `edit` and `write` are refused at call time, `bash` and `powershell` run only Plan's reviewed read-only commands (`git status` runs, `rm -rf x` does not), and extension tools run only when Plan would admit them (trusted `readOnlyHint`, or selected in the Plan policy). Tool schemas stay visible, as in the planner, so the child's prompt cache and a later resume are unaffected
1257
+ - The child never needs Plan mode loaded itself: the policy is enforced by this extension, including for `extensions: false` and `isolated` agents. Explore and Plan keep their read-only surfaces unchanged
1258
+ - Every spawn path asks: the `Agent` tool, `@handle` mentions, RPC, schedules created before Plan and `SubagentWorkflow` stages, each as it starts. Nested `allowed_subagents` children also inherit their owner's restriction, even after the parent leaves Plan
1259
+ - The decision is per run. A child spawned during Plan keeps the restriction after you approve and leave Plan, since it was briefed as a planner; resuming it after Plan gets its normal surface back, and resuming any agent during Plan restricts that run. Both hold for `Agent` `resume` and for `steer_subagent` on a finished agent, which resumes through the same path. An agent started before Plan keeps its surface while it runs
1260
+ - `isolation: "worktree"` is refused while planning: a worktree exists to make edits
1261
+ - Nothing Plan's model starts may outlive Plan unrestricted: `Agent` with `schedule` is refused while planning, and so is parking a quota-blocked background dispatch (it fails with the reset instead). Both would fire as fresh spawns after Plan
1262
+ - `steer_subagent` and the `subagents:rpc:steer` channel are refused while planning for a running or queued agent that runs without Plan's restrictions (one started before Plan), so a steer from Plan's model or another extension's tool cannot drive it into edits. Its running gate is never changed underneath it: wait for it to finish and steer it then (that resumes it read-only), stop it with `stop_subagent` (Plan admits it and never asks the gate), or leave Plan first. An agent already running under Plan stays steerable
1263
+ - A malformed answer to the state query (wrong version, no boolean `active`) never counts as Plan being off: it restricts like an unusable one. That is how a version-skewed `pi-plan-mode` (a future v2) or another extension answering `pi-plan-mode:state-query` shows up: the child is limited to `read`, `grep`, `find` and `ls`, its other calls fail with `Plan mode is active and sent no tool policy this subagent understands`, and schedules, parking and steers of pre-Plan agents are refused, even with Plan off
1264
+ - The child's prompt says it is in Plan mode, and its expanded `Agent` row and conversation viewer tags read `plan`
1265
+ - Extension tools are classified from the child's own tool metadata (`sourceInfo`): a tool the planner selected is admitted only from the same source path, so a project-scope copy, or a project-scope `readOnlyHint`, stays blocked
1266
+ - An answer saying Plan is active but carrying no policy this extension understands fails closed to `read`, `grep`, `find` and `ls`. Without `pi-plan-mode` nothing answers and nothing changes
1267
+ - Plan mode and this extension shake hands (`pi-plan-mode:gate-query`): Plan blocks `Agent`, `SubagentWorkflow` and `steer_subagent` while active unless this gate answers (only those three, by name: `get_subagent_result`, `stop_subagent` and any other tool that starts no child stay available in Plan), and once Plan mode has shown itself a state query nobody answers refuses the spawn (a resume runs fail-closed). Needs `@nredd/pi-plan-mode` newer than `0.59.3-nredd.3` with this extension newer than `0.22.0-nredd.8`; update both together
1268
+
1269
+ What Plan does not reach:
1270
+
1271
+ - A child queued before Plan starts later with its full surface: the decision was made when it was spawned. Plan's model cannot steer it meanwhile
1272
+ - A schedule created before Plan fires during Plan as a fresh spawn, and is restricted then like any other
1273
+ - Steering from `/agents`, the conversation viewer and `@handle` is the user, not Plan's model, and is not refused
1274
+ - The handshake is hardening, not a sandbox. Every extension shares `pi.events`, and a well-formed `{ version: 1, active: false }` reply from one of them cannot be told apart from Plan's own. Any active answer still wins, but a spoofed one hides a Plan that stayed silent, so the child runs unrestricted. Extension code runs in-process with full access anyway
1275
+ - A nested child of a pre-Plan owner is restricted when Plan is on at its own spawn
1276
+
1188
1277
  ## Worktree Isolation
1189
1278
 
1190
1279
  Set `isolation: worktree` to run an agent in a temporary git worktree:
@@ -1193,11 +1282,13 @@ Set `isolation: worktree` to run an agent in a temporary git worktree:
1193
1282
  Agent({ subagent_type: "refactor", prompt: "...", isolation: "worktree" })
1194
1283
  ```
1195
1284
 
1196
- The agent gets a full, isolated copy of the repository. The worktree directory is removed on completion either way — what differs is whether a branch is left behind:
1285
+ The agent gets a full, isolated copy of the repository. On successful cleanup the worktree directory is removed; whether a branch is left behind depends on the work:
1197
1286
  - **No changes:** worktree is cleaned up automatically, no branch
1198
- - **Changes made:** changes are committed to a new branch (`pi-agent-<id>`), and the result names the branch and the `git merge` command for it. The branch is the only artifact — the worktree path is gone, so nothing points into it
1287
+ - **Changes made:** changes are committed to a new branch (`pi-agent-<id>`), and the result names the branch and the `git merge` command for it. After successful cleanup the branch is the only artifact — the worktree path is gone, so nothing points into it
1199
1288
  - **Agent committed its own work:** the branch is created at the agent's HEAD, preserving its commits (uncommitted leftovers are committed on top first)
1200
1289
 
1290
+ If Git cannot inspect, commit, create the branch, or remove the worktree, cleanup stops without force-removing it. The result reports the error and worktree path (and the branch if already created); inspect and recover the tree before removing it. A failed status check does not establish whether changes exist. Git removal also refuses uncommitted work that appears after the status check. Git refuses to remove any worktree with submodule state (a gitlink in the index, registered in `.gitmodules` or not, or a `modules` dir in the worktree's git dir), so such a worktree is removed with `--force` only after the outer tree and every nested repository re-verify clean; that is decided from the repository state, not git's locale-dependent message. Status checks pass `--untracked-files=all` and removal pins `status.showUntrackedFiles=all`, so a `status.showUntrackedFiles=no` config cannot hide new files from cleanup. A nested repository with uncommitted files (untracked included), a stash, or commits no remote-tracking ref contains, on a branch, a tag or HEAD (all deleted with the worktree), keeps the worktree and reports `submodule work would be lost on removal`. Offline a fetched tag cannot be told from a local one, so a tag on an unpublished commit fails safe. Nested clones under a gitignored path, which no gitlink records, are found through `git status --ignored` and inspected the same way, including ones nested inside a submodule or another ignored clone. Status checks also pass `--ignore-submodules=none`, so `submodule.<name>.ignore` or `diff.ignoreSubmodules` cannot hide a moved gitlink; if `git add -A` then stages nothing (it skips a gitlink hidden by `ignore = all`), cleanup keeps the worktree and says the changes could not be staged. Status checks first clear `assume-unchanged` and `skip-worktree` index bits (a skip-worktree file absent from disk is left alone), so an edit those bits hide is committed, not deleted; a regular file with a mode change staged in the index (vs HEAD, or on a newly added file) that the file on disk no longer matches (e.g. `update-index --chmod`, or a `chmod` after `git add`; a plain unstaged `chmod` is committed normally) cannot be told apart from a later on-disk change and `add -A` would silently restage the disk mode, so cleanup fails safe before staging: it keeps the worktree and reports `mode staged only in the index; worktree kept:` with the paths (skipped under `core.fileMode=false`). Path listings used for these decisions (`ls-files`, `ls-tree`, `diff --raw`, `status --ignored`) run with `core.quotePath=true` and without `-z`, and quoted names are decoded back by `unquoteGitPath`, so a multibyte name split across `pi.exec`'s output chunks cannot be mis-decoded to U+FFFD; nested git dirs are read from `.git` rather than `rev-parse` output. A name git prints raw non-ASCII or that is not valid UTF-8 cannot be inspected, so the worktree is kept with an error. A submodule HEAD at the sha the base commit records is not at risk, and neither is a leftover module git dir at a recorded sha. A cleanup failure marks a normally successful agent as failed, including a structured workflow child; a stopped agent stays stopped and reports the failure.
1291
+
1201
1292
  The agent's system prompt names the worktree as an isolated copy and tells it to work only there, even if other instructions name the main checkout — otherwise an inherited parent prompt or a task prompt mentioning the project path walks it straight back out of the copy. This is a directive, not a sandbox: an agent with shell access can still `cd` out, so don't rely on `isolation` alone to protect the main checkout.
1202
1293
 
1203
1294
  The automatic preservation commit uses `--no-verify`, so local pre-commit hooks can't block it — the commit is local-only and never pushed, and pre-push/server-side hooks still apply.
@@ -1234,25 +1325,11 @@ skills: api-conventions, error-handling
1234
1325
  ---
1235
1326
  ```
1236
1327
 
1237
- **Discovery roots** (checked in this order, first match wins):
1238
-
1239
- | Scope | Path | Source |
1240
- |---|---|---|
1241
- | Project | `<cwd>/.pi/skills/` | Pi-standard |
1242
- | Project | `<cwd>/.agents/skills/` | [Agent Skills spec](https://agentskills.io/integrate-skills) |
1243
- | User | `$PI_CODING_AGENT_DIR/skills/` (default `~/.pi/agent/skills/`) | Pi-standard |
1244
- | User | `~/.agents/skills/` | [Agent Skills spec](https://agentskills.io/integrate-skills) |
1245
- | User | `~/.pi/skills/` | Legacy (pre-Pi) |
1246
-
1247
- **Per root, a skill named `foo` resolves to the first of:**
1248
-
1249
- - `<root>/foo.md` — flat file at the top level
1250
- - `<root>/foo/SKILL.md` — directory skill (top-level)
1251
- - `<root>/*/.../foo/SKILL.md` — directory skill, found by recursive descent
1328
+ Named preloads use Pi's configured-resource resolution and parser. Trusted project discovery, the configured agent directory, and installed package manifests use Pi's precedence; a trusted project's ancestor `.agents/skills` roots participate when Pi discovers them. Pi-disabled resources, including `skills: ["-skills/review"]`, remain disabled and cannot return through compatibility lookup. Compatibility lookup otherwise retains the historical user roots (`<home>/.agents/skills`, `<home>/.pi/skills`) and explicit top-level `<name>.md` files. A package skill hidden behind a rejected project symlink alias is recovered through a scratch-cwd Pi resolution, so the project's package filters (`skills` allowlists and exclusions, relative or absolute sources) still decide whether it loads. Package discovery remains inspect-only: unavailable configured npm or Git sources are skipped, never installed or lifecycle-executed.
1252
1329
 
1253
- Recursion skips dotfile directories and `node_modules`. A directory that itself contains a `SKILL.md` is treated as a single skill — we don't descend into it. Traversal is byte-order sorted for deterministic resolution across filesystems.
1330
+ **Name matching:** Pi matches a skill by its frontmatter `name:`. The compatibility lookup runs only when Pi finds no enabled or disabled skill of that name, and matches by file or directory name instead, so a `review/` directory declaring `name: other` answers both `other` and `review`. Per root, `foo` resolves to the first of `<root>/foo.md`, `<root>/foo/SKILL.md`, then `<root>/*/.../foo/SKILL.md` by recursive descent. Descent skips dotfile directories and `node_modules`, never enters a directory that has its own `SKILL.md`, and is byte-order sorted. An unreadable match is skipped and the walk continues. Names with path-traversal characters are rejected: anything but a leading letter or digit followed by letters, digits, `.`, `_` or `-`, or longer than 128 chars. The project compatibility roots (`.pi/skills`, each `.agents/skills`) are walked only for a trusted project.
1254
1331
 
1255
- **Security:** project roots reject symlinks at every layer (the `.pi/` or `.agents/` directory, root, flat file, skill directory, `SKILL.md` inside a skill directory), so a cloned repository cannot point a skill at files outside it — intentional deviation from Pi. User roots belong to you and follow symlinks like Pi (linked roots, directories and files; broken links and directory cycles are skipped), so skills linked into `~/.agents/skills/` by an installer or dotfile manager load. Skill names with path-traversal characters (`..`, `/`, `\`, spaces, leading dot, >128 chars) are rejected.
1332
+ **Project links:** automatically discovered project roots reject symlinks at `<cwd>/.pi`, `.pi/skills`, each ancestor `.agents`, `.agents/skills`, and their skill entries. This filter runs before this extension accepts project candidates; a separate untrusted Pi resolution restores only a user or package candidate that Pi's realpath deduplication hid behind a rejected project alias, retaining Pi's package-identity precedence. A candidate whose path cannot be canonicalized (raced away or unreadable) is never restored, and the rest of the preload continues. User compatibility roots and installed package resources retain Pi's normal link handling, so a project or global package's `skills/<name>` symlink that points outside the package is followed and injected. The former loader refused links under project roots only; packages were never covered by that refusal, and this matches Pi. An explicit injected home excludes the runtime process's ambient `~/.agents/skills` result while preserving `<home>/.agents/skills`, so ambient content cannot enter a child prompt. The host still scans the ambient location; this extension filters returned resources rather than providing filesystem isolation.
1256
1333
 
1257
1334
  ## Tool Denylist
1258
1335
 
@@ -1298,17 +1375,20 @@ src/
1298
1375
  nested-tools.ts # Delegation tools handed to subagents (nested spawn/collect/steer)
1299
1376
  process-budget.ts # Process-wide running-agent budget shared by every manager (maxActiveSubagents)
1300
1377
  child-context.ts # AsyncLocalStorage flag marking work done for a child session
1378
+ plan-mode-children.ts # Plan mode for children spawned while the parent plans (#437)
1301
1379
  abortable.ts # Race a wait against Esc without cancelling the background child
1302
1380
  model-call.ts # In-flight model call tracking: thinking / writing / stalled?
1303
1381
  completion-outbox.ts # Deliver completion notifications at the parent's next boundary, coalesced
1382
+ notification-result.ts # How much of a result a notice carries: per-agent cap, batch budget, head+tail clip
1304
1383
  clock.ts # Shared scheduler/outbox/admission/retention/UI clock and native timers
1305
1384
  detached-results.ts # Session-scoped spool and registry for background agents surviving reload/switch
1306
1385
  group-join.ts # `group` join mode: one notification per turn's batch, with timeout
1307
1386
  status-note.ts # Honest status note + salvaged partial output for non-normal outcomes
1387
+ error-message.ts # The text a caught value reports: an Error's message, else String(value)
1308
1388
  usage.ts # Token usage shapes, accumulators, session-stats readers
1309
1389
 
1310
1390
  # Invocation surface
1311
- invocation-config.ts # Shared tool-parameter schemas (isolation, join, thinking, ...)
1391
+ invocation-config.ts # Shared tool-parameter schemas (isolation, cwd, thinking, ...)
1312
1392
  model-selection.ts # Shared precedence, scope and resolution outcome; stored on the agent record
1313
1393
  model-tiers.ts # Exact static/routed tiers, admission, policy and durable disclosure
1314
1394
  model-resolver.ts # Model matching: exact provider/modelId, aliases, fuzzy fallback ranked by version
@@ -1326,9 +1406,10 @@ src/
1326
1406
  home-paths.ts # Injected home normalization and Pi agent-directory defaults
1327
1407
  resource-loader.ts # Fresh scoped settings paths for Pi's real resource/package loader
1328
1408
  memory.ts # Persistent agent memory (resolve, read, build prompt blocks)
1329
- skill-loader.ts # Preload skills (Pi-standard + Agent Skills spec layouts)
1409
+ skill-loader.ts # Preload named skills through Pi's configured resource discovery (trust-gated, inspect-only)
1330
1410
  output-file.ts # Streaming output file transcripts for agent sessions
1331
1411
  worktree.ts # Git worktree isolation (create, cleanup, prune)
1412
+ git-path.ts # Decode git's C-quoted paths (`unquoteGitPath`) for worktree cleanup
1332
1413
  prompts.ts # Config-driven system prompt builder
1333
1414
  context.ts # Parent conversation context for inherit_context
1334
1415
  settings.ts # Persistent settings (~/.pi/agent/subagents.json + .pi/subagents.json)