Cascade
Generate with cheaper tiers first and escalate when evaluators reject the result.
cascade() runs tiers in order. Each tier may inspect the completed result and accept it or escalate to the next tier.
import { cascade, fallback } from "@use-crux/core/routing";
export const answerCascade = cascade({
id: "support-answer-cascade",
prompt: supportPrompt,
tiers: [
{
model: fallback([haiku, gpt5nano], {
on: ["rate_limit", "server_error"],
}),
escalateOn: ["invalid_response"],
evaluate: async ({
result,
input,
cost,
totalCost,
tierIndex,
report,
}) => {
const score = await report(
helpfulness.score({ input: input.question, output: result.text }),
);
return {
accepted: score.score >= 0.8,
confidence: score.score,
note: `tier ${tierIndex}; total cost ${totalCost}; model cost ${cost ?? "unknown"}`,
};
},
},
{ model: opus },
],
budget: { maxCost: 0.05, maxLatencyMs: 5000 },
});When prompt is provided, evaluate({ result, input }) is typed from that prompt. A prompt-bound cascade can only be used with the same prompt.
Evaluators
evaluate receives one object:
| Field | Meaning |
|---|---|
result | The prompt output for bound cascades, otherwise unknown |
input | The prompt input for bound cascades |
model | Concrete model id for the tier |
cost | Provider-reported tier cost, when available |
totalCost | Cumulative tier and judge cost so far |
tierIndex | Zero-based tier index |
report | Awaits a judge/scorer result and folds its cost into totalCost |
Return true, false, or { accepted, confidence?, note? }.
Budgets
budget.maxCost uses measured provider cost plus cost reported through report(). budget.maxLatencyMs aborts in-flight tier work. If latency expires after at least one tier returned, Crux returns the best result and marks the cascade receipt with budgetExceeded: true; otherwise it throws CascadeExhaustedError.
Invalid Responses
Use escalateOn: ['invalid_response'] when structured validation exhaustion should reject a tier and continue.
const extraction = cascade({
prompt: invoicePrompt,
tiers: [
{ model: fallback([gpt5nano, haiku]), escalateOn: ["invalid_response"] },
{ model: opus },
],
});Receipts
Cascade decisions appear in result.routing.trace.
result.routing?.trace;
// [{
// kind: 'cascade',
// id: 'support-answer-cascade',
// acceptedAtTier: 1,
// budgetExceeded: false,
// tiers: [
// { model: 'claude-haiku', status: 'rejected', confidence: 0.62, judgeCost: 0.0028, budget: 0.05, note: 'quality below threshold' },
// { model: 'claude-opus', status: 'accepted' },
// ],
// }]Each tier retains its confidence, judgeCost, configured budget, and evaluator note when supplied. Run Detail renders these receipt fields alongside the tier outcome so a rejection or budget exit can be inspected without reconstructing evaluator state from spans.
cascade() is generate-only. Use router, split, retry, or fallback for streaming model selection.