Batuta Routing Table
Batuta's core opinion: route every task to the cheapest executor that can
handle it. Lanes use the complexity vocabulary that cy-create-tasks
writes into task frontmatter (low, medium, high, critical) — the
same vocabulary runtime_rules[].match.complexity matches on.
Lane semantics (the durable opinion)
| Lane |
Intent |
Selection rule |
low |
Contained change, well-trodden paths |
Cheapest coding-capable model in the catalog |
medium |
New interfaces, moderate coordination |
Mid-tier coding model; raise reasoning before raising cost |
high |
New subsystem, heavy reasoning |
Strong coding model, premium tier acceptable |
critical |
Cross-cutting, high regression risk |
The operator's most trusted frontier model |
How batuta derives the concrete table (never copy an example)
compozy__provider_models_list (with costs) is the ONLY source of
concrete provider/model IDs — it reflects the CLIs actually installed
and the models actually discovered on this machine. A provider absent
from the catalog is not installed; never route to it.
- Map each lane's selection rule onto the catalog using the cost fields
(
input_per_million / output_per_million) as evidence.
- Model enablement is account-side and invisible to the daemon — present
the derived table (with costs) to the operator for confirmation before
storing; ask what their accounts enable when in doubt.
Example only — derived on one machine on 2026-08-11, DO NOT reuse
On that machine the derivation produced: low → codex/gpt-5.6-luna,
medium → codex/gpt-5.6-terra@high, high → codex/gpt-5.6-sol,
critical → claude/claude-opus-4-8. Your catalog will differ; derive, do
not copy.
Canonical rule shape
This is the exact JSON SHAPE batuta writes with compozy__loop_configure
(stored per-workspace override for implement-tasks) after deriving the
values from the catalog — the model/provider strings below are the same
dated example as above and MUST be replaced by the derived ones. The stored
override is what run-loop children resolve at execution — batuta never
sends per-run rules on dispatch, because per-run rules freeze into the run
and are not inherited by run-loop children anyway. Rule matching
precedence inside the stored layer: id > type > complexity.
[
{ "match": { "complexity": "low" }, "runtime": { "provider": "codex", "model": "gpt-5.6-luna" } },
{
"match": { "complexity": "medium" },
"runtime": { "provider": "codex", "model": "gpt-5.6-terra", "reasoning": "high" }
},
{ "match": { "complexity": "high" }, "runtime": { "provider": "codex", "model": "gpt-5.6-sol" } },
{
"match": { "complexity": "critical" },
"runtime": { "provider": "claude", "model": "claude-opus-4-8" }
}
]
Provider quirks
- Some providers multiplex upstreams and require the model field to carry a
prefix — e.g.
opencode only binds opencode/kimi-k2.5, never bare
kimi-k2.5. The catalog's exact model_id is authoritative; copy it
verbatim into the rule.
- A model can exist in the catalog and still be disabled for the operator's
account at the provider (invisible to the daemon). When a lane fails its
bind with zero tokens, ask the operator what their account enables.
Escalation and reclassification
- Repeated failure in a lane: write a surgical
id rule one lane up into
the STORED override (compozy__loop_configure on implement-tasks, e.g.
{"match":{"id":"task_NN"},"runtime":{...}} prepended to the rules), then
re-dispatch batuta-deliver. id beats complexity; remove the rule
after the task lands.
- Operator reclassification in conversation ("use luna for this one")
becomes the same stored
id rule before the next dispatch.
- The daemon persists
resolved_runtime with per-field provenance on every
generation — routing decisions are auditable via compozy__loop_status,
never narrated.
1---2name: batuta-routing3description: Default cost/complexity routing table for the batuta conductor. Read at bootstrap as a starting point, validated against the live provider catalog, then stored as the per-workspace loop configuration; the stored workspace override is authoritative afterwards.4---56# Batuta Routing Table78Batuta's core opinion: route every task to the cheapest executor that can9handle it. Lanes use the `complexity` vocabulary that `cy-create-tasks`10writes into task frontmatter (`low`, `medium`, `high`, `critical`) — the11same vocabulary `runtime_rules[].match.complexity` matches on.1213## Lane semantics (the durable opinion)1415| Lane | Intent | Selection rule |16| ---------- | ------------------------------------- | ---------------------------------------------------------- |17| `low` | Contained change, well-trodden paths | Cheapest coding-capable model in the catalog |18| `medium` | New interfaces, moderate coordination | Mid-tier coding model; raise reasoning before raising cost |19| `high` | New subsystem, heavy reasoning | Strong coding model, premium tier acceptable |20| `critical` | Cross-cutting, high regression risk | The operator's most trusted frontier model |2122## How batuta derives the concrete table (never copy an example)23241. `compozy__provider_models_list` (with costs) is the ONLY source of25 concrete provider/model IDs — it reflects the CLIs actually installed26 and the models actually discovered on this machine. A provider absent27 from the catalog is not installed; never route to it.282. Map each lane's selection rule onto the catalog using the cost fields29 (`input_per_million` / `output_per_million`) as evidence.303. Model enablement is account-side and invisible to the daemon — present31 the derived table (with costs) to the operator for confirmation before32 storing; ask what their accounts enable when in doubt.3334### Example only — derived on one machine on 2026-08-11, DO NOT reuse3536On that machine the derivation produced: `low → codex/gpt-5.6-luna`,37`medium → codex/gpt-5.6-terra@high`, `high → codex/gpt-5.6-sol`,38`critical → claude/claude-opus-4-8`. Your catalog will differ; derive, do39not copy.4041## Canonical rule shape4243This is the exact JSON SHAPE batuta writes with `compozy__loop_configure`44(stored per-workspace override for `implement-tasks`) after deriving the45values from the catalog — the model/provider strings below are the same46dated example as above and MUST be replaced by the derived ones. The stored47override is what `run-loop` children resolve at execution — batuta never48sends per-run rules on dispatch, because per-run rules freeze into the run49and are not inherited by `run-loop` children anyway. Rule matching50precedence inside the stored layer: `id > type > complexity`.5152```json runtime_rules53[54 { "match": { "complexity": "low" }, "runtime": { "provider": "codex", "model": "gpt-5.6-luna" } },55 {56 "match": { "complexity": "medium" },57 "runtime": { "provider": "codex", "model": "gpt-5.6-terra", "reasoning": "high" }58 },59 { "match": { "complexity": "high" }, "runtime": { "provider": "codex", "model": "gpt-5.6-sol" } },60 {61 "match": { "complexity": "critical" },62 "runtime": { "provider": "claude", "model": "claude-opus-4-8" }63 }64]65```6667## Provider quirks6869- Some providers multiplex upstreams and require the model field to carry a70 prefix — e.g. `opencode` only binds `opencode/kimi-k2.5`, never bare71 `kimi-k2.5`. The catalog's exact `model_id` is authoritative; copy it72 verbatim into the rule.73- A model can exist in the catalog and still be disabled for the operator's74 account at the provider (invisible to the daemon). When a lane fails its75 bind with zero tokens, ask the operator what their account enables.7677## Escalation and reclassification7879- Repeated failure in a lane: write a surgical `id` rule one lane up into80 the STORED override (`compozy__loop_configure` on `implement-tasks`, e.g.81 `{"match":{"id":"task_NN"},"runtime":{...}}` prepended to the rules), then82 re-dispatch `batuta-deliver`. `id` beats `complexity`; remove the rule83 after the task lands.84- Operator reclassification in conversation ("use luna for this one")85 becomes the same stored `id` rule before the next dispatch.86- The daemon persists `resolved_runtime` with per-field provenance on every87 generation — routing decisions are auditable via `compozy__loop_status`,88 never narrated.