Skip to content

Escalation

Choosing a cheaper model is only a saving if its answers hold up. When one does not, the call is escalated to a more capable model, and the cost of that second call is charged against the cheap model whose failure bought it.

When an answer fails its check: the agent’s own check on its output (a schema, a citation, a grounding check, a rule), decided by the agent’s code. The router does not judge answers itself. The agent asks the gateway for the next step:

step = gateway.escalated(decision, env, failed_model=model, spent_usd=result.cost_usd)
if step is not None and step.available:
result = await gateway.acall(prompt, env=env, model=step.model, system=system)

Escalating automatically on a failed check, for agents in the library: Coming

  • Up the capability ladder. The next model is the most capable one this call was allowed to use: not excluded, within your allow list and residency rule, available, and not dropped for a hard reason. Where your policy names an escalation target for the task class (escalate_to), that model is used instead.
  • At most a set number of steps: one by default (escalate in your policy). Set it to zero to turn escalation off. When the steps are used up, or nothing is above, the escalation says so (escalation_exhausted, nothing_above) and nothing more is called.
  • Through the gateway, like any call: the second call is checked against your caps and kill switch, priced, recorded and screened in the same way.

The escalation’s cost is charged to the cheap model (charge_to). The decision is recorded again with escalation_usd, so:

  • the routing lever takes the escalation off the saving that choice bought: a cheap model that needed a dear retry did not save what the first decision estimated;
  • the next time the benchmark and the routing figures look at that model, its cost per successful task includes the clean-up its failures caused.

GET /api/v1/routing reports escalated (how many decisions were escalated) and escalation_usd (what the escalations cost) in its totals and in every breakdown. saving_usd is already net of it.

The router anticipates escalation when it chooses. Each candidate is ranked on its expected cost:

expected = direct + (1 − quality) × the direct cost of the next model up

A worked example of the arithmetic, on the card of A decision, worked: Haiku 4.5’s direct cost for the lookup question is $0.011451, and the next model up, Sonnet 4.5, would cost $0.034601. At quality 1.00, Haiku’s expected cost is its direct cost. If Haiku’s measured quality on the class were 0.90, its expected cost would be $0.011451 + 0.10 × $0.034601 = $0.014911: still well below Sonnet’s, so still chosen. A model has to fail often, or be close in price, for the escalation term to change the order.

Note the two “next models” differ. The expected-cost term uses the direct cost of the next model up by capability; an actual escalation goes to the most capable model the call may use, unless escalate_to names one. With one step allowed, the actual escalation can therefore cost more than the term assumed: on the card above, Haiku would escalate to Opus 4.6 ($0.057613), not Sonnet. Set escalate_to for a class to the next model up to make the two the same.