@cr1ms0n/pi-subagent 0.8.9 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/UX.md CHANGED
@@ -1,141 +1,158 @@
1
- # pi-subagent UX
2
-
3
- ## Overview
4
- The standalone pi-subagent provides rich TUI support for monitoring, inspecting, and interacting with isolated subagent runs (single and parallel modes): inline streaming blocks, a terse footer, an ambient widget for background runs, batched completion notifications, mid-run steering, a worktree apply loop, and the `/subagents` inspector. UI logic is kept independent from `runner`/`registry` via small structural adapters (`SubagentAdapter`).
5
-
6
- ## Design principles
7
-
8
- 1. **Pi's tool shell owns state signaling.** The Box wrapper paints
9
- `toolPendingBg` / `toolSuccessBg` / `toolErrorBg`, so inline blocks do not
10
- repeat state words or draw their own success/error framing.
11
- 2. **Cost is a per-run attribute, not a competing ledger.** Dollar cost appears
12
- inside the run's own result block; the parent/children/combined ledger is
13
- available on demand via `/subagent-cost`, in `status` tool output, and in the
14
- `/subagents` overlay header. The footer never shows cost.
15
- 3. **Fixed-height, mutate-in-place progress.** Streaming blocks keep a stable
16
- shape (stats line + one `⎿ activity` line; parallel adds one line per task)
17
- and the same component identity is reused across partial renders.
18
- 4. **Trailing-edge streaming flush.** Structural updates (state transition, new
19
- session id, billed turn) emit immediately; live-text bursts coalesce with a
20
- deferred flush so the last update of a burst always lands.
21
-
22
- ## Surfaces
23
-
24
- ### Inline tool block (foreground runs)
25
- - `renderCall` is exactly one line: `subagent <task preview>` (or
26
- `N parallel tasks — first task…`, `wait a1b2c3d4`, `… · background`).
27
- - `renderResult` while streaming (fixed shape, spinner animates via wall-clock
28
- frame; Pi's working indicator drives repaints):
29
- ```
30
- ⠹ ↻3 · 12.4k tok · 8s
31
- ⎿ reading src/auth/middleware.ts…
32
- ```
33
- - Terminal single run:
34
- ```
35
- ↻8 · 33.8k tok · $0.012 · 12s
36
- ⎿ Found 5 middleware call sites…
37
- → /tmp/report.md
38
- ```
39
- - Parallel: one line per task with a themed state glyph
40
- (`◌ queued · ⠹ running · ✓ done · ✗ failed · ◐ partial · − cancelled · ◷ timeout`),
41
- per-task stats, and a one-line tail (live activity or first output line).
42
- - Expanded (Ctrl+O / `app.tools.expand`): full task output capped with a dim
43
- `… +N lines` trailer pointing at the artifact/child session.
44
- - Durations freeze at `endedAt`; running durations tick at render time.
45
- - Reliability annotations render inline: `[attempt 2]` during a retry,
46
- `[stalled 2m]` while the stall watchdog is flagging silence, and
47
- `◐ wrapped up` on budget-stopped runs that concluded gracefully.
48
-
49
- ### Footer status
50
- Terse and actionable only: `⚙ 2 running · 1 ready · /subagents`. Cleared when
51
- nothing is running or ready. No cost Pi's footer already shows session cost.
52
-
53
- ### Ambient widget (background runs only)
54
- An above-editor widget renders while `async: true` runs are live foreground
55
- runs already render inline as the tool result, so they never appear here
56
- (avoids double-render):
57
-
58
- ```
59
- Subagents
60
- ├─ ⠼ Audit deps · ↻4 · 18k tok · 41s
61
- │ ⎿ checking license headers…
62
- └─ ◌ License scan · 12s
63
- ```
64
-
65
- Cleared when the last background run settles. Spinner and elapsed animate on
66
- a 250ms interval that exists only while background runs are live.
67
-
68
- ### Completion notifications (background runs only)
69
- When an async run reaches a terminal state, a `steer` message (custom type
70
- `subagent-completion`) is queued for the parent LLM before its next LLM call,
71
- so it can react without polling. The human sees a themed compact box (state
72
- glyph, label, stats, one-line preview, artifact pointers); the LLM sees plain
73
- text with run ids and a `wait { id }` pointer.
74
-
75
- - Successes within a short window batch into one message (no fanout spam);
76
- failures bypass batching and flush immediately, carrying held successes.
77
- - A `wait` that already delivered the run suppresses the redundant
78
- notification (delivered-state is re-checked at flush time).
79
-
80
- ### `/subagents` overlay
81
- - Header: title + running/ready counters + full usage ledger + rule.
82
- - List: two lines per run — glyph/id/state/stats, then the task preview.
83
- Selection cursor `▶`, animated spinner for live runs.
84
- - Detail: run stats, summary, then per-task sections (glyph, label,
85
- model/profile/thinking, usage, pointers, transcript/final output/errors),
86
- scrollable with ↑↓/j/k and PageUp/PageDown.
87
- - Actions: `c` cancel, `s` steer (prompts for a message, injects it into the
88
- running child), `d` dismiss, `r` resume, `o` output pointers, `a` apply a
89
- finished run's changed worktree into the main checkout (confirm dialog),
90
- `x` discard worktree + branch (confirm dialog), Enter drill-down,
91
- Esc/b back, Esc/q close.
92
- - Live transcript (`t` on a **running** run's detail): tails the child's
93
- session file (`sessionDir/<…sessionId…>.jsonl`) on a 500ms poll while the
94
- pane is visible compact role/tool lines, auto-follow unless you scroll
95
- up (which pauses follow). No RPC reads; hidden/finished runs never poll.
96
- Missing file shows “waiting for child session…”. `s` steering still works
97
- from the same pane so observe steer stays on one surface.
98
-
99
- ### `/subagent-cost`
100
- Prints the root/subagents/combined ledger once, on demand.
101
-
102
- ### Mid-run steering
103
- Children run in Pi RPC mode, so their stdin stays open as a command channel.
104
- `action: "steer"` (or `s` in the overlay) queues a message that is delivered
105
- after the child's current assistant turn, before its next LLM call course
106
- correction without cancel + retry. Parallel runs steer one task via `index`.
107
-
108
- ### Worktree loop
109
- Finished runs with changed worktrees support `diff` / `apply` / `discard`
110
- actions (tool) and `a` / `x` keys (overlay). `apply` lands the worktree's
111
- combined patch (committed + uncommitted + untracked vs base) onto the main
112
- checkout as **uncommitted working-tree changes** via `git apply --3way`; it
113
- never commits and never deletes the worktree. `discard` is the explicit
114
- cleanup step and always confirms first.
115
-
116
- ### Parallel fan-in
117
- `synthesis: "<instruction>"` on a parallel run spawns one read-only child
118
- after all tasks settle that folds their outputs into a single brief, delivered
119
- first in the result. Synthesis failures degrade silently to raw results.
120
-
121
- ## States
122
- - **Queued/Running**: spinner + live stats + activity tail from live text.
123
- - **Completed/Partial/Failed/Cancelled/Timeout/Lost**: state glyph, frozen
124
- duration, usage summary, output pointers; failures show the error message.
125
- - **Delivered vs Undelivered**: footer/overlay track pending delivery.
126
- - **Notification**: one per terminal transition to avoid spam.
127
-
128
- ## Integration Notes
129
- - Extension wires via `ctx.ui.custom((tui, theme, kb, done) => createSubagentsOverlay(tui, theme, adapter, done), {overlay: true})`.
130
- - Adapter provides getActiveRuns/getCompletedRuns/cancelRun etc. without tight coupling.
131
- - Inline renderers reuse `context.lastComponent` (a `LineBlock`) so the row keeps
132
- a stable component identity across partial renders.
133
- - Streamed tool updates separate LLM-facing `content` (compact status string)
134
- from render-facing `details` (state, usage, live-text tail, run timing).
135
- - Tests combine pure UI models with a headless Pi extension harness: format
136
- helpers, block layouts (collapsed/streaming/terminal/parallel), navigation,
137
- truncation (ANSI-safe via `visibleWidth`), ready state, lifecycle/disposal.
138
- - Follows Pi TUI guidelines (render(width), handleInput, invalidate,
139
- requestRender, dispose). The overlay owns a single animation interval.
140
-
141
- See ARCHITECTURE.md for ownership boundaries. All rendering respects terminal width and ANSI safety.
1
+ # pi-subagent UX
2
+
3
+ ## Overview
4
+ The standalone pi-subagent provides rich TUI support for monitoring, inspecting, and interacting with isolated subagent runs (single and parallel modes): inline streaming blocks, a terse footer, an ambient widget for background runs, batched completion notifications, mid-run steering, a worktree apply loop, and the `/subagents` inspector. UI logic is kept independent from `runner`/`registry` via small structural adapters (`SubagentAdapter`).
5
+
6
+ ## Design principles
7
+
8
+ 1. **Pi's tool shell owns state signaling.** The Box wrapper paints
9
+ `toolPendingBg` / `toolSuccessBg` / `toolErrorBg`, so inline blocks do not
10
+ repeat state words or draw their own success/error framing.
11
+ 2. **Cost is a per-run attribute, not a competing ledger.** Dollar cost appears
12
+ inside the run's own result block; the parent/children/combined ledger is
13
+ available on demand via `/subagent-cost`, in `status` tool output, and in the
14
+ `/subagents` overlay header. The footer never shows cost.
15
+ 3. **Fixed-height, mutate-in-place progress.** Streaming blocks keep a stable
16
+ shape (stats line + one `⎿ activity` line; parallel adds one line per task)
17
+ and the same component identity is reused across partial renders.
18
+ 4. **Trailing-edge streaming flush.** Structural updates (state transition, new
19
+ session id, billed turn) emit immediately; live-text bursts coalesce with a
20
+ deferred flush so the last update of a burst always lands.
21
+
22
+ ## Surfaces
23
+
24
+ ### Inline tool block (foreground runs)
25
+ - `renderCall` is exactly one line: `subagent <task preview>` (or
26
+ `N parallel tasks — first task…`, `wait a1b2c3d4`, `… · background`).
27
+ - `renderResult` while streaming (fixed shape, spinner animates via wall-clock
28
+ frame; Pi's working indicator drives repaints):
29
+ ```
30
+ ⠹ ↻3 · 12.4k tok · 8s
31
+ ⎿ reading src/auth/middleware.ts…
32
+ ```
33
+ - Terminal single run:
34
+ ```
35
+ ↻8 · 33.8k tok · $0.012 · 12s
36
+ ⎿ Found 5 middleware call sites…
37
+ → /tmp/report.md
38
+ ```
39
+ - Parallel: one line per task with a themed state glyph
40
+ (`◌ queued · ⠹ running · ✓ done · ✗ failed · ◐ partial · − cancelled · ◷ timeout`),
41
+ per-task stats, and a one-line tail (live activity or first output line).
42
+ - Expanded (Ctrl+O / `app.tools.expand`): full task output capped with a dim
43
+ `… +N lines` trailer pointing at the artifact/child session.
44
+ - Expanded detail adds one bounded route line for Jev-routed runs: selected
45
+ execution model, selected tools (plus locally added control-plane tools),
46
+ selector version, confidence, outcome and selection latency. Legacy runs
47
+ simply have no route line.
48
+ - Durations freeze at `endedAt`; running durations tick at render time.
49
+ - Reliability annotations render inline: `[attempt 2]` during a same-model
50
+ retry, `[stalled 2m]` while the stall watchdog is flagging silence, and
51
+ `◐ wrapped up` on budget-stopped runs that concluded gracefully.
52
+
53
+ ### Footer status
54
+ Terse and actionable only: `⚙ 2 running · 1 ready · /subagents`. Cleared when
55
+ nothing is running or ready. No cost Pi's footer already shows session cost.
56
+
57
+ ### Ambient widget (background runs only)
58
+ An above-editor widget renders while `async: true` runs are live — foreground
59
+ runs already render inline as the tool result, so they never appear here
60
+ (avoids double-render):
61
+
62
+ ```
63
+ ● Subagents
64
+ ├─ ⠼ Audit deps · ↻4 · 18k tok · 41s
65
+ │ ⎿ checking license headers…
66
+ └─ License scan · 12s
67
+ ```
68
+
69
+ Cleared when the last background run settles. Spinner and elapsed animate on
70
+ a 250ms interval that exists only while background runs are live.
71
+
72
+ ### Completion notifications (background runs only)
73
+ When an async run reaches a terminal state, a `steer` message (custom type
74
+ `subagent-completion`) is queued for the parent LLM before its next LLM call,
75
+ so it can react without polling. The human sees a themed compact box (state
76
+ glyph, label, stats, one-line preview, artifact pointers); the LLM sees plain
77
+ text with run ids and a `wait { id }` pointer.
78
+
79
+ - Successes within a short window batch into one message (no fanout spam);
80
+ failures bypass batching and flush immediately, carrying held successes.
81
+ - A `wait` that already delivered the run suppresses the redundant
82
+ notification (delivered-state is re-checked at flush time).
83
+
84
+ ### `/subagents` overlay
85
+ - Header: title + running/ready counters + full usage ledger + rule.
86
+ - List: two lines per run — glyph/id/state/stats, then the task preview.
87
+ Selection cursor `▶`, animated spinner for live runs.
88
+ - Detail: run stats, summary, then per-task sections (glyph, label,
89
+ model/selector route/profile/thinking, usage, pointers, transcript/final
90
+ output/errors), scrollable with ↑↓/j/k and PageUp/PageDown.
91
+ - Actions: `c` cancel, `s` steer (prompts for a message, injects it into the
92
+ running child), `d` dismiss, `r` resume, `o` output pointers, `a` apply a
93
+ finished run's changed worktree into the main checkout (confirm dialog),
94
+ `x` discard worktree + branch (confirm dialog), Enter drill-down,
95
+ Esc/b back, Esc/q close.
96
+ - Live transcript (`t` on a **running** run's detail): tails the child's
97
+ session file (`sessionDir/<…sessionId…>.jsonl`) on a 500ms poll while the
98
+ pane is visible — compact role/tool lines, auto-follow unless you scroll
99
+ up (which pauses follow). No RPC reads; hidden/finished runs never poll.
100
+ Missing file shows “waiting for child session…”. `s` steering still works
101
+ from the same pane so observe → steer stays on one surface.
102
+
103
+ ### `/subagent-cost`
104
+ Prints the root/subagents/routing/combined ledger once, on demand. Routing
105
+ tokens appear as their own category and their currency as unreported; the known
106
+ dollar totals exclude that unreported selector spend.
107
+
108
+ ### Mid-run steering
109
+ Children run in Pi RPC mode, so their stdin stays open as a command channel.
110
+ `action: "steer"` (or `s` in the overlay) queues a message that is delivered
111
+ after the child's current assistant turn, before its next LLM call — course
112
+ correction without cancel + retry. Parallel runs steer one task via `index`.
113
+
114
+ ### Worktree loop
115
+ Finished runs with changed worktrees support `diff` / `apply` / `discard`
116
+ actions (tool) and `a` / `x` keys (overlay). `apply` lands the worktree's
117
+ combined patch (committed + uncommitted + untracked vs base) onto the main
118
+ checkout as **uncommitted working-tree changes** via `git apply --3way`; it
119
+ never commits and never deletes the worktree. `discard` is the explicit
120
+ cleanup step and always confirms first.
121
+
122
+ ### Parallel fan-in
123
+ `synthesis: "<instruction>"` on a parallel run asks for one read-only child
124
+ after all tasks settle that folds their outputs into a single brief, delivered
125
+ first in the result. It is best effort and deferred: its route is selected only
126
+ when aggregation is actually needed after the workers finish, so it never blocks
127
+ worker launch. A selector or child failure keeps the raw worker outputs, their
128
+ usage and a bounded `Optional synthesis blocked: …` diagnostic instead of
129
+ discarding or re-routing them.
130
+
131
+ ### Plan results (tool output, not TUI)
132
+ `action:"plan"` returns the resolved model/tools and the selector usage it
133
+ incurred, and states that a later dispatch selects again. It starts no child and
134
+ creates no run entry, so plan never adds an overlay row or ambient widget. A
135
+ plan whose optional synthesis selection fails still returns the valid worker plan
136
+ and labels only that synthetic stage blocked with its diagnostic.
137
+
138
+ ## States
139
+ - **Queued/Running**: spinner + live stats + activity tail from live text.
140
+ - **Completed/Partial/Failed/Cancelled/Timeout/Lost**: state glyph, frozen
141
+ duration, usage summary, output pointers; failures show the error message.
142
+ - **Delivered vs Undelivered**: footer/overlay track pending delivery.
143
+ - **Notification**: one per terminal transition to avoid spam.
144
+
145
+ ## Integration Notes
146
+ - Extension wires via `ctx.ui.custom((tui, theme, kb, done) => createSubagentsOverlay(tui, theme, adapter, done), {overlay: true})`.
147
+ - Adapter provides getActiveRuns/getCompletedRuns/cancelRun etc. without tight coupling.
148
+ - Inline renderers reuse `context.lastComponent` (a `LineBlock`) so the row keeps
149
+ a stable component identity across partial renders.
150
+ - Streamed tool updates separate LLM-facing `content` (compact status string)
151
+ from render-facing `details` (state, usage, live-text tail, run timing).
152
+ - Tests combine pure UI models with a headless Pi extension harness: format
153
+ helpers, block layouts (collapsed/streaming/terminal/parallel), navigation,
154
+ truncation (ANSI-safe via `visibleWidth`), ready state, lifecycle/disposal.
155
+ - Follows Pi TUI guidelines (render(width), handleInput, invalidate,
156
+ requestRender, dispose). The overlay owns a single animation interval.
157
+
158
+ See ARCHITECTURE.md for ownership boundaries. All rendering respects terminal width and ANSI safety.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cr1ms0n/pi-subagent",
3
- "version": "0.8.9",
4
- "description": "Community fork of Luke Parke's pi-subagent with explicit model policy and model visibility for Pi",
3
+ "version": "0.9.0",
4
+ "description": "Community fork of Luke Parke's pi-subagent with Jev model/tool routing and verified Pi child capabilities",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Luke Parke",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: subagent
3
- description: Delegate work to isolated child agents with the subagent tool model and thinking policy, explore/review/general profiles, parallel fanout with synthesis, worktree isolation and the diff/apply/discard loop, background runs, steering, output_schema, context fork, and backend tradeoffs (pi/codex/claude). Use when delegating exploration or implementation, running tasks in parallel, or when a subagent run needs inspecting, steering, or landing.
3
+ description: Delegate work to isolated child agents with the subagent tool. Jev routes each new dispatch to an execution model and individual tools from the user's configured candidate list; covers explore/review/general profiles, parallel fanout with best-effort synthesis, worktree isolation and the diff/apply/discard loop, background runs, steering, output_schema, context fork, and the Pi-only new-dispatch rule. Use when delegating exploration or implementation, running tasks in parallel, or when a subagent run needs inspecting, steering, or landing.
4
4
  ---
5
5
 
6
6
  # Subagent
@@ -20,24 +20,24 @@ from isolation, parallelism, or a fresh context.
20
20
  ## Core calls
21
21
 
22
22
  ```ts
23
- // Examples use placeholders only. Replace these with the exact model selected
24
- // from the current modelPolicy route; they do not configure a real model.
25
- const routeModel = "<exact model from current modelPolicy route>";
23
+ // Omit model and fallback_models. Jev selects the execution model from the
24
+ // user's configured candidate list and the individual tools from the locally
25
+ // permitted catalog. An explicit model/fallback is rejected on new work.
26
26
 
27
27
  // Single foreground task (default profile: general)
28
- { task: "Find call sites of parseConfig", description: "Map parseConfig", model: routeModel }
28
+ { task: "Find call sites of parseConfig", description: "Map parseConfig" }
29
29
 
30
30
  // Parallel read-only explorers (default profile for tasks[]: explore)
31
31
  {
32
32
  tasks: [
33
- { task: "Map auth middleware", description: "Auth flow", model: routeModel },
34
- { task: "List env vars in server/", description: "Env inventory", model: routeModel }
33
+ { task: "Map auth middleware", description: "Auth flow" },
34
+ { task: "List env vars in server/", description: "Env inventory" }
35
35
  ],
36
36
  synthesis: "Merge into one prioritized brief"
37
37
  }
38
38
 
39
- // Background notified on completion; wait/status still work
40
- { task: "Audit dependency licenses", model: routeModel, async: true }
39
+ // Background: notified on completion; wait/status still work
40
+ { task: "Audit dependency licenses", async: true }
41
41
  { action: "status", id: "abc123" }
42
42
  { action: "wait", id: "abc123" } // interruptible; does not cancel
43
43
  { action: "cancel", id: "abc123" }
@@ -45,69 +45,98 @@ const routeModel = "<exact model from current modelPolicy route>";
45
45
  // subagent_wait { id: "abc123", timeout_ms?: number }
46
46
 
47
47
  // Worktree loop
48
- { task: "Implement feature A", model: routeModel, profile: "general", isolation: "worktree" }
48
+ { task: "Implement feature A", profile: "general", isolation: "worktree" }
49
49
  { action: "diff", id: "abc123", index: 1 }
50
50
  { action: "apply", id: "abc123", index: 1 }
51
51
  { action: "discard", id: "abc123", index: 1 }
52
52
 
53
- // Dry-run validation + resolved plan (no spawn)
54
- // plan is a dry-run, but every task still needs the policy-routed model.
55
- { action: "plan", tasks: [{ task: "…", model: routeModel, isolation: "worktree" }] }
53
+ // Dry-run validation + resolved plan (no spawn).
54
+ // plan calls Jev and incurs selector fees, then a later dispatch selects again.
55
+ { action: "plan", tasks: [{ task: "…", isolation: "worktree" }] }
56
56
  ```
57
57
 
58
58
  ## Profiles
59
59
 
60
60
  | Profile | Tools | Writes |
61
61
  | --------- | --------------------------------------------------------- | ------------------------------------------- |
62
- | `explore` | read/search/ls (+safe) + Pi context tools | no project-file writes |
62
+ | `explore` | locally permitted read-only tools + Pi context tools | no project-file writes |
63
63
  | `review` | same as explore | no project-file writes |
64
- | `general` | inherited active tools + Pi context tools | yes if tools include bash/edit/write |
64
+ | `general` | Jev chooses from the full available locally permitted catalog + Pi context tools | yes if the selected tools include bash/edit/write |
65
+
66
+ Jev picks individual tool names, not a capability bundle. Candidates come from
67
+ the full available locally permitted catalog, not from agent `tools` defaults and
68
+ not from only the parent's active tools. An explicit `tools` list is a ceiling,
69
+ explore/review stay read-only regardless of the answer, and an empty selection
70
+ never means "all tools".
65
71
 
66
72
  For Pi children, `new_context`, `get_context_remaining`, `history`, and
67
- `notes` are control-plane tools. When available in the parent they remain in
68
- the child allowlist—even if a narrower tool list was requested—so Pi's remote
69
- `contextManagement` can stay active. They may update context notes/window
70
- state, but never grant `bash`, `edit`, or `write` access.
73
+ `notes` are added locally when the parent exposes them, so the selector never
74
+ asks about them. They are control-plane tools: they may update context
75
+ notes/window state, but never grant `bash`, `edit`, or `write` access. Route
76
+ metadata reports them as local additions.
77
+
78
+ The finalized tool subset is passed to the child as Pi's `--tools` allowlist
79
+ (`--no-tools` for a true empty set). Pi 0.86.0 is the verified baseline for
80
+ built-in, extension and late-registered tool enforcement; an unsupported host is
81
+ refused rather than silently weakened.
71
82
 
72
83
  Parallel write-capable tasks sharing one checkout are rejected unless each uses
73
84
  `isolation: "worktree"`, a distinct `cwd`, or `allow_shared_writes: true`.
74
85
 
75
86
  ## Backends
76
87
 
77
- `backend: "pi" | "codex" | "claude"` (default `pi`). Unsupported combinations are
78
- **refused**, not silently degraded:
79
-
80
- | | pi | codex | claude |
81
- | ------------------------ | -------------- | ---------------------- | -------------- |
82
- | `max_cost` | yes | refused (tokens only) | yes |
83
- | read-only profile | tool allowlist | OS sandbox | tool allowlist |
84
- | steering / grace wrap-up | yes | no | no |
85
- | `context: "fork"` | yes | refused | yes |
86
- | `thinking` | yes | no | no |
87
- | `output_schema` | yes | yes | yes |
88
+ New dispatch is Pi-only. `backend: "codex"` or `backend: "claude"` on new work
89
+ is **refused** before any selector or provider work, including a backend
90
+ inherited from agent frontmatter, and is never silently switched to Pi. Existing
91
+ Codex/Claude runs remain manageable through `status`/`wait`/`cancel`/`steer`/
92
+ `diff`/`apply`/`discard`.
93
+
94
+ Another provider's execution model is still eligible through Pi when the user
95
+ lists it in their candidate configuration. Unsupported combinations inside the
96
+ Pi path are **refused**, not silently degraded:
97
+
98
+ | | pi |
99
+ | ------------------------ | -------------- |
100
+ | `max_cost` | yes (provider-reported execution only; not selector currency) |
101
+ | read-only profile | tool allowlist |
102
+ | steering / grace wrap-up | yes |
103
+ | `context: "fork"` | yes |
104
+ | `thinking` | yes |
105
+ | `output_schema` | yes |
88
106
 
89
107
  ## Budgets and safety
90
108
 
91
109
  - Prefer `max_turns`, `max_cost`, and/or `timeout_ms` on long or write-capable runs.
110
+ `timeout_ms` is absolute: local preflight, Jev selection, setup, queue and
111
+ runtime all count against it.
92
112
  - `output_schema` asks the child for a fenced `json:result` block (one repair round).
93
- - `context: "fork"` continues from a fork of the parent session (pi/claude).
94
- - Do not poll `status` in a tight loop use `wait` / `subagent_wait`, or let the
113
+ - `context: "fork"` continues from a fork of the parent session.
114
+ - Do not poll `status` in a tight loop. Use `wait` / `subagent_wait`, or let the
95
115
  completion notification arrive for `async: true` runs.
96
116
  - Point the user at `/subagents` for the live inspector and `/subagent-cost` for
97
- the root / subagent / combined ledger.
98
-
99
- ## Model policy
100
-
101
- Every new task must pass a `model` that exactly matches the current
102
- `modelPolicy` mapping in `~/.pi/subagent.json`; agent frontmatter,
103
- `taskDefaults.model`, and parent-session model inheritance are ignored. An
104
- agent route replaces the default route, and configured fallback order is
105
- immutable. Omit `fallback_models` to use the route; if supplied, it must match
106
- exactly. An optional route `thinking` value is an opaque Pi thinking-level
107
- string; common values include `off`, `minimal`, `low`, `medium`, `high`, `xhigh`,
108
- and `max`, but model-specific values are passed through unchanged. It is a
109
- default; explicit task, agent, and profile `taskDefaults.thinking` values
110
- override it. Management actions do not require model. The extension re-reads
111
- this policy on each dispatch and injects it into
112
- the parent prompt. If the policy is missing or invalid, management remains
113
- available but new spawns and synthesis are rejected.
117
+ the root / subagent / routing / combined ledger. Routing cost is reported as
118
+ unreported (tokens only, no currency).
119
+
120
+ ## Routing
121
+
122
+ Omit `model` and `fallback_models` on every new call: both are legacy fields,
123
+ and an explicit value is rejected rather than bypassing selection. Jev chooses
124
+ one execution model from the user's dedicated candidate list plus an individual
125
+ include/exclude decision per eligible tool. The local policy then re-validates
126
+ the answer: unknown or unsafe tools cannot launch, explore/review stay read-only,
127
+ and management actions need no routing config or credential.
128
+
129
+ There are no emergency or fallback models, and low confidence is accepted rather
130
+ than treated as a threshold. A Jev timeout or API failure stops the affected new
131
+ dispatch with an actionable error; existing runs stay queryable and cancellable.
132
+ Transient child failures retry the already selected model and tool set within the
133
+ original deadline, up to `max_retries`; a quality failure never reselects.
134
+
135
+ An optional candidate `thinking` value is an opaque Pi thinking-level string;
136
+ common values include `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and
137
+ `max`, but model-specific values are passed through unchanged. It is a default:
138
+ explicit task, agent, and profile `taskDefaults.thinking` values override it.
139
+ The extension re-reads `jevRouting` on each dispatch and injects the current
140
+ routing guidance into the parent prompt. If `jevRouting` is missing or invalid,
141
+ or the credential environment variable is unset, management remains available but
142
+ new spawns, `/btw`, plan, resume, fork and synthesis are rejected.