> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dari.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Decision Pipeline

> Use the advanced policy-engine phases when selection needs orchestration.

Most applications should use `createRouter`. Import `@mupt-ai/dari-router/policy-engine` only when your host must inspect routing evidence, call a selector itself, persist routing state, or coordinate selection with other work.

The policy engine is the advanced deterministic core beneath [`createDariRoutingPolicy`](/framework/policies). It works with a different vocabulary than the framework: candidates are model/reasoning-level pairs keyed by `model` (not `id`), and inputs use wire-style names like `thinking_level`, `prefix_hits`, and `tool_choice_fp`.

## Prepare, select, finalize

`prepareRoute()` performs deterministic candidate resolution and builds the selector request. Your host calls the selector. `finalizeRoute()` validates the selector output against the prepared candidates.

```ts theme={null}
import {
  finalizeRoute,
  prepareRoute,
  type RouteInput,
} from "@mupt-ai/dari-router/policy-engine";

const input: RouteInput = {
  candidateModels: ["openai/gpt-5.4-mini", "anthropic/claude-sonnet-4-6"],
  metadataLookup: (model) => modelMetadata[model]!,
  pricing: (model) => pricing[model] ?? null,
  requiredCapabilities: [],
  strategy: "slm",
  messages: conversation,
  chainsByModel: new Map([
    ["openai/gpt-5.4-mini", []],
    ["anthropic/claude-sonnet-4-6", []],
  ]),
  prefixHits: [],
  nowMs: Date.now(),
  toolChoiceFp: "",
  responseFormatFp: "",
  selectorModel: "openai/gpt-5.4-mini",
  selectorContextWindowChars: 400_000,
};

const prepared = prepareRoute(input);
if (!prepared.selectorPreparation) throw new Error("No selector request");
const selectorOutput = await callSelector(prepared.selectorPreparation.selectorRequest);
const result = finalizeRoute(prepared, selectorOutput);
console.log(result.decision);
```

The input is deliberately explicit. The policy engine does not read credentials, environment variables, databases, or a clock. Supply model metadata, pricing, conversation state, and timestamps from your host. `route(input, selector)` composes the two phases when you do not need an interleaving point.

Most fields have straightforward meanings. `chainsByModel` and `prefixHits` describe the conversation's prompt-cache history: which provider-cache blocks are already warm, so cost estimates can price a cached read instead of a full write. `toolChoiceFp` and `responseFormatFp` are fingerprints of the last tool-choice and response-format settings; a change there invalidates prompt-cache continuity, so the engine prices the tail differently.

## Evidence and state

The prepared result exposes resolved model/reasoning pairs, cache-aware cost estimates, pruning decisions, recovered decisions, warnings, and the exact selector request. Unknown pricing remains unknown rather than becoming zero.

Hosts can persist and later provide `chainsByModel`, `prefixHits`, and previous decisions. Those values describe provider-cache continuity (whether a provider can read previously seen prompt blocks from cache instead of paying for them again); they are not a replacement for application conversation storage. [Prompt Caching](/framework/prompt-caching) explains how this routing evidence differs from provider usage and selection leases.

## Selector output and fallback

The selector returns JSON containing `selected_model`, `reasoning_effort`, and `reason`. `finalizeRoute()` requires the selected pair to be among prepared candidates. The prepared fallback configuration can produce a validated fallback decision; executing it and reporting usage remain host responsibilities.

## Anonymous selection

The policy engine also supports training/evaluation flows where the selector input hides which real model each option is: candidates are replaced by shuffled action labels (for example `A`, `B`, `C`) so the selector scores the option, not the brand:

```ts theme={null}
import {
  assignAnonymousActions,
  anonymizeSelectorInput,
  parseAnonymousActionSelection,
} from "@mupt-ai/dari-router/policy-engine";

const mapping = assignAnonymousActions(candidates, Math.random);
const anonymousInput = anonymizeSelectorInput(selectorInput, mapping);
const selectedPair = parseAnonymousActionSelection(selectorOutput, mapping);
```

Use a seeded random source for reproducible datasets. Anonymous selection is for selector training and evaluation, not a requirement for ordinary routing.

The complete no-network example is `dari-router/examples/basic_route.ts`.
