A minimal Pareto-optimal OpenRouter model router for pi, based on Jev
Contents
I have been using pi as a coding agent with one model set as the default. That default applies the same trade-off to every task. A migration plan, a TypeScript bug, and a postmortem want different things from a model. My earlier test of reasoning levels showed how far intelligence, cost, and latency move when you change one setting.
pi-jev-router moves that choice to model routing per task. A classifier says what kind of work arrived, and local code picks the route. I then benchmarked it against a fixed model. Routing is sold as a way to cut cost, and in my account it did the opposite.
How Jev classifies a task before the model call
Jev is TypeSafe’s first System One model. It returns structured decisions instead of generated text. Choice picks one item from a fixed list. Score rates a state against an ordered rubric. Noul returns the probability of a yes-or-no answer. Choice and Score come back with probabilities and a confidence value.
Jev accepts text and structured JSON, a standard data format for machine-readable values. It does not write code or prose. That limit is what makes it useful before a generative call. The extension asks what kind of task arrived, and ordinary code decides what happens next.
Five routing questions in one request
At the start of each pi task, the extension builds a small envelope. It holds the prompt, bounded context, required tools, estimated context size, attempt number, and policy version.
Jev answers five questions in a single request. What is the task category? How hard is the reasoning? What does failure cost? Is the brief ready? Should the task split into subtasks?
Local code owns everything after that: model identifiers, budgets, context limits, and permissions. The first version of that policy was three lines.
| Task | First route |
|---|---|
| Planning | anthropic/claude-sonnet-5, high thinking |
| Code | openrouter/pareto-code |
| Writing | openai/gpt-5.4-mini, low thinking |
Those three names are what I benchmarked first. They are also what failed.
Writing tasks also receive Simplified Technical English instructions, which ask for short sentences, active voice, and one idea per sentence.
The extension switches models at task boundaries only. It never swaps a model during a tool call. Shadow mode, manual pins, session budgets, and context checks cover the cases where the classifier gets it wrong.
Why not just use OpenRouter’s Pareto Code Router
OpenRouter’s Pareto Code Router already solves part of this. You send openrouter/pareto-code and set min_coding_score between 0 and 1. OpenRouter maps that value to a low, medium, or high coding tier, picks the cheapest available model in the tier, and falls back within the same tier. Omit the parameter and the router defaults to the high tier.
Pareto answers which coding model should serve this request. pi-jev-router answers what kind of work arrived and which policy handles it. The first profile sent code tasks to Pareto and routed planning and writing elsewhere, because a coding score says nothing about architecture or prose.
Each tier is a percentile band on Artificial Analysis coding scores. That makes the score a moving position in a benchmark field, not the probability that a model passes the tests in your repository. That gap is why the package ships a measured profile as well.
Workers that hand back evidence
The main session gets a dispatch_task tool, so a planning task can hand off a subtask without a second supervising process. A worker takes a durable brief and runs in its own pi process.
In a Git repository the worker gets its own worktree, a second checkout of the same repository on a separate path. A verifier runs inside that worktree. A passing exit code merges the changes. A failing check removes them. The worker’s report is a claim, and the verifier supplies the evidence. SQLite, a file-based database, holds the task state.
What the paired cost benchmark showed
I ran five tasks: three code tasks, one architecture task, and one writing task. Each ran twice, once against fixed Sonnet and once through the router, in isolated workspaces with independent verifiers. All ten runs passed.
The code tasks averaged $0.093 on fixed Sonnet and $0.717 through Pareto Code in this account. Planning cost $0.164 fixed and $0.176 routed. Writing cost $0.113 fixed and $0.012 on GPT-5.4-mini. Across all five tasks the then-default router profile cost $2.34 against $0.56 for the baseline, and the code route accounts for almost all of that gap.
So the package briefly shipped two profiles that pinned models. pareto_code kept the Pareto route. empirical_cost used the frontier I measured for this account and this task set. Both are still there as controls. Neither is the default any more.
Which route lost the money
The average across five tasks hides three different results.
Writing won. The task dropped from $0.113 to $0.012 and from 32 seconds to 12 seconds, and the verifier passed both runs. Planning was a wash at $0.164 against $0.176, which is the closest thing here to a control, because both arms used the same model.
The three code tasks lost, and they lost on every axis. They cost six to nine times more, they ran slower, and they produced the same passing result.
The shape of those numbers is the part worth reading twice. On fixed Sonnet the three tasks cost $0.079, $0.089, and $0.109, so the bill tracks the size of the work. Through Pareto Code they cost $0.706, $0.706, and $0.740 while their elapsed times still varied. That flat line fits one expensive model answering all three requests rather than the cheapest model that clears a bar. If a request arrives without min_coding_score, the router defaults to the high tier. That is the first thing I will check.
Route the code tasks back to the fixed model, keep the measured writing route, and the same five tasks cost $0.45 against $0.56. That is about 18 percent cheaper and 10 percent faster. The profile is fitted on the runs that produced it, so read that figure as a description of this pilot rather than a forecast.
Picking one point on a frontier
I have a background in portfolio optimization from my bachelor thesis, so when the router’s job turned into a trade-off between quality, cost, and latency, my first instinct was the familiar one. I reached for convex optimization and started writing the problem in CVXPY.
Then I stopped.
Two things make the reflex wrong here. The choice is discrete: Markowitz allocates continuous weights across assets, while the router assigns one model per task, so there is no portfolio to solve for, only a small set of non-dominated models to rank. And the frontier is already computed. Once the catalogue is filtered against the task and dominated models are dropped, the set is small enough to enumerate. A solver adds a dependency and a failure mode and changes nothing.
So I compared the selection rules directly. That was the more interesting result. Six of the rules that can reach non-convex frontier points, weighted Chebyshev, augmented Chebyshev, the achievement scalarising function, L1, L2 and L4 compromise programming, and TOPSIS, returned the same model. The useful part of this frontier is convex. The machinery for the concave case had nothing to do.
The disagreements that remained were a normalisation artifact. Two weighted sums over the same frontier can differ by more than a hundred times in cost, only because min-max scaling compresses the cheap end of the axis and a log scale does not. The rule was not deciding. The units were.
That leaves the two lightest definitions.
- Epsilon-constraint. Minimise cost subject to a quality floor. One parameter, no normalisation, and it reads like an SLA because that is what it is.
- Knee point. No parameters. It takes the model farthest from the chord that joins the cheapest and dearest models on the frontier. On the representative task above, that is
deepseek-v4-flash-0731for code andglm-5.3for writing.
The package ships the knee point. A frontier with fewer than three models, or one with no spread, has no knee, so the selector falls back to a weighted value function. The explorer above is the same set of rules, so you can see where each one lands before you trust it.
The same five tasks, re-run
I rebuilt the selector around the frontier and ran the benchmark again. Three profiles, five tasks each, in isolated workspaces with independent verifiers and no shared state.
| Profile | Pass rate | Total cost | Cost per success |
|---|---|---|---|
| Fixed Sonnet 5 | 100% (5/5) | $0.57 | $0.11 |
| Pinned role router | 100% (5/5) | $1.57 | $0.31 |
| Frontier and knee | 100% (5/5) | $0.20 | $0.04 |
The knee router passed every task at 65 percent below the fixed baseline and 87 percent below the pinned router I had shipped. The three code tasks went to z-ai/glm-5.3-flash for about $0.0035 each, and all three passed. The planning task had too small a frontier for a knee, so it fell back to the weighted rule, picked Sonnet 5, and cost $0.176, which is most of the run. That is the fallback working, and it is also the next thing to fix.
The original result was not wrong. It described the policy I wrote, not routing. Three hardcoded routes cost four times the baseline. The same classifier, pointed at a frontier instead of three model names, came in 65 percent under it.
What the pilot actually proved
Five tasks cannot locate a general cost frontier for model routing. Neither can ten. The first run showed which part of the system failed, and it was not the classifier. Every task reached the route its category implies, and all ten runs passed. One line of policy cost four times the baseline.
The second run shows the fix. A selector that reads the catalogue, filters it against the task, and takes the knee point passed the same five tasks for a third of the baseline cost. The classifier did not change. The model names left the code and moved into the frontier.
Read both runs as five tasks on one account, not as a general result. The full source is available on GitHub.