@cr1ms0n/pi-subagent 0.8.8 → 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/CHANGELOG.md CHANGED
@@ -1,14 +1,22 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## Unreleased
4
+
5
+ ## 0.9.0 — 2026-09-21
4
6
 
5
- ## 0.8.8 2026-09-16
7
+ ### Jev model and tool routing
6
8
 
7
- ### Remote Context tools are target-model gated
9
+ - Replace fixed `modelPolicy` routes for extension-managed work with required Jev selection from user-described, locally available model candidates and individual permitted tools. New work is Pi-only; management and explicit low-level SDK specs remain available.
10
+ - Route normal/parallel/background dispatch, paid plan, `/btw`, resume/fork and deferred synthesis through the shared selector. Reject legacy explicit model/fallback fields; accept valid low-confidence decisions without substituting an emergency model.
11
+ - Verify the child model and exact selected tool set before sending task text. Carry cancellation/deadlines through routing, preflight, startup and same-model retries.
12
+ - Persist bounded per-request selector receipts separately from execution usage, with deduplicated native delivery and unreported selector currency. Preserve resume-lock safety, fanout usage and retained worktree pointers across failures.
13
+ - Document manual configuration migration, minimum disclosure and rollback. No automatic settings rewrite, installation or live provider evaluation is included.
8
14
 
9
- - Restrict `new_context`, `get_context_remaining`, `history`, and `notes` to Pi children whose target model is an exact `compaction.gatewayContextModels` entry of the operator-owned `pi-openai-toolkit` config while `compaction.contextManagement` is `remote`. The previous 0.8.6 behavior appended these tools to every Pi child whenever the parent exposed them, so a non-allowlisted child could receive context tools simply because the parent session had them.
10
- - Matching is exact and case-sensitive on the full `provider/model` string; prefixes, bare model ids, and native `openai-codex` identity do not match. Explicit requests for a context tool on a non-eligible target are rejected as unavailable, inherited/default lists are filtered, and retry fallback attempts are re-filtered per attempt so a disallowed fallback cannot inherit them. The internally constructed synthesis child uses the same gate.
11
- - Missing, unreadable, invalid, or non-`remote` toolkit configuration now fails closed to no context tools. Non-Pi backends, read-only/write classification, and model-policy routing are unchanged. The package reads the existing operator-owned allowlist and does not duplicate it in `~/.pi/subagent.json`.
15
+ ## 0.8.9 2026-09-16
16
+
17
+ ### Restore the stable 0.8.7 behavior
18
+
19
+ - Restore the `0.8.7` source behavior after the withdrawn `0.8.8` release. Remote Context ownership and model gating remain in the separate `pi-openai-toolkit` project.
12
20
 
13
21
  ## 0.8.7 — 2026-09-14
14
22
 
package/README.md CHANGED
@@ -2,16 +2,17 @@
2
2
 
