Model resolution matrix
Which model fires for which call class, resolved per provider family at session start.
Every call into the runtime resolves to a concrete model through a
(ProviderFamily × CallClass) matrix. The family is locked at
session start (it picks tokenizer, prompt-cache key, tool-call dialect,
and refusal pattern); the call class is declared per-call by the
seam that's invoking the LLM.
This page is the reference table for the routing cluster. The three concepts — CallClass, Seam, family-lock — that decide which model fires for which kind of call are framed in Family-lock → the routing cluster.
The matrix
anthropic | openai | google | deepseek | moonshot | mistral ‡ | xai | |
|---|---|---|---|---|---|---|---|
synthesize | claude-sonnet-4-6 | gpt-5.4 | gemini-3-pro | deepseek-v4-pro | moonshotai/kimi-k2.5 | mistral-large-latest | grok-4.3 |
reasoning | claude-sonnet-4-6 † | gpt-5.4-mini | gemini-3-pro † | deepseek-v4-pro † | moonshotai/kimi-k2.5 † | mistral-large-latest † | grok-4.20-0309-reasoning |
converse | claude-haiku-4-5 | gpt-5.4-mini | gemini-3-flash-preview | deepseek-v4-flash | moonshotai/kimi-k2.5 † | mistral-small-latest | grok-4.20-0309-non-reasoning |
utility | claude-haiku-4-5 | gpt-5.4-nano | gemini-3-flash-preview | deepseek-v4-flash | moonshotai/kimi-k2.5 † | mistral-small-latest | grok-4.20-0309-non-reasoning |
† Aliased up to that family's synthesize rung because no distinct
mid-rung model exists. Moonshot is single-rung today — every call
class resolves to the same model id.
‡ mistral is not currently exposed for routing. Its matrix
entries are placeholder model ids (mistral-small-latest /
mistral-large-latest) that don't resolve at any transport, so the
family is excluded from EXPOSED_FAMILIES and a session cannot lock
to it today. The row is shown for matrix completeness only.
The live matrix is the source of truth — it lives in @pleach/core
and is the only place edited when a model is added or rotated. A
future build of this page will regenerate the table directly from
the published matrix; until then, treat this rendering as a
snapshot suitable for orientation, not for procurement decisions.
Call classes
| Class | When the runtime picks it | Why it has its own row |
|---|---|---|
synthesize | The single final answer for a turn. | Highest-quality model per family; exactly one of these fires per turn. |
reasoning | An intermediate generator whose output feeds the next call. | Quality matters, but it's not user-visible prose — a smaller model is acceptable. |
converse | Short user-facing prose (clarifying questions, refusal hints, retry narration). | Latency-sensitive and conversational — typically the cheapest model that can hold the voice. |
utility | Internal classification or extraction (intent detection, refusal repair). | Pure machine-consumed; cheapest model the family ships. |
The classes are a downgrade ladder: synthesize → reasoning → converse → utility is the cost-quality direction.
Transports
Family choice fixes the tokenizer; transport choice fixes how the request gets to the provider. Both lock at session start.
| Family | Available transports |
|---|---|
anthropic | native · openrouter · byok-native · byok-openrouter |
openai | native · openrouter · byok-native · byok-openrouter |
google | openrouter · byok-openrouter (native pending) |
deepseek | openrouter · byok-openrouter (native pending) |
moonshot | openrouter · byok-openrouter |
mistral ‡ | not exposed for routing — placeholder model ids (see the matrix note above) |
xai | openrouter · byok-openrouter (native pending) |
Families without a native provider collapse native and byok-native
requests to their OpenRouter equivalents at resolve time, rather than
dispatching to a transport that doesn't exist. The resulting
transport is recorded on the audit row, so the collapse is visible
after the fact.
native reaches the provider directly. openrouter routes through
OpenRouter — useful for evaluation and for families without a native
transport. byok-* variants delegate credentials to the end user's
BYOK key rather than the deployment's. Transport is session-scoped, so
mid-session escalation across transports doesn't silently happen.
A session that starts on anthropic + byok-native and exhausts the
user's key surfaces the exhaustion through the ledger as a
providerCascade row with outcome.status: "exhausted", not a
silent flip to the deployment's own credentials.
Cross-family fallback policy
The runtime does not silently widen across families when the
session's locked family is exhausted. Once every in-family rung is
tried, the runtime surfaces a family-exhausted state to the host
and lets the user pick another family explicitly. The pivot
exists because cross-family widening produced a measurable regression
class: a session that began on anthropic's tool-call dialect would
silently cross to openai's on cascade, and tools whose argument
schemas relied on Anthropic's input_schema shape would stop being
called correctly — the same agent code, the same session id, two
incompatible tool dialects within one conversation. The family lock
makes that crossing visible to the host instead of invisible to the
runtime.
A narrow legacy carve-out preserves cross-family fallback for models that don't resolve through the matrix at all — BYOK rigs, modal-LLM endpoints, and unrecognized model slugs — because those sessions never had a family lock to honor in the first place.
Where to go next
Family-locked routing
One ProviderFamily and one Transport lock at session start, freezing tokenizer, prompt-cache key, tool-call dialect, and refusal pattern. The cascade walks in-family rungs only.
Session lifecycle
How a session is minted, persisted, resumed, aborted, time-traveled, and deleted — the unit of identity that outlives any single turn.