MeridiansMeridians

AI model routing — tier · category · profile [Concept]

Source path: knowledge-base/diagrams/concepts/ai-model-routing.md

# AI model routing — tier · category · profile `[Concept]`

Every LLM call funnels through **one boundary** (`callGenerate`) and asks for its model by *what kind of work it is*, never by a hardcoded id. A three-level indirection turns "this is a prose call" into "run it on this OpenRouter model" — and lets the whole fleet re-tune with a one-line change or a profile swap.

```mermaid
flowchart LR
    Call["call site<br/>*'this is a **write** call'*"] -->|"modelFor('write')"| Resolver

    subgraph Resolver["model-resolver — a precomputed lookup"]
        direction TB
        Cat["**category** (12)<br/>generate · plan · extract · predict ·<br/>write · review · synthesis · gameTheory ·<br/>interaction · action · intuition · question"]
        Prof["**active profile**<br/>a full category → tier assignment<br/>*(app settings; balanced-smart default)*"]
        Tier["**tier** (4)<br/>budget · balanced · performance · frontier"]
        Cat -->|"profile maps category→tier"| Prof --> Tier
    end

    Tier -->|"TIER_MODEL[tier]"| Model(["concrete OpenRouter id<br/>e.g. google/gemini-2.5-flash"])
    Model --> Boundary["**callGenerate** — the one AI boundary<br/>+ named token budget · reasoning budget · websearch"]
```

**The three levels — each swappable in isolation**

| Level | What it is | Change it to… |
|---|---|---|
| **Category** | the *kind* of work (12 modalities) — how a call site names its need | add a new modality of LLM work |
| **Profile** | a complete category→tier assignment — the fleet's quality/cost posture | shift the whole fleet up/down a rung, or pin a few categories |
| **Tier** | budget · balanced · performance · frontier — the quality/cost ladder | retune every category on that rung via `TIER_MODEL` |

**Invariants**

- **`modelFor(category)` is the only way to pick a model.** Call sites never hardcode an id; `standards` §4 enforces it. This is what makes a profile swap re-route every call of a category live.
- **Impact grouping guides the ladder.** Categories are tagged by how much dropping their tier costs downstream — *substrate* work (`generate`, `plan`, `extract`, `predict`) compounds and is protected; *additive/ephemeral* work (`gameTheory`, `question`) can ride a cheaper tier.
- **Token limits and reasoning budgets are fleet settings, not call-site magic numbers.** `MAX_TOKENS_*`, `resolveReasoningBudget`, `resolveWebsearch` resolve at the boundary; JSON is parsed strictly and malformed output follows the caller's ordinary retry policy.

**Where it lives:** `src/lib/core/ai-profiles.ts` (tiers · categories · profiles · `TIER_MODEL`), `src/lib/core/model-resolver.ts` (`modelFor`, precomputed category→model map), `src/lib/engine/ai/infra/api.ts` (`callGenerate` + budget/websearch resolvers). The call-site inventory and cost ladder are owned by [AI_MODELS.md](../../knowledge/conventions/AI_MODELS.md); the discipline by the `standards` skill. High-level engine flow: [engine-pipeline](../flows/engine-pipeline.md).
Open on GitHub

Raw Markdown source