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 ‡ | qwen | thinkingmachines | xai | |
|---|---|---|---|---|---|---|---|---|---|
synthesize | claude-sonnet-4-6 | openai/gpt-5.6-luna-pro | google/gemini-3.6-flash | deepseek/deepseek-v4-flash-0731 | moonshotai/kimi-k2.5 | mistral-large-latest | qwen/qwen3.7-flash | thinkingmachines/inkling | x-ai/grok-4.5 |
reasoning | claude-sonnet-4-6 † | openai/gpt-5.6-luna | google/gemini-3.5-flash-lite | deepseek/deepseek-v4-flash-0731 † | moonshotai/kimi-k2.5 † | mistral-large-latest † | qwen/qwen3.7-flash † | thinkingmachines/inkling-small | x-ai/grok-4.5 † |
converse | claude-haiku-4-5 | openai/gpt-5.6-luna | google/gemini-3.5-flash-lite | deepseek/deepseek-v4-flash-0731 † | moonshotai/kimi-k2.5 † | mistral-small-latest | qwen/qwen3.7-flash † | thinkingmachines/inkling-small | x-ai/grok-4.5 † |
utility | claude-haiku-4-5 | openai/gpt-5.6-luna | google/gemini-3.5-flash-lite | deepseek/deepseek-v4-flash-0731 † | moonshotai/kimi-k2.5 † | mistral-small-latest | qwen/qwen3.7-flash † | thinkingmachines/inkling-small | x-ai/grok-4.5 † |
† Resolves to the same model id as that family's synthesize rung —
either an explicit alias-up (anthropic reasoning: no distinct mid-rung
model exists) or a single-rung family where every call class shares one
model (moonshot, deepseek, qwen, xai). openai (luna →
luna-pro), google (flash-lite → 3.6-flash), and thinkingmachines
(inkling-small → inkling) carry a real two-rung ladder.
‡ 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.
Per-call-class overrides
SessionRuntimeConfig.perCallClassModelRouting replaces the matrix
rung for named call classes. It's a partial map from call class to
model id, read once at construction. Classes you leave out keep their
matrix rung.
const runtime = new SessionRuntime({
// ...
// anthropic session: serve converse calls from the synthesize rung
perCallClassModelRouting: { converse: "claude-sonnet-4-6" },
});@pleach/coding-agent/policy ships createPerCallClassRouting({ routes }),
which stamps the same field from a policy
(Multi-synthesize).
An absent, empty, or all-blank map leaves the runtime on the unrouted
path.
Which adapters honour it. The runtime passes the routed id to
adapter.resolveModel as the call's modelOverride. Core's
createProviderAdapter and the quickstart standard adapter (what
createPleachRuntime wires) resolve to it and serve it. An adapter
that ignores the override keeps its own model, and the decision is
recorded as not-honored rather than applied.
Cross-family routes are refused. The routed id's family is derived
and compared with the session's locked family. The route is refused
when the families differ (cross-family), when no family can be
derived from the id (family-unknown), or when the lock isn't
observable yet (lock-unobservable). A family that was only defaulted
counts as unobservable, not as a lock. A refused call resolves as if
unrouted. The refusal is disclosed by a [PerCallClassRouting] override-refused
probe and by the decision stamped on auditMetadata.perCallClassRouting
(requestedModelId, status, reason, and the requested and locked
families). Change families by re-locking the session, not by routing.
Pricing follows the model served. A route to another rung of the
same family is priced at that rung's per-model rates. A route to the
class's own rung is priced exactly like an unrouted call. A route to a
model that sits in no rung of the family is left unpriced: costUsd
is omitted rather than filled at the call class's default rate.
Custom quickstart streams must dispatch to ctx.resolvedModel. A
providerStream you pass to the quickstart receives a context whose
resolvedModel is set on the seam path only when a route was honoured
for that call. When it's present, send the request to that model: the
call is already priced, cached, and audited as it. When it's absent,
pick your model as before.
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.