Per-turn cascade model router for AI agents. Starts cheap, climbs the tiers when the cheap one fumbles.
Don't want to wire it yourself? Switchback ships fully hosted inside VibeKit — every "Auto" turn runs through this cascade with brand-locked ladders, telemetry, and billing built in. Or hit the hosted classifier directly:
POST https://vibekit.bot/api/v1/switchback/route.
Most AI agents pick one model per app and use it for every turn. A "fix this typo" turn and a "refactor the auth flow" turn both go to the same flagship — wasted spend on the trivial half, no escalation safety on the complex half.
Switchback classifies each user turn (trivial / standard / complex), starts the agent on the cheapest model in your ladder that can plausibly handle it, then escalates mid-turn — swapping to a more capable model on the next agent round — if it detects the cheap one is fumbling.
Born inside VibeKit. Extracted as a standalone library because the routing pattern is useful well beyond one product.
npm install vibekit-switchbackimport {
createCascade,
createOpenRouterClassifier,
} from 'vibekit-switchback';
const cascade = createCascade({
// Cheap → flagship. Brand-locked is your call: if your user has an
// Anthropic key, hand Switchback an all-Anthropic ladder; if they're
// on OpenAI, an all-OpenAI ladder. Switchback stays inside whatever
// ladder you give it.
ladder: [
'openai/gpt-5.4-mini',
'openai/gpt-5.4',
'openai/gpt-5.5',
],
classify: createOpenRouterClassifier({
apiKey: process.env.OPENROUTER_API_KEY!,
}),
});
const state = await cascade.init({
userMessage: 'add a dark-mode toggle to the header',
recentHistory: [], // optional: last 1-2 assistant turns for context
});
// state.initialModel === 'openai/gpt-5.4' (classifier picked tier 1)
// state.classifier.why === 'small UI feature'
// Run your agent's first round with state.initialModel:
const round0 = await runAgentRound({ model: state.initialModel, ... });
cascade.recordTokens(state, round0.totalTokens);
// After each round, check for escalation:
const decision = cascade.evaluate(state, {
hasNoText: round0.text.trim() === '',
endedOnToolUse: round0.endedOnToolUse,
steps: round0.steps, // optional — enables tool_error_spike trigger
joinedText: round0.text, // optional — enables refusal_signal trigger
}, /* round = */ 0);
if (decision.escalate) {
const nextModel = cascade.escalate(state, decision.reason!, 0, round0.totalTokens);
// ...use nextModel for the next round
}
// At end-of-turn, persist telemetry:
const telemetry = cascade.telemetry(state);
// { initial_model, final_model, escalations, signals[], per_tier_tokens }- Mid-turn escalation. Other routers (NotDiamond, Martian) pick a model at request start and that's that. Switchback can hand off mid-conversation if the cheap tier gives up — requires deep agent-loop integration but catches failures other routers can't.
- Brand-locked by design. Switchback doesn't decide "use Claude or GPT" — the caller hands it one ladder per user, all within their BYOK provider. Your user's key stays your user's key, every tier.
- Agent-loop signals. Five escalation triggers built around actual agent execution: empty-tool exits, tool-error spikes, continuation exhaustion, explicit retry-sentinels, refusal phrases. Not just chat-completion patterns.
- Telemetry baked in. Every
cascade.telemetry(state)returns a clean JSON record ready to write to yourusage_logs/ metrics sink. Five columns: initial model, final model, escalation count, signal log, per-tier tokens.
| field | type | description |
|---|---|---|
ladder |
readonly string[] |
Ordered list of model ids, cheap first. |
classify |
(userMessage, recentHistory) => Promise<ClassifierResult> |
Your classifier function. See below. |
Returns: { init, evaluate, escalate, recordTokens, telemetry }.
Drop-in classifier using OpenRouter. Fail-soft (returns tier 1 on any error so the cascade never blocks on classifier issues).
| option | type | default | description |
|---|---|---|---|
apiKey |
string |
(required) | OpenRouter API key. |
model |
string |
openai/gpt-5.4-mini |
Model slug for classification. |
baseUrl |
string |
https://openrouter.ai/api/v1 |
Override for OR-compatible proxies. |
timeoutMs |
number |
8000 |
Per-call timeout. |
| trigger | fires when |
|---|---|
empty_tool_exit |
2 consecutive rounds end on tool_use with no text output. |
tool_error_spike |
≥3 tool-error entries in this round's steps[]. |
continuation_exhaust |
Round index ≥5 with still no text accumulated. |
sentinel_after_retry |
Your agent loop hit its empty-response retry and the retry was also empty. |
refusal_signal |
First ~200 chars of the assistant reply start with "I can't" etc. |
Each escalation bumps tier by 1 (capped at ladder top).
- You only have one model. Save yourself the classifier latency. Switchback is for cascades.
- You don't run an agent loop. The mid-turn escalation only helps if you have multiple rounds per user turn. For one-shot chat-completion routing, you want NotDiamond / Martian / OpenRouter's
autoinstead. - Latency-critical chat. The classifier adds ~200-500ms before the first token of round 0. If your UX needs sub-200ms first-token-latency, skip it.
MIT — see LICENSE.
Built by VibeKit. The approach is heavily inspired by Factory.ai's Factory Router — same thesis, different implementation. Switchback's contributions are the brand-locked ladders, mid-turn escalation, and agent-loop-signal triggers.