> ## 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.

# Policies

> Write routing policies that choose between eligible models.

After Router Core filters candidates by capability, your policy picks one.

## Write A Policy

A policy receives the normalized request, eligible candidates, and an abort signal:

```ts theme={null}
import type { RoutingPolicy } from "@dari/router-core";

const policy: RoutingPolicy = ({ request, candidates }) => {
  const preferred = request.tools.length > 0
    ? "anthropic/claude-sonnet-4-6"
    : "openai/gpt-5.4-mini";
  const selected = candidates.find((c) => c.id === preferred)
    ?? candidates[0]!;

  return {
    model: selected.id,
    reasoningEffort: selected.defaultReasoningEffort,
    reason: request.tools.length > 0
      ? "Use the preferred model for tool calls."
      : "Use the smaller model for plain text.",
  };
};
```

Policies can be sync or async. They can use a heuristic, benchmark data, a learned model, a remote service, or another LLM. Router Core validates the result against eligible candidates.

`reasoningEffort` is optional — omitting it applies the candidate's default. The field is `reasoningEffort` in TypeScript, `reasoning_effort` in JSON, and `thinking_level` in Dari custom rules.

## Inspect Without Executing

Call `router.select()` to see a decision without making a provider call:

```ts theme={null}
import { openAIChatRequest } from "@dari/router-core";

const request = openAIChatRequest({
  model: "my-router",
  messages: [{ role: "user", content: "Explain cache locality." }],
});
const selection = await router.select(request);
console.log(selection.decision);
```

`anthropicRequest(payload)` provides the equivalent normalization for Anthropic Messages.

## Dari's Selector Policy

`createDariRoutingPolicy()` uses a separate LLM call (the selector) to choose the route. Its answer is validated before execution.

```ts theme={null}
import { createDariRoutingPolicy, createPiRuntime, createRouter } from "@dari/router-core";

const pi = await createPiRuntime({ apiKey: process.env.OPENAI_API_KEY! });
const policy = createDariRoutingPolicy({
  runtime: pi,
  selectorModel: "openai/gpt-5.4-mini",
  selectorContextWindowChars: 400_000,
});

const router = createRouter({
  models: [pi.model("openai/gpt-5.4-mini"), pi.model("openai/gpt-5.4")],
  policy,
  runtime: pi,
});
```

`selectorModel` doesn't need to be a routed candidate. `selectorContextWindowChars` is a character budget for the selector prompt, not a token count.

Optional callbacks add evidence to the choice:

* `pricing` — model and cache rates
* `evals` — benchmark scorecards
* `state` — observed provider-cache prefixes
* `metadata` — richer model capabilities

None are required to run the policy.

### Custom Rules Strategy

Use `strategy: "custom"` to give the selector natural-language rules:

```ts theme={null}
const policy = createDariRoutingPolicy({
  runtime: pi,
  selectorModel: "openai/gpt-5.4-mini",
  selectorContextWindowChars: 400_000,
  strategy: "custom",
  customConfig: {
    rules: [
      { when: "architecture or difficult debugging", use: "openai/gpt-5.4", thinking_level: "high" },
      { when: "simple factual questions", use: "openai/gpt-5.4-mini" },
    ],
    default: "openai/gpt-5.4-mini",
  },
});
```

The selector judges each rule's `when` text against the request. Its answer must name an eligible pair or Router Core returns a policy error.
