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

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 (46) hide show
  1. package/README.md +160 -43
  2. package/package.json +8 -8
  3. package/src/agent-file-toggle.ts +6 -4
  4. package/src/agent-manager.ts +418 -118
  5. package/src/agent-runner.ts +155 -62
  6. package/src/cross-extension-rpc.ts +11 -36
  7. package/src/custom-agents.ts +9 -5
  8. package/src/default-agents.ts +2 -4
  9. package/src/enabled-models.ts +8 -7
  10. package/src/enterprise-local.ts +5 -5
  11. package/src/home-paths.ts +27 -0
  12. package/src/index.ts +231 -107
  13. package/src/invocation-config.ts +8 -35
  14. package/src/memory.ts +9 -9
  15. package/src/mention-clone.ts +9 -5
  16. package/src/model-resolver.ts +41 -11
  17. package/src/model-scope.ts +2 -1
  18. package/src/model-selection.ts +159 -0
  19. package/src/model-tiers.ts +171 -0
  20. package/src/nested-tools.ts +31 -26
  21. package/src/output-file.ts +23 -1
  22. package/src/package-resources.ts +25 -10
  23. package/src/process-budget.ts +346 -0
  24. package/src/quota-admission.ts +21 -9
  25. package/src/resource-loader.ts +51 -0
  26. package/src/schedule.ts +41 -19
  27. package/src/settings.ts +111 -8
  28. package/src/skill-loader.ts +8 -7
  29. package/src/stale-ctx.ts +16 -0
  30. package/src/types.ts +33 -0
  31. package/src/ui/agent-row.ts +13 -3
  32. package/src/ui/agent-widget.ts +15 -4
  33. package/src/ui/conversation-viewer.ts +6 -3
  34. package/src/ui/fallback-flag.ts +90 -0
  35. package/src/ui/fleet-list.ts +363 -53
  36. package/src/ui/fleet-state.ts +154 -0
  37. package/src/ui/notification-view.ts +18 -2
  38. package/src/ui/workflow-card.ts +5 -1
  39. package/src/ui/workflow-dialog.ts +10 -0
  40. package/src/workflow/host.ts +43 -45
  41. package/src/workflow/journal.ts +13 -1
  42. package/src/workflow/progress.ts +4 -0
  43. package/src/workflow/runtime.ts +25 -8
  44. package/src/workflow/saved.ts +19 -17
  45. package/src/workflow/task.ts +10 -0
  46. package/src/workflow/worker-source.ts +156 -38
