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

# Operations

> Run a reliable router across requests, replicas, and provider failures.

`createRouter` handles request normalization, eligibility, selection, execution, protocol serialization, and optional fallback. Your host remains responsible for credentials, deployment, durable state, and application telemetry.

## Leases

A policy can commit a model for later turns:

```ts theme={null}
const policy: RoutingPolicy = ({ candidates }) => ({
  model: candidates[0]!.id,
  leaseTurnsRemaining: 3,
  reason: "Keep this conversation on one model.",
});
```

A lease is used only when the request has a `cacheKey` (OpenAI or Anthropic `prompt_cache_key`). The default in-memory store expires leases after 30 minutes. `createRouter` calls `pruneExpired()` before each cacheKey-bearing selection, and every served turn refreshes the TTL, so leases serve while they still have turns and have not expired. `router.select()` and `router.fetch()` honor leases; `router.evaluatePolicy()` does not.

A selection lease and a provider prompt cache are related but different: the lease keeps routing on one model, while the provider decides whether the prompt prefix is cached. See [Prompt Caching](/framework/prompt-caching) for the complete responsibility boundary.

For replicas or restarts, provide a `LeaseStore` backed by your database or cache:

```ts theme={null}
import type { LeaseStore } from "@mupt-ai/dari-router";

const leaseStore: LeaseStore = {
  async get(cacheKey) { return database.readLease(cacheKey); },
  async set(cacheKey, lease) { await database.writeLease(cacheKey, lease); },
  async delete(cacheKey) { await database.deleteLease(cacheKey); },
  async pruneExpired(nowMs) { await database.deleteExpiredLeases(nowMs); },
};
```

The store owns atomic read-modify-write behavior under concurrent requests. Store failures are advisory: a failed operation does not fail the request, but a lease may not be honored or persisted.

## Hooks

Hooks are fire-and-forget and may return promises. A throwing or rejecting hook does not change the response:

```ts theme={null}
const router = createRouter({
  models,
  policy,
  executor,
  hooks: {
    onSelection(selection, request) { metrics.selection(selection, request); },
    onCompletion(completion, selection) { metrics.completed(completion, selection); },
    onStreamClose(completion, selection, error) { metrics.stream(selection, error); },
    onError(error, selection) { logger.error(error, selection); },
  },
});
```

`onSelection` observes the selected decision. Completion and error hooks identify the model that actually served, including a fallback.

## Fallback

Enable one cross-model retry at the router boundary:

```ts theme={null}
const router = createRouter({
  models,
  policy,
  executor,
  fallback: { enabled: true, requiresDifferentProvider: true },
});
```

The router tries another eligible candidate if execution fails. Same-model retry budgets and provider error classification belong to the executor. A failed leased primary is released before the next turn.

## IDs and deployment

Use `generateId` when tests or application tracing require deterministic response IDs. Deploy the `fetch` handler behind your web server or Bun's server. The router itself does not persist conversations or provider-cache observations.

## When to leave `createRouter`

`createRouter` is selector-first. If you need durable routing state, speculative execution, cache-aware cost estimates, or custom billing/retry orchestration, use the lower-level `/policy-engine` phases described in [Decision Pipeline](/framework/decision-pipeline), [Prompt Caching](/framework/prompt-caching), and [Speculative Routing](/framework/speculative-routing).
