Kestrel scores each step before it runs and sends it to the smallest model that can do it. Renames go to a small model; only the hard reasoning reaches a large one.
Fig. 1Model routing
Steps flow into the router. Most go to the small model, some to the medium one, and the rare hard step goes to the large one.
Before each step, Kestrel scores how hard it is from 0 to 1, from what the step asks for and how earlier steps like it went. Then it picks the model for that score.
kestrel.yaml
routing: small: score < 0.4 # 72% of steps medium: score < 0.8 # 21% large: score >= 0.8 # 7%, the hard ones
Most of a run is reading files, renaming things and running tests. Those steps score low, which is why routing cuts the token part of a run's cost by about two thirds.
await kestrel.runs.create({ repo: "acme/web-app", task: "Untangle the billing state machine", routing: { floor: "medium" }, // never use the small model on this run});
kestrel.runs.create( repo="acme/web-app", task="Untangle the billing state machine", routing={"floor": "medium"}, # never use the small model on this run)
Note
Every step in a trace shows its score and the model it went to, so you can see where the tokens went.