package/README.md CHANGED
@@ -17,7 +17,7 @@ https://github.com/user-attachments/assets/8685261b-9338-4fea-8dfe-1c590d5df543
17
17
  - **Claude Code look & feel** — same tool names, calling conventions, and UI patterns (`Agent`, `get_subagent_result`, `steer_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** — Claude Code-style navigable list of `main` + every running subagent rendered below the editor (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). 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)**
@@ -54,7 +54,7 @@ pi -e ./src/index.ts
54
54
 
55
55
  Requires pi **1.1.0 or newer**: pi 0.86 and 0.87 moved the system prompt and conversation history into the session transcript, and the `@handle` conversation clone and the agent transcript files are built on that model. The `peerDependencies` range declares it, so npm flags an older pi at install time.
56
56
 
57
- ### In-process factory and clock
57
+ ### In-process factory, clock and home
58
58
 
59
59
  `src/index.ts` exports `createSubagentsExtension(options: SubagentsExtensionOptions = {}): ExtensionFactory`.
60
60
  The default export is `createSubagentsExtension()` and keeps native time and timer reference behavior.
@@ -70,8 +70,12 @@ declare const clock: Clock;
70
70
  const extension = createSubagentsExtension({ clock });
71
71
  ```
72
72
 
73
- `SubagentsExtensionOptions` has one optional field, `clock?: Clock`. The exported clock contract matches
74
- session-kit's scheduling boundary:
73
+ `SubagentsExtensionOptions` accepts `clock?: Clock` and `homeDir?: string`. An explicit home binds
74
+ user-scoped discovery, settings and child-session paths without mutating `HOME`; omitted homes keep
75
+ call-time host defaults. Explicit agent/session directory overrides still win. The boundary audit and
76
+ test seams are documented [here](docs/home-injection.md).
77
+
78
+ The exported clock contract matches session-kit's scheduling boundary:
75
79
 
76
80
  ```ts
77
81
  interface TimerHandle { cancel(): void; unref?(): void }
@@ -101,12 +105,13 @@ start gate when present; legacy queued resumes retain the clock-driven fallback.
101
105
 
102
106
  `npm run check` runs Biome, TypeScript, the full Vitest suite and `test:bun`. The four session scenario
103
107
  files (`test/{session-scenarios,fleet-session,viewer-session,worktree-isolation-e2e}.test.ts`) run under
104
- both Vitest and Bun 1.3.14 on core `v1.3.0-nredd.6` release tarballs. They use exported `createHarness`
108
+ both Vitest and Bun 1.4.4 on core `v1.3.0-nredd.8` release tarballs. They use exported `createHarness`
105
109
  and `createInteractiveHarness`/`VirtualTerminal`, real child sessions and git, one injected extension
106
110
  clock, promise-held tools/streams and raw SGR mouse input. Homes, settings and provider credentials
107
111
  are fixtures; no installed pi, tmux or real-clock sleeps are needed. Existing old-host strict-mode
108
112
  coverage explicitly removes the optional SDK export; the shared faux runtime uses the host's real
109
- `withBeforeProviderRequest` facade. The full older suite remains Vitest-only.
113
+ `withBeforeProviderRequest` facade. The full older suite remains Vitest-only. Maintainer guidance on public index callback
114
+ coverage and retained safety guards is [here](../../docs/subagents-index-continuation.md).
110
115
 
111
116
  ### Other hosts
112
117
 
@@ -163,9 +168,9 @@ Schedule formats:
163
168
  - **One-shot relative** — `"+10m"`, `"+2h"`, `"+1d"`. Fires once at that future time.
164
169
  - **One-shot absolute** — full ISO timestamp, e.g. `"2026-12-25T09:00:00.000Z"`.
165
170
 
166
- 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.
171
+ 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.
167
172
 
168
- **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.
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.
169
174
 
170
175
  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.
171
176
 
@@ -179,13 +184,13 @@ Restrictions:
179
184
 
180
185
  ### Quota admission
181
186
 
182
- With a subscription router installed (e.g. [pi-subscription-router](https://github.com/nredd/pi-subscription-router)), a fresh `Agent` dispatch first asks it whether the model is out of included quota, over `pi.events`: `router:rpc:admit` `{ requestId, model: "provider/id" }`, replied on `router:rpc:admit:reply:<requestId>` as `{ success, data: { ok, resolvedModel?, resetAt?, wait? } }` (`resolvedModel` is ignored; `wait: false` is the router's exhaustion policy saying fail now, absent means wait).
187
+ With a subscription router installed (e.g. [pi-subscription-router](https://github.com/nredd/pi-subscription-router)), a fresh `Agent` dispatch first asks it whether the model is out of included quota, over `pi.events`: `router:rpc:admit` `{ requestId, model: "provider/id" }`, replied on `router:rpc:admit:reply:<requestId>` as `{ success, data: { ok, resolvedModel?, resetAt?, wait? } }` (`resolvedModel` labels the admitted model for tiers; `wait: false` is the router's exhaustion policy saying fail now, absent means wait).
183
188
 
184
189
  - Admitted, or no router answers within 100ms: the agent starts as usual
185
190
  - Refused, background, reset known, `wait` not `false`, scheduling on: the dispatch is parked as a one-shot scheduled job a minute after the reset, persisted like any schedule. When it fires it asks again and re-parks at the new reset while still blocked; if the refusal can no longer park (`wait: false`, no reset, or a reset already past) the job is not started and ends with `lastStatus: "error"` and an error event. Only an explicit admission, or no router answering, starts it. If pi was closed through the reset, it asks again 10s after the session starts, once the router has warmed its usage cache
186
191
  - Refused otherwise (foreground, `inherit_context`, scheduling off, unknown reset, or `wait: false`): the call fails with the reset time and no agent starts
187
192
 
188
- Resumes, cron and interval jobs, and RPC or workflow spawns are not gated. A `router/*` virtual model still routes each request inside the child.
193
+ Live resumes keep their existing selection. Tier requests are admitted on every fresh entry path, including RPC, mentions, nested and workflow children, and every scheduled fire. Only top-level `Agent` background calls and scheduled fires can persist a quota wait; other refused starts error. A `router/*` virtual model still routes and fails over inside the child.
189
194
 
190
195
  ## UI
191
196
 
@@ -219,20 +224,41 @@ The status, elapsed time and tokens are read from the agent record on every rend
219
224
 
220
225
  ### FleetView
221
226
 
222
- While subagents are running, a Claude Code-style navigable list renders **below** the editor:
227
+ FleetView starts **collapsed**, rendering exactly one line below the editor, with no overflow rows or blank spacer. Always-collapsed is deliberate: it saves space even for one agent and avoids resizing when a fan-out crosses a threshold. `ctrl+shift+a` toggles it; the settled explicit choice is recorded as a versioned `subagents:fleet-state` custom entry, outside model context.
228
+
229
+ Auto-repeat events are ignored. A 300ms quiet period coalesces rapid toggles (net-no-change bursts write nothing); shutdown flushes the final choice before the activation loses its context. This bounds writes per burst, not all-time size of the host's append-only journal. The last valid choice on the active branch survives ticks, widget resets, `/reload`, and restarting/resuming that session. A fresh session defaults to collapsed. Temporary arrow navigation writes no state entry. This uses the fleet's existing focus-aware terminal-input handler, like its arrows and the foreground `ctrl+b` handoff; there is no subagent key-rebinding setting. Todo's `ctrl+shift+t`, monitors' `ctrl+shift+m`, and pi's `ctrl+o` keep their existing roles.
223
230
 
231
+ ```text
232
+ agents: 3 running, 2 queued, 4 done · ~$28.70 · ctrl+shift+a to expand
233
+ [1 failed, 2 parked, 1 fallback] agents: 3 running, 1 failed, 2 parked · ctrl+shift+a to expand
224
234
  ```
225
- esc to interrupt · ← for agents · ↓ to manage
235
+
236
+ Counts cover top-level records, including running starts and queued/waiting agents without a session, plus workflow containers (not their children). `steered` counts as done; errors and aborted runs count as failed; stopped runs stay distinct. Successful/stopped records retain the existing four-second linger.
237
+
238
+ Unacknowledged errors and aborted runs do not expire from the fleet: the UI journals minimal failure identities and acknowledgements, replayed from the active session branch on reload or disk resume. Terminal callbacks and detached-result ingress record failures explicitly, including failures between render ticks. It retains at most 128 detailed alerts (id, agent type, short sanitized description, terminal state and timestamp), never complete records/results/sessions; four durable per-kind overflow counters preserve exact outstanding totals beyond that cap. Overflow contributes to both collapsed and expanded headers, but has no individual navigation rows. Specific overflow ids can still be acknowledged through result consumption. The existing session journal is the identity authority: overflow deduplication/acknowledgement scans it backwards in O(branch length) time and O(1) extra space; checkpoints restore bounded state without replaying all failures. No additional file/global registry or unbounded UI id/ack set is kept. Live record and workflow-owner observation uses weak keys, not retained records; recreated workflow display projections do not rescan acknowledged/overflow identities every tick. Each failure/ack transition writes one bounded checkpoint (up to 128 minimal details plus counters), trading journal bytes for bounded checkpoint replay; no all-time compaction is claimed. Old failures cannot leave a healthy-looking header or hide the widget.
239
+
240
+ Acknowledged means the existing `resultConsumed` flag was set by inline result delivery, `get_subagent_result`, RPC consume, or successfully closing the failed agent's FleetView conversation. A session-less/restored failure is acknowledged by selecting it to read its error or minimal-metadata toast. Restored workflow failures without a live inspector use the same toast acknowledgement. Merely delivering a completion notice, ticking, or a viewer-open failure does not acknowledge it. Failed workflow containers stay until their inspector closes successfully; paused runs stay blocked until resumed/stopped.
241
+
242
+ Enabled quota-parked schedules awaiting dispatch count as parked even with no agent record, and clear when dispatched, disabled or removed.
243
+
244
+ 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
+
246
+ 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.
247
+
248
+ Expanded, the existing navigable list renders **below** the editor:
249
+
250
+ ```
251
+ esc to interrupt · ← for agents · ↓ to manage · ctrl+shift+a to collapse
226
252
 
227
253
  ● main
228
254
  ○ workflow audit-src 12/40 agents · 32s · ↓ 26.4k tokens
229
- ○ general-purpose Sleep then report 1 11s · ↓ 13.1k tokens
230
- ○ general-purpose Sleep then report 2 11s · ↓ 13.1k tokens
231
- ↓ 3 more
255
+ ○ general-purpose Sleep then report 1 ▶ running · 11s · ↓ 13.1k tokens
256
+ ○ general-purpose Sleep then report 2 ▶ running · 11s · ↓ 13.1k tokens
257
+ ↓ 3 more: 2 running, 1 failed
232
258
  ```
233
259
 
234
- 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 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` / `↓ N more` indicators (blank at an edge), without moving the content below it. 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`.
235
- With [`showModel`](#persistent-settings) on, each row names the model and thinking level it runs at (`sonnet 4.6 · thinking: high · 11s · ↓ 13.1k tokens`), 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.
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.
236
262
 
237
263
  ### Fullscreen conversation observer
238
264
 
@@ -366,7 +392,7 @@ Expanded, a notice adds `↻3 · 3 tools · 12.4k token · 4.1s`, the result pre
366
392
  | Type | Tools | Model | Prompt Mode | Description |
367
393
  |------|-------|-------|-------------|-------------|
368
394
  | `general-purpose` | all 7 | inherit | `append` (parent twin) | Inherits the parent's full system prompt — same rules, CLAUDE.md, project conventions |
369
- | `Explore` | read, bash, grep, find, ls | haiku (falls back to inherit) | `replace` (standalone) | Fast codebase exploration (read-only) |
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` |
370
396
  | `Plan` | read, bash, grep, find, ls | inherit | `replace` (standalone) | Software architect for implementation planning (read-only) |
371
397
 
372
398
  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,7 +425,7 @@ An unreadable or unparseable agent file is skipped, not fatal — a warning name
399
425
  color: red
400
426
  description: Security Code Reviewer
401
427
  tools: read, grep, find, bash
402
- model: claude-opus-4-6
428
+ model: anthropic/claude-opus-4-6
403
429
  thinking: high
404
430
  max_turns: 30
405
431
  ---
@@ -436,8 +462,8 @@ All fields are optional — sensible defaults for everything.
436
462
  | `memory` | — | Persistent agent memory scope: `project`, `local`, or `user`. Auto-detects read-only agents |
437
463
  | `disallowed_tools` | — | Comma-separated tools to deny even if extensions provide them |
438
464
  | `isolation` | — | Set to `worktree` to run in an isolated git worktree, or `off` to refuse one even when the caller passes `isolation: "worktree"` (frontmatter is authoritative). Workflow scripts are the exception: as in Claude Code, only their `agent()` option decides. `none`, `no`, and `false` are accepted spellings of `off` |
439
- | `model` | inherit parent | Default model — `provider/modelId`, [machine-local alias](#machine-local-model-aliases), or fuzzy name (`"haiku"`, `"sonnet"`). An explicit `Agent({ model })` selection overrides this default. Resolved tolerantly (`.`/`-` and a trailing date stamp are interchangeable) and falls back to the same model under another provider if the named one doesn't have it |
440
- | `thinking` | inherit | off, minimal, low, medium, high, xhigh, max — actual availability depends on your pi version and model; pi clamps unsupported levels down |
465
+ | `model` | inherit parent | Default exact `provider/modelId`, [tier](#model-tiers) such as `@cheap`, or [legacy alias](#machine-local-model-aliases). An explicit `Agent({ model })` selection overrides this default. Configured values are never fuzzy matched |
466
+ | `thinking` | inherit | Default thinking level: off, minimal, low, medium, high, xhigh, max — an explicit `Agent({ thinking })` overrides it for that run. Actual availability depends on your pi version and model; pi clamps unsupported levels down |
441
467
  | `max_turns` | unlimited | Max agentic turns before graceful shutdown. `0` or omit for unlimited |
442
468
  | `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` |
443
469
  | `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:` |
@@ -449,15 +475,15 @@ All fields are optional — sensible defaults for everything.
449
475
  | `isolated` | `false` | Hermetic specialist mode: forces `extensions: false` + `skills: false` + drops `ext:` selectors. Only built-in tools. Distinct from `isolation: worktree` (filesystem) |
450
476
  | `enabled` | `true` | Set to `false` to disable an agent (useful for hiding a default agent per-project) |
451
477
 
452
- Frontmatter is authoritative for `thinking`, `max_turns`, `inherit_context`, `run_in_background`, `isolated`, and `isolation`. An agent file's `model` is its portable default; an explicit `Agent({ model })` selection overrides it. Other `Agent` tool parameters only fill fields the agent config leaves unspecified. Other spawn paths differ per field:
478
+ Frontmatter is authoritative for `max_turns`, `inherit_context`, `run_in_background`, `isolated`, and `isolation`. An agent file's `model` and `thinking` are portable defaults: an explicit `Agent({ model, thinking })` selection overrides either for that run. With no explicit thinking level, the definition wins; if it has none either, no level is passed and pi picks one at session creation (the per-model setting, else `defaultThinkingLevel`, clamped to the model), which does not follow the parent's live level. Only the `@mention` clone path starts from the parent's `ctx.thinkingLevel`. Model selection additionally follows the [`agentModels`](#per-agent-models) precedence. Other `Agent` tool parameters only fill fields the agent config leaves unspecified. Other spawn paths differ per field:
453
479
 
454
480
  - `isolated` holds on every spawn path, including `@handle` mentions, workflows and [cross-extension RPC](https://github.com/tintinweb/pi-subagents/blob/master/docs/rpc.md).
455
481
  - `isolation` holds on every path except workflows: as in Claude Code, only the script's `agent()` option decides there.
456
482
  - `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`.
457
483
 
458
- **Fuzzy names pick the newest model.** 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.
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.
459
485
 
460
- **Forgiving `model:` resolution.** A `model:` pin is matched against pi's model registry tolerantly, so cosmetic id variations don't silently drop the agent back to the parent's model: `.` and `-` are treated as equivalent in version numbers (`claude-haiku-4.5` ≡ `claude-haiku-4-5`), a trailing `-YYYYMMDD` date stamp is optional (`anthropic/claude-haiku-4-5-20251001` matches an undated registry id and vice-versa), and a `provider/modelId` whose named provider doesn't carry that model retries the bare id against every provider. Precedence is **exact → fuzzy under the named provider → same model under any provider → unavailable**, so an exact match always wins and dated snapshots aren't conflated. If nothing resolves, the pin can't run and the agent inherits the parent model — `/agents → Agent types` flags this case as `(unavailable, fallback: inherit)` and shows the resolved target `(→ provider/id)` when resolution lands on a different provider or version than configured. (This is distinct from [Model Scope](#model-scope) enforcement, which matches the `enabledModels` allowlist by *exact* entry.)
486
+ **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.
461
487
 
462
488
  ### Nested subagents
463
489
 
@@ -479,7 +505,18 @@ allowed_subagents: support-file-finder, support-callsite-tracer # or `all`
479
505
 
480
506
  The hard cap is depth 2 by default: main session (0) → subagent (1) → nested child (2). Change it project-wide with `maxSubagentDepth` in `subagents.json` (or `/agents → Settings → Nested depth`); `0` or `1` turns nesting off everywhere. An agent already at the cap gets no nested tools at all — not even `get_subagent_result`, since it can never own a child. A child must independently set `allowed_subagents` to delegate again; isolated agents never receive nested tools.
481
507
 
482
- Nested children occupy no concurrency slot, in either pool — their parent already holds one, and queueing them behind it would deadlock a parent waiting on its own child. The depth cap bounds how *deep* nesting goes, not how *wide*: a parent's only limit on concurrent children is that each spawn costs it a turn. Pair `allowed_subagents` with a `max_turns` on that agent if you want a hard ceiling on its fan-out.
508
+ Nested children occupy no slot in either per-session pool — their parent already holds one, and queueing them behind it would deadlock a parent waiting on its own child. They do take a lease from the [process budget](#process-budget) when it is on, which reserves slots for them so that cannot deadlock. The depth cap bounds how *deep* nesting goes, not how *wide*: a parent's only limit on concurrent children is that each spawn costs it a turn. Pair `allowed_subagents` with a `max_turns` on that agent if you want a hard ceiling on its fan-out.
509
+
510
+ **Depth-2 review delegation.** The default depth already allows main session (0) → implementation agent (1) → reviewer (2). Give the implementation agent an explicit allowlist naming only the reviewer, and leave the reviewer without `allowed_subagents`, so it is a leaf:
511
+
512
+ ```yaml
513
+ ---
514
+ # ~/.pi/agent/agents/implementer.md
515
+ allowed_subagents: code-reviewer
516
+ ---
517
+ ```
518
+
519
+ The implementer then calls `Agent({ subagent_type: "code-reviewer", ... })` in the foreground and gets the review inline. With the process budget on, its slot stays held while the reviewer runs, and the reserve keeps reviewers startable when every top-level slot is an implementer waiting on one.
483
520
 
484
521
  Because a subagent session never activates this extension (that is what keeps a child from building a second agent manager, and it is why nested tools are injected directly instead), a subagent also gets none of the extension's other surfaces: no `/agents` command, no cross-extension RPC handlers, no `subagents:ready` event.
485
522
 
@@ -536,6 +573,10 @@ A few rules the examples don't make obvious:
536
573
  | `none` with `isolated: true` or `extensions: false` | no | `none` |
537
574
  | `none`, or only `ext:` entries | yes | `no built-ins, extension tools only` |
538
575
 
576
+ When the effective built-in set lacks `bash` (including `disallowed_tools: bash`), the shared suffix adds `; no built-in shell: cannot run bash commands`. This applies to every type in full, compact and custom-template type lists. It does not claim extensions cannot execute commands: an extension may supply `ssh_exec` or another command tool. Put stronger restrictions in the agent description's **first sentence** so compact mode preserves them, e.g. `no shell/ssh: cannot run commands`. Shell-less prompts should require a FIRST-turn explanation, before tool calls, when a task needs unavailable commands. This is prompt guidance, not a dispatcher classifier.
577
+
578
+ `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
+
539
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.
540
581
 
541
582
  ### Shipping agents in a pi package
@@ -587,7 +628,7 @@ Launch a sub-agent.
587
628
  | `thinking` | string | no | Thinking level: off, minimal, low, medium, high, xhigh, max (availability depends on pi version and model) |
588
629
  | `max_turns` | number | no | Max agentic turns. Omit for unlimited (default) |
589
630
  | `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 |
590
- | `resume` | string | no | Agent ID to resume a previous session. The resumed run gets a fresh `max_turns` budget |
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 |
591
632
  | `isolated` | boolean | no | No extension/MCP tools |
592
633
  | `isolation` | `"off"` \| `"worktree"` | no | `worktree` runs in an isolated git worktree; `off` (the default) does not. Absent from the schema entirely when `worktreeIsolation: false` |
593
634
  | `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` |
@@ -748,10 +789,26 @@ There are two independent pools.
748
789
 
749
790
  The two are deliberately **not** one limit. A foreground agent blocks the parent anyway — the parent could have done that work itself without paying a slot — so charging it to the background pool would let a saturated pool starve the main session.
750
791
 
751
- The foreground pool does not cover `resume`: a foreground resume reopens an existing session and never reaches the spawn path, so several blocking resumes in one message can still run at once. A *background* resume does take a background slot and queues behind them like any other background agent.
792
+ The foreground pool does not cover `resume`: a foreground resume reopens an existing session and never reaches the spawn path, so several blocking resumes in one message can still run at once (the process budget below does cover them). A *background* resume does take a background slot and queues behind them like any other background agent. A resume of an agent that is still `running` or `queued` is refused on either path, including two resumes of one agent in the same message.
752
793
 
753
794
  Nested children and a [workflow](#subagentworkflow)'s agents are outside the pool entirely. A nested child would deadlock behind a parent waiting on it; a workflow already bounds its own fan-out at `max(1, min(16, cpus - 2))`, and counting its agents twice would let one run fill the session's pool and starve everything else.
754
795
 
796
+ ### Process budget
797
+
798
+ **Process-wide** (`maxActiveSubagents`, default `0` = off; `nestedSubagentReserve`, default `2`). One ceiling on running subagents for the whole pi *process* — not the machine: a second pi process has its own. Unlike the two pools it counts every start, in every agent manager in the process: top-level, nested, workflow, scheduled, RPC and `@handle` spawns, foreground and background resumes, and agents a `/new`, `/resume`, `/fork` or `/reload` left running in the previous instance. It bounds running agents only; it does not look at CPU or memory and does not serialize commands.
799
+
800
+ A run holds its slot until it actually settles, worktree cleanup included, and a parent keeps its slot while it waits on a child: pi runs a turn's tool calls in parallel, so the parent's other tools may still be executing. Shutting a session down does not free its runs' slots either; each run frees its own when it returns. To keep held slots from deadlocking, `nestedSubagentReserve` slots are held back per nesting level, up to the *global* `maxSubagentDepth` `L`. With `limit(j) = maxActiveSubagents - nestedSubagentReserve × (L - j)`, a run at depth `d` (top-level, workflow and scheduled runs are depth 1) starts only while, for every level `j` from `d` to `L`, fewer than `limit(j)` runs at depth ≤ `j` hold slots. With `maxActiveSubagents: 12`, the default reserve and the default depth 2, at most 10 top-level runs hold slots and the last 2 only ever go to depth-2 children. With `maxSubagentDepth` 0 or 1 top-level runs get all 12.
801
+
802
+ The guarantee needs a coherent policy: with nesting on, a reserve of at least 1 and `maxActiveSubagents ≥ nestedSubagentReserve × (L - 1) + 1`. A budget that breaks that (`nestedSubagentReserve: 0`, or 12 and 2 at depth 8) is ignored with a warning and the previous policy stays in force. A project file can still deepen its own nesting, but the reserve covers only `L`: while the budget is on, a nested `Agent` call deeper than `L` fails at the call instead of waiting behind its own ancestors.
803
+
804
+ Waiting starts are served first come, first served among those that fit: a child may pass an earlier top-level start that has to wait for the reserve. A waiting `Agent` call or resume shows as `queued` and can be cancelled with Esc or `/agents` as usual. `bypassQueue` starts (scheduled jobs, the `/agents` agent generator) skip the per-session pools but not this budget, so a scheduled job can start late. At most 128 starts wait at once while the budget is on; the next spawn or resume fails at the call.
805
+
806
+ Both settings are read from global configuration only (`~/.pi/agent/subagents.json` or the enterprise manifest); a project file's value is ignored with a warning, since the budget spans every session in the process. The same goes for the depth it reserves for: the global `maxSubagentDepth`, not a project's. The last activation to apply its settings wins, and removing the key turns the budget off. Turning it off or raising a limit applies at once; turning it on, lowering a limit or changing the global depth waits until no subagent in the process holds a slot, since running parents were admitted under the old limits. A stderr warning names a deferred policy; the previous policy remains in force, and continuous work can delay the change indefinitely. Stop starting work and let the process become idle to apply it safely.
807
+
808
+ Managers loaded from a pi-subagents build without the budget are not counted; restart pi after upgrading for a complete count. A reload between builds with incompatible budget state versions refuses the entire new activation: no tools, commands or manager are registered, and `session_start` fails the bind with the host's `ExtensionStartupError` and an explicit restart diagnostic, even if the requested policy is off. On an older host without that fatal-bind API, the factory refuses immediately with the restart and host-upgrade diagnostic. It never continues unbudgeted or creates a parallel private count; existing runs in the old build can still settle under their original state.
809
+
810
+ ### Sending to the background
811
+
755
812
  Sending a foreground agent to the background moves its slot: it gives up the foreground slot (so a queued blocking `Agent` call can start) and takes a background one. If the background pool is already full it runs above capacity rather than being suspended, and queued background work waits until the count drops back under the limit.
756
813
 
757
814
  ## Join Strategies
@@ -768,6 +825,48 @@ When background agents complete, they notify the main agent. As in Claude Code,
768
825
  **Configuration:**
769
826
  - Configure join mode in `/agents` → Settings → Join mode
770
827
 
828
+ ## Model tiers
829
+
830
+ 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.
831
+
832
+ Bindings live ONLY in global `~/.pi/agent/subagents.json` or the `subagents` object of an owner-only `enterprise.local.json`. Project tier definitions and resolution policies are ignored with a warning. Keys match `^[a-z0-9][a-z0-9._-]*$` and are case sensitive. Each nonempty list contains either exactly one registered `router/<profile>`, or only exact physical `provider/id` members. No mixtures, nested tiers, fuzzy entries or implicit neighbouring tiers. Exact model IDs may contain revision markers such as `@20240620`; a bare `@other-tier` binding entry is still rejected. Invalid bindings are dropped with a warning; references to them are `unmapped`.
833
+
834
+ Routed personal setup (configure the router profiles separately):
835
+
836
+ ```json
837
+ {
838
+ "modelTiers": { "cheap": ["router/cheap"], "default": ["router/default"], "deep": ["router/deep"], "review": ["router/review"] },
839
+ "modelResolution": { "onUnmapped": "error", "onUnavailable": "error" },
840
+ "agentModels": { "*": "@default", "Explore": "@cheap", "code-reviewer": "@review" }
841
+ }
842
+ ```
843
+
844
+ A routed tier starts on its virtual model, not a physical chain member. The router owns headroom ranking, admission and mid-run failover. Exhaustion never tries `router/default` or another tier: it follows the existing quota parking/error rules. Missing router profiles are `unavailable`, not fuzzy guesses.
845
+
846
+ GPT-only setup, with no Anthropic dependency or router required:
847
+
848
+ ```json
849
+ { "modelTiers": { "cheap": ["openai/gpt-6-luna"] }, "agentModels": { "Explore": "@cheap" } }
850
+ ```
851
+
852
+ Enterprise manifest (machine-local, permissions `0600`):
853
+
854
+ ```json
855
+ { "version": 1, "subagents": { "modelTiers": { "cheap": ["example-provider/example-wire-model"] }, "agentModels": { "Explore": "@cheap" } } }
856
+ ```
857
+
858
+ Ordered static list:
859
+
860
+ ```json
861
+ { "modelTiers": { "cheap": ["provider-a/fast-model", "provider-b/fast-model"] } }
862
+ ```
863
+
864
+ Static tiers choose the first credentialed, admitted member and retain skipped members with `no-credentials` or `quota` reasons. With a router installed, each member is admitted separately. Without one, admission fails open and disclosure explicitly says `static; no quota signal`.
865
+
866
+ `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
+
868
+ 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
+
771
870
  ## Machine-local model aliases
772
871
 
773
872
  `~/.pi/agent/enterprise.local.json` is an optional, owner-only (`0600`) machine-local manifest for provider configuration that must never enter a repository. Its `subagents.modelAliases` section maps a logical name to ordered canonical `provider/modelId` candidates. The extension loads it only from the user agent directory, never from a project, and only accepts manifest version `1`. The same `modelAliases` key is also accepted in the global `~/.pi/agent/subagents.json` (when both define it, the manifest's whole set replaces the global one); a project `.pi/subagents.json` value is ignored with a warning, so a cloned repo cannot redirect which provider a subagent runs on.
@@ -783,17 +882,21 @@ When background agents complete, they notify the main agent. As in Claude Code,
783
882
  }
784
883
  ```
785
884
 
786
- Alias candidates are resolved in order against authenticated models; Pi sessions and schedules retain only the selected canonical ID. Missing, malformed, group-readable, or unsupported manifests are ignored with a safe path-and-reason warning. Aliases may not contain `/`; candidates must be canonical IDs, so aliases cannot recurse. A request whose alias has no available candidate fails with its alias and candidate IDs, never credential data.
885
+ Alias candidates are resolved in order against authenticated models. Agent records retain the requested input and selection outcome alongside the selected model; schedules retain the explicit request and resolve it again at fire time. Missing, malformed, group-readable, or unsupported manifests are ignored with a safe path-and-reason warning. Aliases may not contain `/`; candidates must be canonical IDs, so aliases cannot recurse. Legacy aliases are exact static tiers with admission and skip disclosure, not fuzzy shortcuts. An unavailable alias follows `modelResolution.onUnavailable`, never exposing credential data.
787
886
 
788
887
  ## Per-agent models
789
888
 
790
- `agentModels` in the global `~/.pi/agent/subagents.json` (or the `subagents` section of `enterprise.local.json`) maps an agent `type`, or `"*"` for every type, to a model: any form `Agent({ model })` takes (`provider/modelId`, alias, fuzzy name). Types match case-insensitively.
889
+ `agentModels` in the global `~/.pi/agent/subagents.json` (or the `subagents` section of `enterprise.local.json`) maps an agent `type`, or `"*"` for every type, to an exact `provider/modelId`, `@tier`, or legacy alias. Agent types match case-insensitively; tier names do not.
791
890
 
792
891
  ```json
793
- { "agentModels": { "Plan": "opus", "Explore": "haiku", "*": "sonnet" } }
892
+ { "agentModels": { "Plan": "@deep", "Explore": "@cheap", "*": "@default" } }
794
893
  ```
795
894
 
796
- Precedence, highest first: the `Agent` tool's `model` parameter, `agentModels[type]`, `agentModels["*"]`, the agent definition's `model:`, the parent's model. It applies on every spawn path (top-level, nested, workflow `agent()`). A value that does not resolve falls back down the chain silently, like a definition default. Like `modelAliases` it chooses providers, so a project `.pi/subagents.json` value is ignored with a warning.
895
+ Precedence, highest first: the `Agent` tool's `model` parameter, `agentModels[type]`, `agentModels["*"]`, the agent definition's `model:`, the parent's model. The shared `selectModel` module applies it on every fresh spawn: top-level and nested `Agent`, workflow `agent()`, scheduled jobs, RPC, direct `@mention` and reopened evicted sessions. `/agents` labels use the same configured selection. An unresolved configured value falls directly to the parent, not a lower-priority configured default, with disclosure: the spawn path records `Note: configured model "<input>" did not resolve; running on parent model <provider>/<id>.` on the agent, and every surface reads it from there: the `Agent` result text and expanded row, `get_subagent_result`, the completion notice, nested `Agent` results, workflow rows and `agent()` results (text, error and a `modelNote` field), the widget, fleet list and conversation viewer, completion events, and agents started by scheduler fire, cross-extension RPC or `@mention`. A live-session resume retains its record's selection and note, ignoring new model inputs and config. An evicted conversation's `modelTier`, `modelFallback` and every model note describe the continuation's actual outcome; `previousModelTier` and `previousModelFallback` separately retain the prior run's outcomes, never as substitutes for a missing or changed binding. Explicit unresolved literal/fuzzy model names are hard errors on every fresh dispatch, including scheduled fires; tiers and legacy aliases follow `modelResolution`. Configured inputs and alias members are exact; only explicit non-tier tool inputs retain fuzzy ranking and provider retry. Tier failures obey `modelResolution`. A workflow `agent({ schema })` result stays pure JSON: the note is a sibling `_modelNote` on an object payload and a line in the run log. Like `modelAliases` it chooses providers, so a project `.pi/subagents.json` value is ignored with a warning.
896
+
897
+ - **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
+
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).
797
900
 
798
901
  ## Model Scope
799
902
 
@@ -827,6 +930,8 @@ Runtime tuning values set via `/agents` → Settings (max concurrency, max foreg
827
930
 
828
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).
829
932
 
933
+ **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
+
830
935
  **Nested depth** (`maxSubagentDepth`, default `2`): the hard ceiling on [nested delegation](#nested-subagents), counted from the main session (main = 0, its subagents = 1). `0` or `1` disables nesting project-wide regardless of any agent's `allowed_subagents`. Read when a subagent session is built, so a change applies to agents started after it.
831
936
 
832
937
  **Fallback agent** (`fallbackSubagent`, default `general-purpose`): the agent used when a caller-supplied `subagent_type` doesn't resolve to exactly one enabled agent — unknown, disabled, or ambiguous because two agents differ only by case. Name any enabled agent to route those calls there instead, or set `none` for **strict**, fail-closed dispatch: the call is refused with an error listing the available types, and nothing spawns. Strict mode matters most for background and scheduled calls, which would otherwise start executing a substituted agent before the caller learns anything. Also settable from `/agents → Settings → Fallback agent`. The boolean `false` is accepted as a spelling of `none`, because it would otherwise be dropped as the wrong type and silently leave the permissive default in place. Every other value is read as an agent name, so a mistaken `off` fails loudly at dispatch rather than meaning one thing in the settings file and another in the resolver. A fallback agent that is itself unknown or disabled is a misconfiguration and is reported rather than quietly replaced. Note the default is unchanged and stays permissive by design: with `disableDefaultAgents` and no `general-purpose` of your own, an unresolvable type still resolves to a built-in config carrying *all* tools — set `none` (or name one of your own agents) to close that.
@@ -864,15 +969,16 @@ The `~` marks it as pi's estimate rather than a billed figure. **A cost is shown
864
969
 
865
970
  Independent of `reportUsage`: this one is what you read, that one is what your session counts. Toggle via `/agents → Settings → Show cost`; applied live.
866
971
 
867
- **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:
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:
868
973
 
869
974
  ```text
870
975
  ├─ ⠹ Explore inspect code · sonnet 4.6 · thinking: high · ↻3 · 8.2k token · 4.1s
976
+ ○ Explore inspect code ▶ running · sonnet 4.6 · think:high · thinking · 4s · 41s · ↓ 8.2k tokens
871
977
  ```
872
978
 
873
979
  Off by default because the row already carries the description, turns, tool uses, tokens and elapsed time, and every character it gains is one the description loses on a narrow terminal. The other surfaces show the pair either way: the `Agent` tool result names the model beside its tags, and the conversation viewer's `↳` row spells out the canonical `provider/model-id`.
874
980
 
875
- Both places report what the run *actually* used, read back from the child session once pi has resolved its defaults and clamped the level to what the model supports — not what the call asked for. Where the thinking level differs from the request, because pi clamped it or the agent file's frontmatter outranked it, the request is kept beside the effective value rather than dropped. An explicit `model` always wins over frontmatter, so there's no model request to disclose:
981
+ Both places report what the run *actually* used, read back from the child session once pi has resolved its defaults and clamped the level to what the model supports — not what the call asked for. Where Pi clamps the requested thinking level, the request is kept beside the effective value rather than dropped. An explicit `model` selection also overrides a frontmatter default; model resolution has no requested-versus-effective display:
876
982
 
877
983
  ```text
878
984
  ↳ anthropic/claude-haiku-4-5 · thinking: low (asked max) · background
@@ -937,20 +1043,24 @@ Agent lifecycle events are emitted via `pi.events.emit()` so other extensions ca
937
1043
 
938
1044
  | Event | When | Key fields |
939
1045
  |-------|------|------------|
940
- | `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`) |
941
- | `subagents:started` | Agent transitions to running (including queued→running) | `id`, `type`, `description` |
942
- | `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` |
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 |
943
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 |
944
1050
  | `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` |
945
- | `subagents:usage` | Every assistant message any agent spends on — top-level, nested and workflow children alike, live while it runs | 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 |
946
- | `subagents:compacted` | Agent's session successfully compacted | `id`, `type`, `description`, `reason` (`"manual"` / `"threshold"` / `"overflow"`), `tokensBefore`, `compactionCount` |
947
- | `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 agents can still emit `subagents:usage`. Other detached activations may also still report usage, so this is not a process-global barrier. On quit, flush **synchronously** (`writeFileSync`, not `fs.promises`): `emit` does not await listeners and pi exits once shutdown handlers return | `{}` |
1051
+ | `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 |
1053
+ | `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 | `{}` |
948
1054
  | `subagents:scheduled` | Schedule lifecycle change | `{ type: "added" \| "removed" \| "updated" \| "fired" \| "error", … }` (job/agentId/error fields per type) |
949
1055
  | `subagents:scheduler_ready` | Scheduler bound to session, enabled jobs armed | `sessionId`, `jobCount` |
950
1056
  | `subagents:ready` | RPC handlers registered and armed — fired on session start; not emitted in a session that excludes pi-subagents | `{}` (empty object) |
951
1057
  | `subagents:settings_loaded` | Persisted settings applied at extension init | `settings` (merged global + project) |
952
1058
  | `subagents:settings_changed` | `/agents` → Settings mutation was applied | `settings`, `persisted` (`boolean` — `false` on write failure) |
953
1059
 
1060
+ **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
+
1062
+ **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.
1063
+
954
1064
  The four agent-lifecycle events — `subagents:started`, `:completed`, `:failed`, `:compacted` — are emitted for **top-level agents only**; nested subagents and a workflow's children report through the parent or workflow that owns them. `subagents:usage` is the one event that is not scoped, on purpose: it is a ledger, not a lifecycle. It fires once per message for every agent, so summing `usage.cost.total` across it gives what this session's subagents spent, each message counted exactly once under the agent that spent it — unlike the ancestor records, which fold a nested child's spend into every parent. It fires regardless of `reportUsage`, which decides only whether the parent session's own totals include the spend.
955
1065
 
956
1066
  `tokens.total` = `input + output + cacheWrite`. `cacheRead` is excluded — each turn's `cacheRead` is the cumulative cached prefix re-read on that one API call, so summing per-message would over-count it as a measure of work done. Use `contextUsage.percent` (surfaced as `(NN%)` in the widget) for current context size.
@@ -1012,9 +1122,9 @@ pi.events.emit("subagents:rpc:spawn", {
1012
1122
  });
1013
1123
  ```
1014
1124
 
1015
- `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 the limit. `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.
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.
1016
1126
 
1017
- `options.model` accepts either a `Model` object (e.g. `ctx.model`) or a `"provider/modelId"` string — strings are resolved against `ctx.modelRegistry` at the RPC boundary, so 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.
1127
+ `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.
1018
1128
 
1019
1129
  `options.cwd` (absolute path to an existing directory — anything else returns an error envelope; `null` means unset) runs the agent in a different working directory than the parent session. Its tools operate there and the prompt's environment block describes it, but **`.pi` config still loads from the parent session's project** — the target directory's `.pi` extensions never execute, and its agents/skills/settings are not picked up. Combined with `isolation: "worktree"`, the worktree is created *from* the target directory's repo, the agent works at the equivalent subdirectory inside the copy (a monorepo-package cwd stays scoped to that package), and the resulting `pi-agent-*` branch lands in that repo — the completion message names it. On session end, worktree registrations are pruned in every repo that received one; only a hard crash can leave a stale entry (then: `git worktree prune` in the target repo). Agents with `memory:` keep reading/writing the parent project's memory.
1020
1130
 
@@ -1163,6 +1273,7 @@ This is useful for creating agents that inherit extension tools but should not h
1163
1273
  docs/ # Long-form guides (shipped to npm; README links out to them)
1164
1274
  workflows.md # SubagentWorkflow: writing, editing, saving and re-running scripts
1165
1275
  rpc.md # Cross-extension integration: pi.events, subagents:rpc:*, manager registry
1276
+ home-injection.md # Injected home boundaries, call-time defaults and filesystem canaries
1166
1277
  examples/
1167
1278
  workflows/ # Runnable examples, executed by test/workflow-examples.test.ts
1168
1279
  agent-tool-description.md
@@ -1185,6 +1296,7 @@ src/
1185
1296
  agent-runner.ts # Session creation, execution, graceful max_turns, steer/resume
1186
1297
  agent-manager.ts # Agent lifecycle, concurrency queue, completion notifications
1187
1298
  nested-tools.ts # Delegation tools handed to subagents (nested spawn/collect/steer)
1299
+ process-budget.ts # Process-wide running-agent budget shared by every manager (maxActiveSubagents)
1188
1300
  child-context.ts # AsyncLocalStorage flag marking work done for a child session
1189
1301
  abortable.ts # Race a wait against Esc without cancelling the background child
1190
1302
  model-call.ts # In-flight model call tracking: thinking / writing / stalled?
@@ -1197,7 +1309,9 @@ src/
1197
1309
 
1198
1310
  # Invocation surface
1199
1311
  invocation-config.ts # Shared tool-parameter schemas (isolation, join, thinking, ...)
1200
- model-resolver.ts # Model resolution: exact provider/modelId, aliases, `agentModels`, fuzzy fallback ranked by version
1312
+ model-selection.ts # Shared precedence, scope and resolution outcome; stored on the agent record
1313
+ model-tiers.ts # Exact static/routed tiers, admission, policy and durable disclosure
1314
+ model-resolver.ts # Model matching: exact provider/modelId, aliases, fuzzy fallback ranked by version
1201
1315
  enabled-models.ts # Read pi's enabledModels settings (project over global)
1202
1316
  model-scope.ts # scopeModels allowlist policy, shared by top-level and nested tools
1203
1317
  mention.ts # `@handle message` grammar: suggestion triggers and send parsing
@@ -1209,6 +1323,8 @@ src/
1209
1323
  schedule-store.ts # PID-locked, session-scoped, atomic schedule persistence
1210
1324
 
1211
1325
  # Context & environment
1326
+ home-paths.ts # Injected home normalization and Pi agent-directory defaults
1327
+ resource-loader.ts # Fresh scoped settings paths for Pi's real resource/package loader
1212
1328
  memory.ts # Persistent agent memory (resolve, read, build prompt blocks)
1213
1329
  skill-loader.ts # Preload skills (Pi-standard + Agent Skills spec layouts)
1214
1330
  output-file.ts # Streaming output file transcripts for agent sessions
@@ -1230,7 +1346,8 @@ src/
1230
1346
  agent-widget.ts # Persistent widget: spinners, activity, status icons, theming
1231
1347
  agent-row.ts # The Agent tool row: live one-line summary, expanded prompt/tags/transcript
1232
1348
  notification-view.ts # Completion notice renderer: one-line collapsed, stats/preview/link expanded
1233
- fleet-list.ts # FleetView: navigable agent list below the editor
1349
+ fleet-list.ts # FleetView: one-line summary/disclosure and navigable list below the editor
1350
+ fleet-state.ts # Versioned journal checkpoints, bounded failure details and exact overflow counters
1234
1351
  conversation-viewer.ts # Live conversation overlay for viewing agent sessions
1235
1352
  viewer-keys.ts # Viewer scroll keys resolved through user keybindings
1236
1353
  agent-mention.ts # `@` roster (running, resumable, and startable agents) + popup rows
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nredd/pi-subagents",
3
- "version": "0.22.0-nredd.6",
3
+ "version": "0.22.0-nredd.8",
4
4
  "description": "A pi extension that brings Claude Code-like sub-agents and workflow orchestration to pi — parallel execution, live widget, fleet view, custom agent types, mid-run steering, dynamic workflows, Claude Code compatibility, look and feel.",
5
5
  "author": "tintinweb",
6
6
  "license": "MIT",
@@ -42,30 +42,30 @@
42
42
  "scripts": {
43
43
  "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc",
44
44
  "prepublishOnly": "npm run lint && npm run typecheck && npm run test && npm run build",
45
- "test": "vitest run",
45
+ "test": "vitest run --coverage",
46
46
  "test:watch": "vitest",
47
47
  "test:e2e": "vitest run e2e --reporter=verbose",
48
48
  "typecheck": "tsc --noEmit",
49
49
  "lint": "biome check src/ test/",
50
50
  "lint:fix": "biome check --fix src/ test/",
51
51
  "check": "npm run lint && npm run typecheck && npm run test && npm run test:bun",
52
- "test:bun": "bun test test/session-scenarios.test.ts test/fleet-session.test.ts test/viewer-session.test.ts test/worktree-isolation-e2e.test.ts",
52
+ "test:bun": "bun test test/session-scenarios.test.ts test/fleet-session.test.ts test/viewer-session.test.ts test/worktree-isolation-e2e.test.ts --coverage --coverage-reporter=text --coverage-reporter=lcov --coverage-dir=coverage/bun && PI_EXPECT_BUN_RUNTIME=1 node ../../scripts/run-vitest.ts bun test/resource-loader-home.test.ts --coverage.enabled=false",
53
53
  "test:coverage": "vitest run --coverage",
54
54
  "bench": "vitest bench",
55
55
  "bench:ab": "node test/perf/ab.mjs"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@biomejs/biome": "^2.4.14",
59
- "@earendil-works/pi-ai": "https://github.com/nredd/pi/releases/download/v1.3.0-nredd.6/earendil-works-pi-ai-1.3.0.tgz",
60
- "@earendil-works/pi-coding-agent": "https://github.com/nredd/pi/releases/download/v1.3.0-nredd.6/earendil-works-pi-coding-agent-1.3.0.tgz",
61
- "@earendil-works/pi-tui": "https://github.com/nredd/pi/releases/download/v1.3.0-nredd.6/earendil-works-pi-tui-1.3.0.tgz",
59
+ "@earendil-works/pi-ai": "https://github.com/nredd/pi/releases/download/v1.3.0-nredd.8/earendil-works-pi-ai-1.3.0.tgz",
60
+ "@earendil-works/pi-coding-agent": "https://github.com/nredd/pi/releases/download/v1.3.0-nredd.8/earendil-works-pi-coding-agent-1.3.0.tgz",
61
+ "@earendil-works/pi-tui": "https://github.com/nredd/pi/releases/download/v1.3.0-nredd.8/earendil-works-pi-tui-1.3.0.tgz",
62
62
  "@xterm/headless": "5.5.0",
63
63
  "@sinclair/typebox": "^0.34.49",
64
64
  "@types/node": "^25.5.0",
65
- "@vitest/coverage-istanbul": "^4.1.11",
65
+ "@vitest/coverage-istanbul": "5.0.3",
66
66
  "typebox": "^1.3.27",
67
67
  "typescript": "^6.0.0",
68
- "vitest": "^4.1.11"
68
+ "vitest": "5.0.3"
69
69
  },
70
70
  "pi": {
71
71
  "extensions": [