Docs / Routing & escalation
Routing & escalation
The ladder decides where a request can go; the gate decides whether it stays. This page covers the ladder, the learned start-rung bandit, speculative escalation, cross-provider failover, budget caps, and the modes that tune all of it.
The ladder#
provider/model rungs, cheapest first. A request opens at the start rung and climbs exactly one rung per gate failure. No gate failure = no escalation. A budget or rung-cap hit = serve the best attempt on hand.The escalation rule
A ladder is an ordered list of rungs, cheapest first. Each rung is provider/model. A request opens on the start rung and climbs one rung per gate failure until something passes or a cap is hit.
Configuring the ladder
[ladder]
rungs = [
"anthropic/claude-haiku-4-5", # rung 0
"openai/gpt-mini", # rung 1
"anthropic/claude-sonnet-5", # rung 2
]
max_rungs = 3 # hard cap on the climbmax_rungs too high "just in case." Every escalation costs real tokens on the higher rung. If the quality bar is set by the gate (it should be), a longer ladder only adds spend on requests that would have passed earlier — it does not improve quality above the gate's threshold.Learned start-rung#
Always starting at rung 0 is wasteful when a class of traffic reliably escalates. An optional bandit learns a better start rung — where to begin the cascade. The gate still decides what to serve, so a mis-predicted start only changes the entry point, never correctness. This is the "predict-to-start, verify-to-serve" split.
The two algorithms
Two algorithms are available (ADR 0007). The default is UCB1, and the bandit is default-off.
| Algorithm | Character | When |
|---|---|---|
ucb1 (default) | Deterministic, fully auditable — the same history yields the same choice. | You need every routing decision to be replayable from the receipt. |
thompson | Beta posteriors with discounting for drift; Monte-Carlo propensities for clean off-policy evaluation. | Traffic shifts over time and you want the sampler to track it, with propensities OPE can trust. |
[bandit]
enabled = false # default-off
algo = "ucb1" # or "thompson"Speculative escalation#
When an escalation is likely, waiting for the gate to fail before starting the next rung pays serial latency. Speculation prefetches k rungs ahead in parallel, serves the first that passes, and discards the rest. The served bytes are byte-identical to the serial path — speculation changes latency, never output.
The speculation band
To avoid speculating on requests that will obviously pass or obviously fail, a speculation_band fires the prefetch only in the marginal gate-pass zone:
[speculation]
enabled = true
lookahead = 1 # prefetch 1 rung ahead
speculation_band = [0.35, 0.65] # only speculate when the pass-signal is marginalspeculation_band = [0, 1] to always speculate. This fires parallel rungs on every request — including obvious cheap passes — burning higher-rung compute as wasted cost. The band is a guard: only speculate when the pass signal is genuinely marginal.Failover & budgets#
Escalation doubles as resilience. A transport error or a 5xx from a provider triggers cross-provider failover — the next rung may be a different vendor, so one provider's outage degrades gracefully instead of failing the request.
Budget caps
Spend is bounded at three levels, plus the rung cap:
[budget]
per_request_usd = 0.05
per_session_usd = 1.00
per_day_usd = 250.00per_request_usd even when the rung's output is not served: verifying a bad answer still costs tokens.Modes#
A mode is a named preset over the escalation knobs — a single dial that shifts the cost/quality/latency balance without editing config. Set it per task, per turn, or per request.
What each mode changes
| Mode | What it does |
|---|---|
observe | Shadow only — route and gate, record everything, serve the original path. Zero behavior change. |
cost | Bias hard to the cheap tier; escalate reluctantly. |
balanced (default) | The no-op baseline — escalate on proof of need. |
quality | Add one rung of headroom (+1 rung) for harder work. |
latency | Turn speculation on — prefetch ahead, serve the first pass. |
max | Start at the top rung when nothing less will do. |
Inspect the resolved presets with firstpass modes. The mode in force is recorded on every receipt.
latency enables speculation (lookahead = 1 in the marginal band). quality widens the cascade headroom by one rung. max starts at the top rung and skips cheaper options entirely. observe runs the gate and records the receipt but serves the original provider response byte-for-byte — a zero-risk shadow. The resolved knobs for any mode are visible in firstpass modes.max mode as a blanket default. max starts at the strongest rung, paying the 15–75× tier premium on every request — including the easy ones a cheaper model would ace. balanced with a well-tuned gate handles the vast majority of traffic at a fraction of the cost; reserve max for the rare task that genuinely requires it.Mode precedence#
x-firstpass-mode header beats everything. The built-in fallback is balanced. The mode in force is written to every receipt.When more than one source names a mode, the most specific wins:
x-firstpass-mode header > per-route routing_mode > FIRSTPASS_MODE_PROFILE env > balanced.# override the mode for a single request
curl localhost:8080/v1/messages \
-H "x-firstpass-mode: quality" \
-d @request.json