# Pi assessment and routing (explicit opt-in)

Load `src/index.ts` from the repository root with `pi --extension ./src/index.ts`. **Automatic routing is off by default.** The adapter prefers Pi's OpenRouter credentials (`/login openrouter`), then `OPENROUTER_API_KEY`, then `TYPESAFE_API_KEY`. OpenRouter sends to Jev 1.13 at `https://openrouter.ai/api/v1/systemone`; direct TypeSafe uses `jev-latest`. Jev is separate from Pi's coding model. No key is stored in router config and the recipient never changes silently after consent.

## Commands

- `/model-router-assess once`: send the next prompt's prepared context to Jev and display five judgments; never switch models.
- `/model-router-route once`: assess and potentially switch the Pi model/thinking level for the next prompt. The model remains active afterward; *permission to reassess* is one-shot.
- `/model-router-route once base=<ref>`: same one-shot consent, plus a **local** Git diff preflight against the explicitly supplied base. Run this before a branch-review prompt, e.g. `/model-router-route once base=main` followed by `/skill:review-branch main`. No skill name is recognized or hard-coded. This one-shot command replaces auto consent. Requires a trusted project, Git repository root as Pi's working directory, and a configured `policy.substantialReview`. No base is guessed; ordinary auto routing does not run a Git preflight.
- `/model-router-auto on`: in a **trusted project with an interactive TUI or RPC client**, check credentials and routing config, then ask for an explicit confirmation describing future unredacted transmissions and the named recipient. Only if confirmed, route **each subsequent prompt in this session** without asking again. RPC clients must answer the Pi `confirm` request; it times out after 30 seconds. Print/JSON modes cannot enable automatic routing.
- `/model-router-auto off`: revoke automatic and any pending one-shot permission. `/model-router-assess off` and `/model-router-route off` do the same. Enabling either one-shot command *replaces* automatic consent rather than pausing it; enabling automatic routing replaces one-shot consent. Invalid commands do not grant permission.

Automatic consent is **in memory only**: it is not a config flag or a session entry. It remains active across `/tree` navigation **within the same session**, so unredacted text from whichever branch is active on a later prompt may be sent to the locked recipient. Navigation clears any pending one-shot permission, diagnostics, and in-flight assessment; a navigation during an in-flight Pi model switch instead revokes auto consent because the switch cannot be rolled back safely. Auto consent is cleared on session start/shutdown, extension reload, explicit revocation, lost trust, changed recipient, manual model/thinking-level selection, a routing timeout, or a failed/partial Pi model switch. An unexpected route/assessment failure before switching leaves auto enabled and retries on the next prompt; this can repeat calls and transmission of future prompt context under the original consent. Manual selections take precedence; use `/model-router-auto on` and confirm again to resume after revocation. The compact `Router auto: <recipient>` status remains visible while it is enabled, separately from the assessment status. A subsequent prompt re-resolves credentials before reading context; if the recipient has changed, routing stops and consent must be renewed. Explicit `off` aborts an in-flight Jev assessment when possible and ignores late results; a request already transmitted cannot be unsent, and a Pi model switch already started may complete. If a switch times out, is revoked or manually overridden mid-flight, or only partially succeeds, inspect Pi's active model and thinking level; the router cannot roll back an already-started Pi model change. Pi's thinking-level event has no origin field and may arrive after a router switch finishes; the router tracks its expected level transitions so those delayed self-events do not revoke consent. A concurrent manual thinking change during an in-flight router switch cannot always be distinguished from Pi's own model-switch level adjustment.

The `before_agent_start` pipeline is `mapContext()` → `prepareContext()` → `assessTask()` → `selectModel()` → `setModel()`/`setThinkingLevel()` (routing also loads configuration and checks runtime candidates **before** sending to Jev). With an explicit base, a bounded Git preflight runs before context preparation or Jev transmission: it counts committed, staged, unstaged, and untracked changes relative to the merge base. It does not transmit Git paths, diff content, or files to Jev. Tracked binary/submodule changes and untracked binary, symlink, or oversized files produce incomplete line counts rather than blocking assessment. Known line counts are a lower bound; when Jev identifies a review, incomplete counts conservatively apply the configured substantial-review allowlist, with no fallback outside that tier. Nonignored untracked files remain included. Local branch names work without an `origin/` prefix. Invalid/unavailable refs, no shared ancestor, no changes, excessive file counts, malformed metadata, filesystem errors/races, and Git failures still stop preflight: **no assessment is sent** and the model stays unchanged. Fixed diagnostic messages explain the reason without exposing paths, refs, file contents, or raw Git errors. Bounds remain 200 files, 512,000 bytes per untracked text file (with a bounded extra-byte growth check), 2,000,000 bytes of Git output per command, and 4 seconds per Git command. Symlink targets and oversized files are not read. Aggregate file/line/directory counts affect only local model selection and are not persisted. One-shot permission is consumed before prompt-bearing work; auto permission remains only while its session-scoped conditions hold. Stale results after session/branch changes, revocation, newer requests, or an aborted turn cannot switch models. No router results or raw input are injected into the model transcript or stored as extension entries; Pi records its own model-change entry when switching.

