Most agentic coding setups run one model for the whole job: it decomposes the task, writes the code, and then wades through thousand-line test logs looking for the one line that matters. That wastes a good model twice over — you're paying frontier-model rates for work a much cheaper model would do just as well, and you're diluting the model's context with noise while you're at it.
My method is simple to state: treat a coding turn as a sequence of phases, and give each phase the model best suited to it. I've packaged it as pi-tiered-router, an extension for the
A coding turn breaks into phases:
classify → plan → validate → execute
(tool output compressed as it streams in)
Each phase binds to a role, and each role binds to a model plus a thinking level — all configurable:
| Role | Default (model, thinking) | Job |
|---|---|---|
| Planner | ||
| Opus, high | Decompose the goal into a numbered plan | |
| Validator | ||
| Fable, medium | Independently critique the plan before any code is written | |
| Executor | ||
| Sonnet, medium | Do the actual work | |
| Tool parser | ||
| Haiku, off | Classify the task up front, and compress noisy tool output before it hits the executor's context |
Note that the cheapest role does double duty: it runs the up-front complexity classification and the tool-output compression. Both are high-volume, low-difficulty jobs — exactly what you don't want a frontier model spending context on.
Planning and validation happen out of band — they never touch the executor's conversation. Only the final, validated plan gets injected as context. The model doing the actual coding sees a clean, distilled plan instead of the whole reasoning trace that produced it.
A minimal config is just the roles you want to change; everything else falls back to defaults:
{
"roles": {
"planner": { "model": "anthropic/claude-opus-*", "thinking": "high" },
"validator": { "model": "anthropic/claude-fable-*", "thinking": "medium" },
"executor": { "model": "anthropic/claude-sonnet-*", "thinking": "medium" },
"toolParser":{ "model": "anthropic/claude-haiku-*", "thinking": "off" }
}
}
Model specs use wildcards (claude-opus-*
) so the config survives point releases, and any role can point at any provider — OpenAI, Google, a local Ollama model, whatever your setup has.
The name comes from what the classification is for. Before anything runs, the cheapest role rates the request into a tier — trivial, simple, standard, or complex — and the tier scales the whole chain:
This is the part I want to be clear about, because it's an easy thing to get wrong. There are no spend caps and no silent downgrades. Every phase always gets the model and effort level that produces the best result for that phase.
The efficiency is a side effect of routing well, not the goal:
If you want it cheaper, you configure cheaper models per role (or lighter tier chains) — the router will never make that tradeoff for you behind your back.
If you've read Anthropic's writing on multi-agent systems, the obvious question is: how is this different from the orchestrator/subagent pattern — a lead agent that dynamically decomposes a task, spawns worker agents, and synthesizes their results?
The honest answer is that they optimize different things, and being clear about the tradeoffs is more useful than pretending my method wins on every axis.
The load-bearing difference: I decompose by phase, decided by code; the orchestrator decomposes by subtask, decided by a reasoning model. My pipeline is fixed, and its cleverness is binding each phase to a model — deterministic, given a cheap up-front classification. The orchestrator instead has a reasoning model look at this specific task, decide how to split it, spawn workers in parallel, read what comes back, and re-plan.
The way I'd put it: I built a well-engineered assembly line; the orchestrator is a research team. An assembly line is faster and more reliable when the product is known — and the wrong tool when the job is "go figure out what we should even build."
It isn't strictly either/or: the package includes an opt-in dispatch_step
tool that farms parallel steps out to isolated subprocesses, so the executor can borrow a page from the orchestrator playbook when a step genuinely benefits from it.
pi install npm:pi-tiered-router
Or browse it on the pi package gallery: pi.dev/packages/pi-tiered-router
It ships with four modes (plan / agent / ask / debug) and a first-run setup wizard. It's v0.1.0 — I'd genuinely like to hear how it holds up on real work, especially where the phase-based method breaks down and you'd want something more orchestrator-shaped.