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.
Installs into .claude/skills of the current project.
Are you the author of Batuta Routing?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/compozy-batuta-routing)
---
name: batuta-routing
description: 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.
---
# 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)
1. `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.
2. Map each lane's selection rule onto the catalog using the cost fields
(`input_per_million` / `output_per_million`) as evidence.
3. 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`.
```json runtime_rules
[
{ "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.