MIT · Pi extension · flash ↔ pro tiering

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.

$ pi install npm:pi-tier-router
flash pro router/auto

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.

00

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.

$ pi "kill the race in the queue" local pro conf=0.93 via concurrency
01

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.

[auto-router] memory flash (3 neighbours, 100% agree) ring: 2 000 entries · ~285 KB · never grows with age
02

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.

{"rating":"flash","confidence":0.95} self rated=flash conf=0.95
03

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.

conf ≥ 0.8 → decide unsure → Jev (optional) no answer → keep current model
04

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.

$ /session flash 8.2k tok $0.00 pro 41k tok $0.12 cost is reported per physical model

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.

settingmeaning
flash-modelcheap default — also the model that rates each issue
pro-modelstrong escalation target; may sit on a different provider
formatprovider/model-id, split on the first slash, so ids containing slashes work
defaultsomitted 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
$ /auto-router set flash=deepseek/deepseek-v4-flash pro=deepseek/deepseek-v4-pro
$ /auto-router memory

/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.

conditionwhat happens
default is router/auto, pair goneerror notification, then degraded routing instead of a crash
configured pair missingwarning — provider rewritten or pro model removed; the router stays disabled until the config matches
default changed mid-runinfo — pick router/auto in /model to route this session
no config, no matching pairinactive — Pi behaves exactly as if the extension were not installed
default is a physical pro modelrouting 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

$ pi install npm:pi-tier-router

From GitHub

straight from the repository

$ pi install git:github.com/rioliu/pi-tier-router

Then select it

and save it as your default

$ /model → router/auto, then Ctrl+S
requirementpurpose
both role modelsregistered in models.json with working credentials — if either is missing the router stays out of the way
TYPESAFE_API_KEYoptional — Pi's classifier credential for the Jev step (typesafe/jev-latest); with no credentialed classifier, the flash model's lean is kept
AUTO_ROUTER_DEBUGset 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.