One script in front of every non-Claude model
Every call to a model outside Claude goes through a router and a task class. The class picks the destination, and only one class can change the repository.
On August 18, I sent a long coding task to an external model. It ran in the foreground,
directly tied to my terminal. The shell killed the process after 2 minutes with exit 143. I ran
it again in the background, without tying up the terminal, and it finished: 52,226 tokens. A
second dispatch of the same kind reached 56,564. One foreground run died, another ran in the
background, and there was a second dispatch. All were in the burst class. Only the runtime
environment changed.
The router already existed then. The episode showed me that it needs to know the environment
each class runs in. Whoever writes the prompt should not need to know that. Every call to a
model outside Claude goes through the route-engine.sh script.
One entry point
The call is route-engine.sh <caller> <task_class>, with the prompt on standard input.
caller is the name of the agent making the call. The class describes the type of work, and
the router picks the destination.
For every call, the script writes a receipt to a JSONL log, a text file with one record per line. It records the requested provider and the one that actually answered. Sometimes they differ. That is why the receipt matters.
I have written about choosing a model by type of work and about trying another route when a resource runs out. This post is about where those choices meet: the class.
The classes
There are seven today:
reason: reasoning. It is a sentinel that runs nothing. The main model keeps doing the reasoning.bulk: mechanical drafts.cheap: classify, summarize, and extract.burst: long, independent coding tasks.review: a deeper, independent second opinion.review-fast: small or docs-only PRs. Never security, migrations, authentication, or release.vision: images.
Only burst can write to the repository. It runs in isolation in the caller's working folder,
without network access. The other classes only receive text and return text. Nothing is
changed. This limit is intentional.
Burst has no text fallback
The split becomes clear when burst fails. If the router switched to a text-only engine, the
call would return an answer without changing any files. The automation would see a response,
mark the task done, and leave the repository untouched. It is the same false success I
described in the post about fallback.
That is why burst has no text fallback. If no engine can write, the call fails and makes that
clear. I prefer an error that stops the process to a polite answer that did nothing.
The 2-minute episode matters here too. A burst task takes time by nature, so the shell
timeout is part of its environment. In the foreground, the process dies. In the background, it
finishes.
The ALLOWLIST
The router has an allow list called the ALLOWLIST. ADR-024, proposed on August 9, describes 133
explicit rows and two wildcards: codex * and ollama *. This list limits where each class
can go. It does not limit spending.
Two decisions came later and were separate from ADR-024. On August 12, I set a short list of model families for the PR reviewer: Claude, codex, antigravity, ollama cloud, and opencode-go. The rule lives in the router, not in the reviewer's prompt. The reviewer can ignore its prompt text, but not the router. The other decision is ADR-025, accepted on August 14. It replaced the command-line program for the multi-provider gateway with a direct HTTP call.
Engines are counted by meter: subscription (Claude Max, Codex, Ollama Cloud, and some gateway subscriptions), shared pool, and free. The order is subscription, pool, then free.
What each engine can do
On September 3, I learned that the Claude arm of burst edits files but cannot run commands.
The codex arm runs commands but cannot write inside .git. Both are part of burst, but they
have different abilities.
The prompt does not change that. The engine sets the ability. Asking the Claude arm to "run the tests" does not help because it cannot run commands. The router, which picks the arm, needs to know this, not whoever writes the prompt.
