Crux
GuidesRouting & Fallback

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:

FieldMeaning
resultThe prompt output for bound cascades, otherwise unknown
inputThe prompt input for bound cascades
modelConcrete model id for the tier
costProvider-reported tier cost, when available
totalCostCumulative tier and judge cost so far
tierIndexZero-based tier index
reportAwaits 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.

On this page