Pay for difficulty.
Not for every message.
pi-tier-router is a virtual model for Pi that routes each session between a cheap flash model and a strong pro model — the tiering idea behind Claude Code's haiku/sonnet/opus levels, but config-driven, so it works with MiMo, DeepSeek or any OpenAI-compatible endpoint. The first message of a session is rated once: routine work stays on flash, genuinely hard issues escalate. Jev is an optional fallback, never a dependency.
Why
Cheap by default.
Strong on demand.
Every design choice below exists so the strong model is only reached when the issue actually justifies it — and so nothing you own gets rewritten to make that happen.
Local first
Stack traces, race conditions, 竞态条件, greetings, typos — decided in 0 ms with zero tokens, in English or Chinese. Only prompts the local layer can't read cleanly ever reach a model, and its scan is bounded so a huge paste stays sub-millisecond.
Flash is the default
The flash model must be confident (≥ 0.8) before anything escalates. Routine questions, small fixes and standard debugging never leave the cheap tier.
One prompt, one decision
Decided when the prompt arrives and reused for its whole agent run — tool loop included — with no repeated rating calls. The next prompt decides again.
Jev is optional
Consulted only when the flash model is unsure, and only if a
credentialed classifier model is configured (TYPESAFE_API_KEY). In normal
operation it is called zero times — it is never a dependency.
Any model family
The two roles are plain config: flash-model and pro-model as
provider/model-id. They may even live on different providers.
Pi's files stay Pi's
models.json is never touched — availability comes from Pi's live
registry. The only setting read is your default model id, used to warn when it changes.
It tells you when it breaks
Configured pair missing → warning and the router disables itself. Default changed → info. A stock install stays completely silent.
How it works
Decide per prompt.
Locally first.
Read the prompt locally
When the prompt arrives, a feature router reads it: 0 ms, zero tokens, deterministic. Clear signals decide on the spot; mixed or missing signals abstain and hand over to the chain below.
Reuse what the store knows
Abstained prompts hit the decision store first: past chain verdicts for similar prompts — matched by signal-profile overlap or trigram sketch, settled by a k-NN vote — are reused with zero model calls. It lives next to the extension and is removed with it.
Rate only what local declined
An abstained prompt goes to the flash model, which returns a rating plus a confidence as JSON. The call is bounded at 10 s with minimal reasoning, so it never holds up a turn.
Apply the bar
Confidence ≥ 0.8 is final. Unsure → ask Jev if a classifier is configured. Neither can answer → keep the current model and stop routing; nothing is guessed.
Serve the whole run
The decision covers every request of that agent run — tool follow-ups included — and the next prompt decides again. Compaction summaries run on flash.
Configure
Two keys, or a wizard.
The extension owns its config. Nothing here is written into Pi's settings schema, and the file disappears with the extension.
| setting | meaning |
|---|---|
| flash-model | cheap default — also the model that rates each issue |
| pro-model | strong escalation target; may sit on a different provider |
| format | provider/model-id, split on the first slash, so ids containing slashes work |
| defaults | omitted or malformed keys fall back to a MiMo pair |
Where it lives
Next to the extension file —
<install-dir>/extensions/auto-router.json — read lazily while the
extension is loaded and ignored entirely when it is not. Because it travels with the
code, uninstalling takes it with it, and pi update keeps it
(verified for npm and git sources). An older copy in
~/.pi/agent/extensions/ is moved next to the extension the first time it
loads, so uninstalling leaves nothing behind.
/auto-router status prints the resolved pair, the
config path and your current default. /auto-router memory shows the decision
store — path, size, reuse — and /auto-router memory reset wipes it; it
rebuilds itself from new verdicts.
When your model settings change
It warns. Or it disables itself.
Re-checked at session start and before every prompt, so an edited config or a rewritten provider takes effect without restarting Pi.
| condition | what happens |
|---|---|
| default is router/auto, pair gone | error notification, then degraded routing instead of a crash |
| configured pair missing | warning — provider rewritten or pro model removed; the router stays disabled until the config matches |
| default changed mid-run | info — pick router/auto in /model to route this session |
| no config, no matching pair | inactive — Pi behaves exactly as if the extension were not installed |
| default is a physical pro model | routing never engages: route() only runs for requests made with router/auto |
Watching a decision
Set AUTO_ROUTER_DEBUG=1 to trace which source decided, and how long the rating
took. The interactive footer shows the routed model, and /session reports cost
per physical model.
Install
Two ways in.
Pi loads TypeScript directly — there is no build step.
From npm
published package
From GitHub
straight from the repository
Then select it
and save it as your default
| requirement | purpose |
|---|---|
| both role models | registered in models.json with working credentials — if either is missing the router stays out of the way |
| TYPESAFE_API_KEY | optional — Pi's classifier credential for the Jev step (typesafe/jev-latest); with no credentialed classifier, the flash model's lean is kept |
| AUTO_ROUTER_DEBUG | set to 1 to trace each routing decision |
Install one or the other
Loading both the npm package and a file copy silently breaks the wizard: /auto-router
stops dispatching and the text goes to the model instead.
Source, tests and the full README live at
github.com/rioliu/pi-tier-router
· MIT licensed.