3
3
  This is an independent community fork of **@parke.dev/pi-subagent 0.8.0**, originally by Luke Parke. It is not an official upstream release. The original MIT license and copyright are preserved. Upstream source: [LukasParke/pi-extensions](https://github.com/LukasParke/pi-extensions/tree/main/packages/pi-subagent).
4
4
 
5
- This fork requires a user-owned `modelPolicy` in `~/.pi/subagent.json` before new subagent tasks can run, and displays actual models in the TUI.
5
+ This fork replaces the upstream fixed model route with mandatory Jev routing. New subagent dispatches require a `jevRouting` block in `~/.pi/subagent.json` (a dedicated candidate-model list plus the name of the environment variable holding your TypeSafe credential), and the TUI shows the selected execution model and tools.
6
6
 
7
7
  Production-grade isolated subagents for [Pi](https://github.com/badlogic/pi-mono).
8
8
 
9
9
  Delegate research, parallel exploration, and clean-context review to child Pi
10
- processes. Named agent personas, cancellable background runs with completion
11
- notifications and a live widget, mid-run steering, graceful budget wrap-ups,
12
- automatic retry with model fallback, a stall watchdog, session resume and
13
- context forking, worktree isolation with a diff/apply/discard loop, capability
14
- profiles, a root/subagent/combined cost ledger, and a TUI inspector.
10
+ processes. Named agent personas, Jev-selected execution models and individual
11
+ tools, cancellable background runs with completion notifications and a live
12
+ widget, mid-run steering, graceful budget wrap-ups, bounded same-model retry, a
13
+ stall watchdog, session resume and context forking, worktree isolation with a
14
+ diff/apply/discard loop, capability profiles, a root/subagent/combined cost
15
+ ledger, and a TUI inspector.
15
16
 
16
17
  ## Install
17
18
 
@@ -45,17 +46,18 @@ This fork is published manually after pack-content and offline checks — see
45
46
  ## Quick usage
46
47
 
47
48
  ```ts
48
- // Single foreground task; `model` must exactly match ~/.pi/subagent.json modelPolicy.
49
- { task: "Find all call sites of parseConfig and summarize patterns.", description: "Map parseConfig usage", model: "<provider/model-id>" }
49
+ // Single foreground task (default profile: general). Omit model and
50
+ // fallback_models: Jev selects the execution model from your configured list.
51
+ { task: "Find all call sites of parseConfig and summarize patterns.", description: "Map parseConfig usage" }
50
52
 
51
- // Named agent persona prompt from .pi/agents/reviewer.md; model still comes from modelPolicy.
52
- { task: "Review this diff for security issues", agent: "reviewer", model: "<provider/model-id>" }
53
+ // Named agent: persona prompt from .pi/agents/reviewer.md. Jev still picks the model.
54
+ { task: "Review this diff for security issues", agent: "reviewer" }
53
55
 
54
- // Parallel read-only explorers (default profile: explore); each task needs its mapped model.
56
+ // Parallel read-only explorers (default profile for tasks[]: explore).
55
57
  {
56
58
  tasks: [
57
- { task: "Map auth middleware flow", description: "Auth flow map", model: "<provider/model-id>" },
58
- { task: "List all env vars used in server/", description: "Env var inventory", model: "<provider/model-id>" }
59
+ { task: "Map auth middleware flow", description: "Auth flow map" },
60
+ { task: "List all env vars used in server/", description: "Env var inventory" }
59
61
  ]
60
62
  }
61
63
 
@@ -63,15 +65,14 @@ This fork is published manually after pack-content and offline checks — see
63
65
  // outputs into a single brief, delivered first.
64
66
  {
65
67
  tasks: [
66
- { task: "Audit backend error handling", description: "Backend audit", model: "<exact model from current modelPolicy route>" },
67
- { task: "Audit frontend error handling", description: "Frontend audit", model: "<exact model from current modelPolicy route>" }
68
+ { task: "Audit backend error handling", description: "Backend audit" },
69
+ { task: "Audit frontend error handling", description: "Frontend audit" }
68
70
  ],
69
71
  synthesis: "Merge both audits into one prioritized findings list"
70
72
  }
71
73
 
72
- // Background run model is still required; the live widget shows the actual model
73
- // and changes when a configured fallback is selected.
74
- { task: "Audit dependency licenses", model: "<provider/model-id>", async: true }
74
+ // Background run: the widget shows the selected model; expanded results include tools.
75
+ { task: "Audit dependency licenses", async: true }
75
76
  // later
76
77
  { action: "status", id: "abc123" }
77
78
  { action: "wait", id: "abc123" } // interruptible; does not cancel
@@ -84,17 +85,17 @@ This fork is published manually after pack-content and offline checks — see
84
85
  subagent_wait { id: "abc123" }
85
86
  subagent_wait { id: "abc123", timeout_ms: 30000 }
86
87
 
87
- // Dry-run a spawn request: full validation + preflights (git repo, fork
88
- // session, output paths), returns the resolved per-task plan (model, tools,
89
- // budgets, isolation) without spawning anything.
90
- { action: "plan", tasks: [{ task: "Implement feature A", model: "<exact model from current modelPolicy route>", isolation: "worktree" }] }
88
+ // Dry-run a spawn request: full validation, Jev selection and local preflights
89
+ // (git repo, fork session, output paths). Returns the resolved per-task plan
90
+ // (model, tools, budgets, isolation) and its selector usage without spawning.
91
+ // Selector fees apply, and a later dispatch selects again.
92
+ { action: "plan", tasks: [{ task: "Implement feature A", profile: "general", isolation: "worktree" }] }
91
93
 
92
94
  // Structured output: the child must end with a fenced json:result block
93
95
  // matching the schema. Invalid output gets one automatic repair round;
94
96
  // delivery is the clean JSON and details carry the parsed object.
95
97
  {
96
98
  task: "Audit the auth module",
97
- model: "<exact model from current modelPolicy route>",
98
99
  output_schema: {
99
100
  type: "object",
100
101
  required: ["findings", "risk"],
@@ -107,15 +108,15 @@ subagent_wait { id: "abc123", timeout_ms: 30000 }
107
108
 
108
109
  // Fork the parent conversation into the child (needs a persisted session).
109
110
  // The child starts from a branched copy of everything discussed so far.
110
- { task: "Implement the plan we agreed on", model: "<exact model from current modelPolicy route>", context: "fork", profile: "general" }
111
+ { task: "Implement the plan we agreed on", context: "fork", profile: "general" }
111
112
 
112
113
  // Budgets with graceful wrap-up: at the limit the child is steered to produce
113
114
  // a final answer and given grace turns before any hard stop.
114
- { task: "Audit deps", model: "<exact model from current modelPolicy route>", max_turns: 15, grace_turns: 2 }
115
+ { task: "Audit deps", max_turns: 15, grace_turns: 2 }
115
116
 
116
- // Automatic retry uses the configured fallback order. If fallback_models is
117
- // supplied, it must exactly match modelPolicy; otherwise omit it.
118
- { task: "Research X", model: "<provider/model-id>", max_retries: 1 }
117
+ // Transient child failures retry the already selected model and tool set, up to
118
+ // max_retries. There are no emergency or fallback models.
119
+ { task: "Research X", max_retries: 1 }
119
120
 
120
121
  // Steer a running child mid-run instead of cancel + retry. The message is
121
122
  // delivered after the current assistant turn, before the next LLM call.
@@ -123,14 +124,14 @@ subagent_wait { id: "abc123", timeout_ms: 30000 }
123
124
  // Parallel runs: pass index to pick one live task.
124
125
  { action: "steer", id: "abc123", index: 1, message: "Wrap up now" }
125
126
 
126
- // Resume a child session
127
- { task: "Continue from your findings and propose a fix plan", model: "<exact model from current modelPolicy route>", resume: "<session-id>" }
127
+ // Resume a child session. The new invocation selects again.
128
+ { task: "Continue from your findings and propose a fix plan", resume: "<session-id>" }
128
129
 
129
130
  // Isolated writers
130
131
  {
131
132
  tasks: [
132
- { task: "Implement feature A", model: "<exact model from current modelPolicy route>", profile: "general", isolation: "worktree" },
133
- { task: "Implement feature B", model: "<exact model from current modelPolicy route>", profile: "general", isolation: "worktree" }
133
+ { task: "Implement feature A", profile: "general", isolation: "worktree" },
134
+ { task: "Implement feature B", profile: "general", isolation: "worktree" }
134
135
  ]
135
136
  }
136
137
 
@@ -159,19 +160,27 @@ conversation or polluting the context window.
159
160
 
160
161
  ## Backends
161
162
 
162
- Children can run on a different agent CLI. Everything else worktrees, process
163
- locks, depth limits, budgets, orphan reclaim is backend-agnostic and applies
164
- unchanged.
163
+ Jev routing manages Pi-backed new dispatch only. A `backend: "codex"` or
164
+ `backend: "claude"` new task is refused before any selector or provider work,
165
+ including a backend inherited from agent frontmatter; the extension never
166
+ silently switches it to Pi. Existing Codex/Claude runs stay manageable:
167
+ `status`, `wait`, `cancel`, `steer`, `diff`, `apply` and `discard` all still
168
+ work. Provider diversity is not lost, because another provider's execution
169
+ model stays eligible through Pi once it is in your configured candidate list.
165
170
 
166
171
  ```ts
167
- { task: "Summarize this module", model: "<exact model from current modelPolicy route>", backend: "codex", profile: "explore" }
168
- { task: "Review this diff", model: "<exact model from current modelPolicy route>", backend: "claude", max_cost: 0.50 }
172
+ // Refused on new work. Use the Pi-backed path so Jev can route it.
173
+ { task: "Summarize this module", backend: "codex", profile: "explore" }
169
174
  ```
170
175
 
171
- Requires the corresponding CLI on PATH (`codex`, `claude`). Capabilities differ,
172
- and **unsupported combinations are refused with an explanation rather than
173
- silently ignored** a dropped `max_cost` or unenforced read-only profile would
174
- be a safety regression, not a minor degradation.
176
+ The low-level SDK is a different contract: `runTasks`/`runSubagent` execute the
177
+ explicit `TaskSpec` you hand them, so embedding code can still select a backend
178
+ directly. Everything else (worktrees, process locks, depth limits, budgets,
179
+ orphan reclaim) is backend-agnostic and applies unchanged.
180
+
181
+ Capabilities differ, and **unsupported combinations are refused with an
182
+ explanation rather than silently ignored**: a dropped `max_cost` or unenforced
183
+ read-only profile would be a safety regression, not a minor degradation.
175
184
 
176
185
  | | `pi` (default) | `codex` | `claude` |
177
186
  | ------------------------------- | -------------- | ------------------------------------- | ---------------------- |
@@ -187,36 +196,43 @@ A budget breach on a backend without steering hard-stops instead of asking the
187
196
  child to wrap up. Codex's read-only sandbox is enforced by the OS, which is
188
197
  stronger than a tool allowlist.
189
198
 
190
- Set a persona's backend in agent frontmatter with `backend: codex`.
199
+ Agent frontmatter `backend:` remains a default. New extension-managed work rejects
200
+ any effective backend other than Pi, including a native backend inherited from an agent.
201
+ Direct SDK specs retain the backend capabilities listed above.
191
202
 
192
203
  ## Profiles
193
204
 
194
205
  | Profile | Tools | Writes |
195
206
  | ---------------------------- | ------------------------------------------------------- | ------------------------------------------- |
196
- | `explore` (parallel default) | read/grep/find/ls + safe extras + allowlisted context tools | no project-file writes |
207
+ | `explore` (parallel default) | locally permitted read-only tools + Pi context tools | no project-file writes |
197
208
  | `review` | same as explore | no project-file writes |
198
- | `general` | inherited active tools + allowlisted context tools | yes if tools include bash/edit/write |
209
+ | `general` | Jev chooses from the full available locally permitted catalog + Pi context tools | yes for selected write-capable tools; unknown custom tools count as writable |
210
+
211
+ Jev chooses individual tool names, not a capability bundle. Candidates come
212
+ from the full available locally permitted catalog, not from the agent file's
213
+ `tools` defaults and not from the parent's currently active tools. An explicit
214
+ task `tools` list is a ceiling, and explore/review keep their read-only rule
215
+ regardless of what the selector returns. An empty selection never means "all
216
+ tools".
199
217
 
200
218
  For the Pi backend, the context-management tools `new_context`,
201
- `get_context_remaining`, `history`, and `notes` are retained in child tool
202
- allowlists only when all three hold: the toolkit config at
203
- `$PI_CODING_AGENT_DIR/extensions/pi-openai-toolkit/config.json` (default:
204
- `~/.pi/agent/extensions/pi-openai-toolkit/config.json`) has
205
- `compaction.contextManagement: "remote"`, the child's target model exactly
206
- equals one `compaction.gatewayContextModels` entry, and the parent exposes the
207
- tool. Eligibility is exact and case-sensitive on the full `provider/model`
208
- string — prefixes, bare model ids, and native `openai-codex` identity do not
209
- match. The package reads that operator-owned list and never duplicates it.
210
-
211
- Context tools are control-plane tools: they may update continuity notes or the
212
- remote context window, but cannot modify the child checkout or run a shell
213
- command. For an eligible target they are appended even when the task supplies a
214
- narrower tool list; for every other Pi target they are removed from inherited
215
- tools, never passed to `--tools`, and an explicit request for one is rejected as
216
- unavailable. Retry fallback attempts are filtered per attempt, so a disallowed
217
- fallback cannot inherit them. If the toolkit file is missing, unreadable,
218
- invalid, or not in Remote Context mode, the effective allowlist is empty (fail
219
- closed). Non-Pi backends are unchanged.
219
+ `get_context_remaining`, `history`, and `notes` are added locally when the
220
+ parent exposes them, so they are never a selector question. They are
221
+ control-plane tools: they may update continuity notes or the remote context
222
+ window, but cannot modify the child checkout or run a shell command. This
223
+ exception also applies when a task supplies a narrower tool list, so Pi's
224
+ `contextManagement` remains usable for configured gateway models. Locally added
225
+ controls are reported in the route metadata.
226
+
227
+ The finalized tool set is passed to the child as Pi's `--tools` allowlist
228
+ (`--no-tools` for a true empty set). Pi 0.86.0 is the verified baseline for
229
+ built-in, extension and late-registered tool enforcement; a host that cannot
230
+ honor that allowlist is refused rather than silently weakened, and the extension
231
+ does not claim identical behavior on untested older releases. Before the real
232
+ task prompt is sent, the child is also asked to confirm the selected model and
233
+ the finalized tool names through a verified private startup command; if the host
234
+ cannot verify that command or the child cannot confirm both, the launch aborts
235
+ with a startup diagnostic instead of running with a broader tool set.
220
236
 
221
237
  Parallel write-capable tasks sharing one checkout are rejected unless each uses
222
238
  `isolation: "worktree"`, distinct `cwd`, or explicit `allow_shared_writes: true`.
@@ -242,6 +258,7 @@ Defaults can be overridden in `~/.pi/subagent.json` and per-field via env vars
242
258
  | `sessionRetentionDays` | `PI_SUBAGENT_SESSION_RETENTION_DAYS` | unused (lifecycle GC) |
243
259
  | `lockRetentionDays` | `PI_SUBAGENT_LOCK_RETENTION_DAYS` | 7 |
244
260
  | `taskDefaults` | — | none |
261
+ | `jevRouting` | - | required for new dispatch (see below) |
245
262
  | `graceTurns` | `PI_SUBAGENT_GRACE_TURNS` | 2 |
246
263
  | `stallAfterMs` | `PI_SUBAGENT_STALL_AFTER_MS` | 90000 |
247
264
  | `stallKillAfterMs` | `PI_SUBAGENT_STALL_KILL_AFTER_MS` | 90000 |
@@ -250,52 +267,93 @@ Defaults can be overridden in `~/.pi/subagent.json` and per-field via env vars
250
267
  | `notifications` | `PI_SUBAGENT_NOTIFICATIONS` | `batched` (`off` disables) |
251
268
  | (bin) | `PI_SUBAGENT_BIN` | auto (`process.execPath` + CLI entry) |
252
269
 
253
- ### Model policy
270
+ ### Jev routing
254
271
 
255
- New spawns are routed only by the user-owned `modelPolicy` in `~/.pi/subagent.json`.
256
- Agent frontmatter, `taskDefaults`, parent-session model inheritance, and ad-hoc
257
- fallback lists are ignored for model selection. The minimal template is:
272
+ New subagent dispatches are selected by Jev, TypeSafe's structured-decision API,
273
+ against a dedicated candidate list you maintain in `~/.pi/subagent.json`. Fixed
274
+ routing is gone: a `modelPolicy` block produces a migration error, and an
275
+ explicit `model` or `fallback_models` on new work is rejected rather than
276
+ bypassing selection. Management actions (`status`, `wait`, `cancel`, `steer`,
277
+ `diff`, `apply`, `discard`) never call the selector and need no credential.
258
278
 
259
279
  ```json
260
280
  {
261
- "modelPolicy": {
262
- "default": {
263
- "model": "<provider/model-id>",
264
- "fallbackModels": [],
265
- "thinking": "medium"
266
- },
267
- "agents": {
268
- "<agent-name>": {
281
+ "jevRouting": {
282
+ "selectorModel": "jev-latest",
283
+ "apiKeyEnv": "TYPESAFE_API_KEY",
284
+ "timeoutMs": 15000,
285
+ "models": [
286
+ {
269
287
  "model": "<provider/model-id>",
270
- "fallbackModels": [],
271
- "thinking": "high"
288
+ "description": "<your characteristics notes, Chinese allowed>",
289
+ "thinking": "<optional opaque Pi thinking default>"
272
290
  }
273
- }
291
+ ]
274
292
  }
275
293
  }
276
294
  ```
277
295
 
296
+ - `selectorModel` defaults to the stable alias `jev-latest`. Pin an exact
297
+ version to control which selector version is requested. This does not guarantee
298
+ deterministic choices; the extension records the version that actually answered.
299
+ - `apiKeyEnv` defaults to `TYPESAFE_API_KEY`. Only the variable *name* lives in
300
+ the file. Set the credential in your local environment; the extension reads it
301
+ at request time and never writes it to config, prompts, argv, logs or results.
302
+ - `timeoutMs` defaults to 15000 and must be an integer between 100 and 600000.
303
+ It bounds one logical task selection, including all its HTTP requests and queue
304
+ waits. Parallel workers each have a selection allowance, still capped by their
305
+ absolute task `timeout_ms` deadline. Deferred synthesis has a separate allowance.
306
+ - `models` holds 1 to 255 entries, each with an exact `provider/model-id` and a
307
+ non-blank description. Those descriptions are what Jev matches against your
308
+ task, so write them the way you would explain the model to a colleague.
309
+ `thinking` is optional.
310
+
311
+ Plan and background-start native usage attachments are limited to 1024 selector
312
+ HTTP receipts per invocation. Larger requests fail with a request-splitting error;
313
+ background work has not started at that point. Previously incurred selector tokens
314
+ remain in the ledger. This bounds an atomic delivery record, not the number of tools
315
+ Jev may consider within each task.
316
+
317
+ The candidate list is intersected with the models the local Pi registry reports
318
+ as available. A configured model Pi cannot resolve is not eligible, and an empty
319
+ eligible pool fails before any request. Adding a model anywhere else in Pi does
320
+ not authorize it, and legacy `modelPolicy` entries are never imported
321
+ automatically. An unknown `jevRouting` field is an error, not a silent default.
322
+
323
+ Jev receives only the current delegated task text, your model IDs and
324
+ descriptions, candidate tool names and descriptions, and the permission/output
325
+ requirements it needs to choose. It does not receive repository files,
326
+ conversation history, full system prompts, persona text or tool parameter
327
+ schemas. Task text and descriptions are user content and may contain sensitive
328
+ material, so treat what you delegate as disclosure to TypeSafe.
329
+
330
+ Every new extension-managed dispatch routes through Jev: `task`/`tasks[]`,
331
+ `action:"plan"`, `/btw`, resume, fork, locally permitted nested dispatch and the
332
+ optional `synthesis` child. `action:"plan"` calls Jev and runs the same local
333
+ preflights, returns the resolved model/tool plan and the selector usage, and
334
+ creates no child or run entry. A later dispatch selects again; there is no cached
335
+ decision to reuse. If optional synthesis selection fails, the worker plan and its
336
+ usage stay valid and synthesis is reported as blocked with its diagnostic.
337
+
278
338
  `thinking` is optional and is an opaque Pi thinking-level string. Common values
279
339
  include `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`, but the
280
340
  package does not remap or restrict model-specific values. Pi receives the value
281
- unchanged and decides whether the active model supports it. It is a route default,
282
- not a strict policy value. Resolution order is: explicit task `thinking` > agent
283
- frontmatter `thinking` > profile `taskDefaults.<profile>.thinking` > the selected
284
- `modelPolicy` route's `thinking` > the parent session's thinking level. Omitting it
285
- preserves the existing behavior.
286
-
287
- Every new `task`/`tasks[]` item must pass the exact mapped `model`. Omit
288
- `fallback_models` to use the configured list; when supplied it must match the
289
- configured order exactly. Management actions remain available when this file is
290
- missing, but new spawns and synthesis are rejected until it is valid. The
291
- extension re-reads the policy on each dispatch and injects the current mapping
292
- into the parent prompt. No provider catalog or credentials are read.
293
-
294
- This guarantee applies to the Extension tool dispatch and its internal synthesis
295
- handoff. The stable SDK exports (`runTasks` and `runSubagent`) are trusted
296
- low-level library APIs and intentionally do not load the Extension's
297
- `modelPolicy`; library callers must parse and validate their own policy before
298
- passing `TaskSpec` values. Do not treat direct SDK calls as policy enforcement.
341
+ unchanged and decides whether the active model supports it. Resolution order is:
342
+ explicit task `thinking` > agent frontmatter `thinking` > profile
343
+ `taskDefaults.<profile>.thinking` > the selected candidate's optional `thinking`
344
+ > the parent session's thinking level. Jev never chooses a thinking level.
345
+
346
+ The extension re-reads `jevRouting` on each dispatch and injects the current
347
+ routing guidance into the parent prompt, so configuration edits reach the next
348
+ decision without a code change. Missing or invalid `jevRouting`, or a missing
349
+ credential environment variable, rejects new dispatch and plan with a remedy;
350
+ management stays available.
351
+
352
+ The mandatory routing above is the Extension dispatch contract. The stable SDK
353
+ exports (`runTasks` and `runSubagent`) are trusted low-level library APIs: they
354
+ execute the explicit `TaskSpec` you pass and perform no implicit routing, config
355
+ discovery or network call. Library callers own model and tool choice, and must not
356
+ read these SDK calls as Jev enforcement.
299
357
 
300
358
  ### Named agent files
301
359
 
@@ -314,7 +372,7 @@ supplies defaults using the same snake_case names as the tool parameters:
314
372
  ```md
315
373
  ---
316
374
  description: Security-focused code reviewer
317
- # Legacy model/fallback fields are ignored; use modelPolicy instead.
375
+ # Legacy model/fallback fields are ignored; Jev routing owns model/tool choice.
318
376
  thinking: high
319
377
  profile: review
320
378
  max_turns: 20
@@ -343,12 +401,13 @@ file one level deep (relative to the agent file, same 64KB/symlink guards
343
401
  as `@contract.json`). Missing or rejected includes leave the line verbatim;
344
402
  includes do not recurse.
345
403
 
346
- Invoke with `{ task: "…", agent: "reviewer", model: "<provider/model-id>" }`.
347
- The agent file supplies persona/capability defaults only; its legacy
348
- `model`/`fallback_models` fields are ignored. An explicit `system_prompt`
349
- appends after the persona body.
350
- Profiles still enforce capability: an agent declaring `profile: review` with
351
- write tools fails closed. The agent catalog is advertised in the tool's
404
+ Invoke with `{ task: "…", agent: "reviewer" }`. The agent file supplies
405
+ persona, capability, thinking and budget defaults only: model and tool selection
406
+ stay with Jev routing, and a legacy `model`/`fallback_models` in frontmatter is
407
+ ignored. An explicit `system_prompt` appends after the persona body.
408
+ Profiles still enforce capability: `profile: review` filters candidates to read-only
409
+ tools. Legacy agent `tools` defaults are ignored; an explicit task `tools` list
410
+ requesting write tools under review fails closed. The agent catalog is advertised in the tool's
352
411
  system-prompt guidelines (session start) and in bare `status` output (live),
353
412
  and file changes are picked up within seconds — no restart needed.
354
413
 
@@ -356,15 +415,17 @@ and file changes are picked up within seconds — no restart needed.
356
415
 
357
416
  `taskDefaults` in `~/.pi/subagent.json` remains available for non-model
358
417
  fields such as thinking, budgets, and retry counts. Its legacy `model` and
359
- `fallbackModels` fields are ignored; model routing belongs only to
360
- `modelPolicy`. A profile `thinking` value overrides the selected route default.
361
- Invalid fields are dropped field-by-field.
418
+ `fallbackModels` fields are ignored; model and tool routing belong only to
419
+ `jevRouting`. A profile `thinking` value overrides the selected candidate's
420
+ optional `thinking` default. Invalid fields are dropped field-by-field.
362
421
 
363
422
  Notes on behavior:
364
423
 
365
- - `timeout_ms` covers queue time plus runtime, but timed-out tasks report
366
- `state: "timeout"` with `timeoutPhase: "queued"|"starting"|"running"` so
367
- agents can retry capacity issues without confusing them for task failures.
424
+ - `timeout_ms` is the absolute task deadline: local preflight, Jev selection,
425
+ setup, queue time and runtime all count against it, and selection cannot reset
426
+ it. Timed-out tasks report `state: "timeout"` with
427
+ `timeoutPhase: "queued"|"starting"|"running"` so agents can retry capacity
428
+ issues without confusing them for task failures.
368
429
  - Budget stops (`max_turns`, `max_cost`) trigger a **graceful wrap-up**: the
369
430
  child is steered to produce its final answer NOW and allowed `graceTurns`
370
431
  more turns before SIGTERM. Results end as `partial` with `wrappedUp: true`
@@ -374,11 +435,11 @@ Notes on behavior:
374
435
  then kills after `stallKillAfterMs` more silence — feeding automatic retry
375
436
  instead of burning the whole timeout.
376
437
  - **Transient failures retry automatically** (queue timeouts, stalls, spawn
377
- errors, provider errors) up to `maxRetries` extra attempts, escalating
378
- through `fallback_models` when provided. Usage accumulates across attempts;
379
- results record `attempts` and `attemptedModels`. Task-quality failures
380
- (nonzero exit with complete protocol, cancellations, budget stops, running
381
- timeouts) never retry.
438
+ errors, provider errors) up to `maxRetries` extra attempts on the already
439
+ selected model and tool set. There are no emergency or fallback models, and a
440
+ quality or budget failure never reselects. Usage accumulates across attempts;
441
+ results record `attempts`. Task-quality failures (nonzero exit with complete
442
+ protocol, cancellations, budget stops, running timeouts) never retry.
382
443
  - `context: "fork"` starts a single child from a real branched copy of the
383
444
  parent conversation (`--fork` on the parent's session file). It requires a
384
445
  persisted parent session, cannot combine with `resume`, and is rejected for
@@ -456,22 +517,23 @@ import {
456
517
  type TaskResult,
457
518
  type RunState,
458
519
  type UsageStats,
459
- } from "@parke.dev/pi-subagent/sdk";
520
+ } from "@cr1ms0n/pi-subagent/sdk";
460
521
  ```
461
522
 
462
523
  The package root is an alias for the same SDK:
463
524
  `import { runTasks } from "@cr1ms0n/pi-subagent"`.
464
525
 
465
- The Extension dispatch path requires `model` to be the exact value from the
466
- current `modelPolicy` route. This placeholder is illustrative only and does not
467
- write or select a real configuration:
526
+ The Extension dispatch path routes every new task through Jev, so callers never
527
+ pass a model to it. The low-level SDK is the opposite contract: it is
528
+ explicit-spec and performs no implicit routing, config discovery or network
529
+ call, so embedding code supplies the model (and tool list) it resolved itself.
530
+ This placeholder is illustrative only and configures nothing:
468
531
 
469
532
  ```ts
470
- const routeModel = "<exact model from current modelPolicy route>";
471
533
  const task: TaskSpec = {
472
534
  task: "Audit src/ for unsafe parsing",
473
535
  profile: "explore",
474
- model: routeModel,
536
+ model: "<provider/model-id resolved by your embedding code>",
475
537
  timeoutMs: 10 * 60_000,
476
538
  };
477
539
  ```
@@ -501,25 +563,34 @@ The Pi extension entry is unchanged: package `pi.extensions` still points at
501
563
  7. On parent session shutdown, live children are aborted and awaited briefly.
502
564
  8. On parent (re)start, orphan process groups recorded under `lockDir` are reaped
503
565
  before any resume is allowed for the matching child session.
504
- 9. Provider-reported usage is counted once per root message and terminal child run.
566
+ 9. Provider-reported usage is counted once per root message and terminal child run;
567
+ selector usage is a separate category counted once per selector request ID.
505
568
  10. Protocol completion prefers `agent_settled` (falls back to non-retrying `agent_end`).
569
+ 11. Jev selection precedes every new extension-managed launch, and the selected
570
+ tool subset is enforced by Pi's CLI allowlist; empty selection never means all tools.
506
571
 
507
572
  ## Layout
508
573
 
509
574
  ```
510
575
  src/
511
- index.ts # stable public SDK entry (@parke.dev/pi-subagent)
576
+ index.ts # stable public SDK entry (@cr1ms0n/pi-subagent)
512
577
  extension.ts # Pi wiring only
513
578
  schema.ts # request schemas (subagent + subagent_wait)
514
579
  btw.ts # /btw side questions (model-hidden entries)
515
580
  backend.ts # backend adapter seam + capability gate
516
581
  backends/ # pi | codex | claude adapters (invocation + parser)
517
582
  policy.ts # profiles, normalization, write guards, agent resolution
583
+ routing-types.ts # selector DTOs, decisions, receipts, local resource limits
584
+ routing-policy.ts# strict jevRouting parser, candidate list, routing guidance
585
+ jev-router.ts # injectable Jev transport, response validation, receipts
586
+ dispatch-preflight.ts # bounded read-only checks before selector work
587
+ startup-check.ts / child-preflight.ts # private child model/tool verification
588
+ dispatch-routing.ts # prepare -> select -> finalize for one or many tasks
518
589
  agents.ts # named agent files (.pi/agents/, .agents/agents/, global)
519
590
  launch.ts # resolve child pi via execPath / PI_SUBAGENT_BIN
520
591
  process-lock.ts # durable session locks, global slots, orphan records
521
592
  worktree.ts # git worktree isolation + diff/apply/discard
522
- orchestrator.ts # multi-task execution, transient retry + model fallback
593
+ orchestrator.ts # multi-task execution, worktree prep, bounded same-model retry
523
594
  runner.ts # child process lifecycle, RPC channel, steering,
524
595
  # graceful budget wrap-up, stall watchdog
525
596
  protocol.ts # Pi RPC/JSON event parser (agent_settled-aware)
@@ -534,14 +605,24 @@ src/
534
605
 
535
606
  ## Develop
536
607
 
608
+ This checkout has no `scripts`, no `devDependencies`, no `tsconfig.json` and no
609
+ test runner, so `npm install`, `npm run typecheck`, `npm test` and
610
+ `npm run pack:check` are not defined here. The checks that do run locally are
611
+ offline:
612
+
537
613
  ```bash
538
- npm install
539
- npm run typecheck
540
- npm test
541
- npm run pack:check
614
+ npm pack --dry-run --json
542
615
  ```
543
616
 
544
- Tests use a deterministic `fake-pi` child. No live model calls are required.
617
+ That verifies the publishable file list matches `package.json` `files` and leaks
618
+ no backup, transcript, session data, research output or generated bundle. Source
619
+ changes are parse/type-strip checked by running the global Pi install's bundled
620
+ esbuild over every `src/` and `extensions/` file; that catches malformed
621
+ TypeScript only, not type errors or peer API mismatches. Provider-free fixtures
622
+ and an injected fake selector transport, run with the already installed Pi runtime
623
+ rather than a repository test framework, exercise routing behavior without any
624
+ model call. See [.trellis/spec/backend/quality-guidelines.md](.trellis/spec/backend/quality-guidelines.md)
625
+ for the exact commands and what each check does and does not prove.
545
626
 
546
627
  ## Cost accounting
547
628
 
@@ -557,6 +638,15 @@ The extension footer stays terse (running/ready counts only). Delivery and
557
638
  replay do not double count runs. See
558
639
  [docs/COST-ACCOUNTING.md](./docs/COST-ACCOUNTING.md).
559
640
 
641
+ Jev selection is billed separately from execution. TypeSafe reports tokens, not
642
+ currency, so the ledger shows routing tokens as their own category, counts each
643
+ selector request once by its request ID (including plan and pre-spawn failures),
644
+ and marks routing cost as **unreported** rather than free. Numeric dollar totals
645
+ exclude unreported routing spend, and `max_cost` caps provider-reported execution
646
+ cost only; it does not cap TypeSafe charges. Route metadata (selected model,
647
+ selected tools, locally added controls, selector version, confidence, outcome,
648
+ latency) travels with the run alongside usage.
649
+
560
650
  ## Roadmap
561
651
 
562
652
  Planned work — agent spawn policies, dry-run validation, engine hardening,