Successful assessments are shown outside the model transcript using one shared format: `Router: <category> R<score> S<score> I<score> M<percent>% W<score>`. In the TUI, this appears in a dedicated widget above the editor (independent of the footer); in RPC, through the extension status channel; in JSON/print modes, as a line on stderr so stdout remains machine-readable. R = reasoning, S = dependency scope, I = context integration (0–2); M = probability of missing critical evidence; W = normalized policy-weighted demand (0–1). Assessment-only shows `W–` without a readable policy. The TUI/RPC display clears on a new operation and session/tree changes; it never includes the chosen model. Errors, skipped routes, and threshold-unmet fallbacks appear as sanitized notifications where UI is available.

## Safety and limits

**Privacy:** consent is **not redaction**. Current prompts and selected historical user/assistant text can contain secrets and are sent unredacted through the confirmed recipient (and potentially onward to TypeSafe as the model provider); image counts and omission notices may be sent, but not image bytes, tool calls/results, system prompts, extension messages, or assistant thinking. Auto mode deliberately grants broader consent for *future* prompts that cannot be reviewed in advance. Do not enable it for sessions where future text might be sensitive. It makes no claim to detect secrets. Jev instructions distinguish conversation evidence from instructions, but prompt-injection resistance is not guaranteed.

Routing requires both `policy` and nonempty `options` in the global `~/.pi/agent/model-router.json` (or `PI_CODING_AGENT_DIR`) and/or trusted project `.pi/model-router.json`; see [configuration](configuration.md). Invalid/unreadable config, no eligible models, unavailable capacity estimates, an oversized request, assessment failure, an `unclear` category, or high missing-evidence probability leave the current model unchanged. When Pi usage is unknown (notably just after compaction), the router estimates capacity **locally** from the active session projection, current prompt/system prompt and selected tool declarations; discarded history and abandoned branches are excluded. No additional context is sent to Jev. The fallback counts one estimated token per UTF-8 byte, 256 tokens per message and 8,192 per image, multiplies input by 1.25, then adds 8,192 output-reserve and 8,192 hidden-overhead tokens. It excludes binary image data and usage/UI metadata. Bounds are 10,000 projected messages, 2,000,000 measured bytes and JSON nesting depth 32. Missing APIs/declarations, malformed or unsupported content, bound exhaustion and invalid numeric usage fail closed with a sanitized unavailable-capacity warning. A usable estimate gets one info notice per attempt; no fitting models get the usual eligibility warning. Valid Pi usage takes precedence and retains the existing capacity budget. Eligibility is checked again immediately before switching; a selected model that no longer qualifies is not switched. Ordinary capacity skips preserve auto consent, while once permission remains consumed. This generous heuristic may exclude smaller models unnecessarily. Later extension handlers, pending messages invisible to the projection, request-time transforms, image normalization and provider serialization can still change the final payload: headroom is not a request-size guarantee. Loading the router after context-mutating extensions can help but cannot cover later request-time transformations. Each `before_agent_start` routing attempt has a 15-second deadline, including credential lookup, candidate filtering, Jev assessment (up to three total HTTP attempts for 5xx responses, with 100 ms between attempts), and model switching; the deadline can interrupt the retry delay or any request and prevents later attempts. Retried Jev calls resend the same prompt context and may incur additional charges. A routing deadline aborts in-flight judgment, continues the prompt without waiting for uncooperative host operations, and revokes auto consent. A model switch already started may still complete after timeout. A threshold-unmet fallback selects the highest-scoring compatible option with a warning. Config/credential failures in auto mode revoke consent; ordinary per-prompt skips do not. Unexpected routing/assessment errors before switching warn and leave auto enabled for the next prompt; a failed or partial model switch revokes it. If an error occurs after switching, inspect Pi's active model.

**Quality:** an explicitly measured review meeting any configured substantial-review threshold is restricted to the policy's allowed option IDs; cheaper models outside that tier are excluded even if their coding benchmark score qualifies. If none of those options is runtime-eligible and category-compatible, the router leaves the model unchanged, rather than falling back to a weaker option. Small reviews and reviews without an explicit base follow the existing policy. Thresholds and the tier are provisional. Weights, DeepSWE mappings, and costs remain uncalibrated; DeepSWE primarily measures coding, not explanation/design/research. The context capacity estimate is conservative, not a provider guarantee. The [offline evals](../evals/README.md) are synthetic regression tests, not real-world validation. Keep auto off in workflows where inaccurate routing would be consequential until empirical calibration and privacy controls are stronger.
