Crux
GuidesRouting & Fallback

Router

Classify input and select a model route before generation starts.

router() selects one route before a model call starts. It is the right primitive when request shape, user tier, region, or an operator override should choose the model.

support-router.ts
import { router, type RouteArgs } from "@use-crux/core/routing";

interface RoutingContext {
  tier: "free" | "pro" | "enterprise";
  betaOptIn?: boolean;
}

export const supportRouter = router({
  id: "support-router",
  classify: ({
    input,
    context,
  }: RouteArgs<RoutingContext, { question: string }>) => {
    if (context.betaOptIn || context.tier === "enterprise") return "deep";
    return input.question.length > 2000 ? "balanced" : "fast";
  },
  routes: {
    fast: { model: gpt5nano, maxTokens: 800 },
    balanced: sonnet,
    deep: { model: opus, reasoning: { effort: "high" } },
    default: sonnet,
  },
});

Routing context is typed from the RouteArgs annotation. It is supplied at the call site with routing: and is not part of the prompt input.

const result = await generate(supportPrompt, {
  model: supportRouter,
  input: { question },
  routing: { tier: account.tier, betaOptIn: account.flags.beta },
});

Forced Routes

Use route: for tests, previews, admin controls, or deterministic comparisons.

await generate(supportPrompt, {
  model: supportRouter,
  input: { question },
  routing: { tier: "pro" },
  route: "deep",
});

route: is typed from the router route keys. Forced routes skip classify and mark the receipt step with forced: true.

Default Route

Always include default. If classify returns an unknown key, Crux runs default and records usedDefaultRoute: true in the receipt.

result.routing?.trace;
// [{ kind: 'router', id: 'support-router', classifiedAs: 'legacy', route: 'default', usedDefaultRoute: true, forced: false }]

The Project Index emits routing.router_missing_default when a router omits default.

Call Profiles

A route can be a raw model, another routing wrapper, or a call profile:

routes: {
  cheap: { model: gpt5nano, temperature: 0, maxTokens: 600 },
  careful: fallback([sonnet, opus]),
  default: sonnet,
}

Profile settings become defaults for the selected concrete attempt. Explicit call-site settings still win.

Avoid

  • Do not omit default.
  • Do not make classify expensive unless the savings justify the extra work.
  • Do not put routing context schemas on the router. Validate at transport boundaries; use RouteArgs for app-internal routing types.
  • Use cascade() when you must inspect a completed result before choosing the next model.

On this page