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

# Router Framework Overview

> Build and self-host an LLM router with pluggable policies and provider execution.

`@dari/router-core` puts multiple LLMs behind one endpoint. It accepts OpenAI Chat Completions and Anthropic Messages requests, picks a model with your policy, executes it, and responds in the caller's protocol.

<Note>
  `@dari/router-core` is a preview package. It is not yet published to a registry. Examples assume a Dari monorepo checkout with `dari-router-core/`.
</Note>

## How It Works

```text theme={null}
OpenAI or Anthropic request
        │
        ▼
 Normalize + filter models
        │
        ▼
  Your policy chooses
        │
        ▼
 Pi AI or a custom executor
        │
        ▼
 Response in the input protocol
```

## Framework Or Managed Platform

Both share routing concepts. The difference is who owns the operational layer.

<CardGroup cols={2}>
  <Card title="Router Framework" icon="route">
    You host the endpoint, provide credentials, write the policy, and own auth, rate limits, routing state, telemetry, and billing.
  </Card>

  <Card title="Managed Platform" icon="cloud">
    Dari hosts the endpoint and manages credentials, routing state, retries, fallbacks, telemetry, billing, and speculative routing.
  </Card>
</CardGroup>

Use the [managed router quickstart](/router/quickstart) for a hosted endpoint. Use the framework when you want full control.

## The Three Boundaries

**Models** — declare candidates with capabilities. `pi.model()` fills this from Pi AI's catalog, or declare manually for custom executors.

**Policies** — receive the normalized request and eligible candidates, return one model and optionally a reasoning effort. Use a heuristic, a learned model, a remote service, or `createDariRoutingPolicy`.

**Executors** — call the selected model. The built-in Pi runtime handles catalog models. Custom executors replace it per-model for other SDKs or transports.

## Two API Levels

<CardGroup cols={2}>
  <Card title="createRouter()">
    Web-standard `fetch()` handler and `select()` method. Best for most apps.
  </Card>

  <Card title="Phased API">
    `prepareRoute()` and `finalizeRoute()` as separate steps. For custom orchestration like speculative routing.
  </Card>
</CardGroup>

## Operational Options

`createRouter()` takes a few optional knobs for running in production. All are host-injected — the framework still reads no env vars, database, or clock source of its own.

* **`leaseStore`** — storage for multi-turn [lease](/framework/auto-router#leases) commitments. Defaults to an in-memory store with a 30-minute TTL; inject a persistent implementation to share leases across processes. Methods may return values or promises, and read-modify-write atomicity under concurrent requests is the store's responsibility. Store failures are non-fatal — a throwing or rejecting store never fails the request: failed reads route as if no lease exists, failed writes serve without persisting, and failed deletes fall back to the turn/TTL expiry.
* **`hooks`** — lifecycle callbacks for telemetry, billing, and logging: `onSelection`, `onCompletion`, `onStreamClose`, and `onError`. `onSelection` observes the policy's decision before execution; the other hooks receive the selection actually served, so after a [fallback](/framework/custom-executors#retry-and-fallback) their decision names the fallback model. Hooks are fire-and-forget and isolated — a throwing hook never affects the request or stream, and `onStreamClose` reports only `finishReason` and usage (streamed output is not buffered).
* **`fallback`** — `{ enabled, requiresDifferentProvider }` opt-in [cross-model retry](/framework/custom-executors#retry-and-fallback) when the selected model's executor fails.
* **`generateId`** — override wire-ID generation (defaults to `crypto.randomUUID()`), useful for deterministic tests or host-correlated IDs.

### Serverless Deployments

The default in-memory lease store assumes a single long-lived process. On serverless platforms (Cloud Run, Lambda, Fly machines), instances scale to zero and requests fan out across replicas, so in-memory leases are lost on every cold start and fragmented across instances. Nothing breaks — the policy just re-selects — but you lose provider prompt-cache affinity, and with the [Auto Router](/framework/auto-router) every lost lease is a billed re-selection on the next turn.

Inject a shared store instead. `examples/lease_stores.ts` in the package ships copy-paste Redis and Postgres `LeaseStore` implementations written against minimal structural client types, plus a runnable demo of a lease surviving a router restart (`bun run example:lease-stores`).

<CardGroup cols={2}>
  <Card title="Quickstart" icon="bolt" href="/framework/quickstart">
    Run the examples and serve your first request.
  </Card>

  <Card title="Models" icon="cube" href="/framework/models">
    Declare candidates and understand eligibility.
  </Card>

  <Card title="Policies" icon="brain" href="/framework/policies">
    Write routing policies.
  </Card>

  <Card title="Pi Runtime" icon="shield-halved" href="/framework/pi-runtime">
    Execute provider calls with injected credentials.
  </Card>

  <Card title="Custom Executors" icon="code" href="/framework/custom-executors">
    Integrate another SDK or transport.
  </Card>
</CardGroup>
