pleach
Architecture

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

anthropicopenaigoogledeepseekmoonshotmistralxai
synthesizeclaude-sonnet-4-6gpt-5.4gemini-3-prodeepseek-v4-promoonshotai/kimi-k2.5mistral-large-latestgrok-4.3
reasoningclaude-sonnet-4-6gpt-5.4-minigemini-3-prodeepseek-v4-promoonshotai/kimi-k2.5mistral-large-latestgrok-4.20-0309-reasoning
converseclaude-haiku-4-5gpt-5.4-minigemini-3-flash-previewdeepseek-v4-flashmoonshotai/kimi-k2.5mistral-small-latestgrok-4.20-0309-non-reasoning
utilityclaude-haiku-4-5gpt-5.4-nanogemini-3-flash-previewdeepseek-v4-flashmoonshotai/kimi-k2.5mistral-small-latestgrok-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

ClassWhen the runtime picks itWhy it has its own row
synthesizeThe single final answer for a turn.Highest-quality model per family; exactly one of these fires per turn.
reasoningAn intermediate generator whose output feeds the next call.Quality matters, but it's not user-visible prose — a smaller model is acceptable.
converseShort user-facing prose (clarifying questions, refusal hints, retry narration).Latency-sensitive and conversational — typically the cheapest model that can hold the voice.
utilityInternal 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.

FamilyAvailable transports
anthropicnative · openrouter · byok-native · byok-openrouter
openainative · openrouter · byok-native · byok-openrouter
googleopenrouter · byok-openrouter (native pending)
deepseekopenrouter · byok-openrouter (native pending)
moonshotopenrouter · byok-openrouter
mistralnot exposed for routing — placeholder model ids (see the matrix note above)
xaiopenrouter · 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

On this page