All notes

Where should a decision model decide?

A model choice can happen per request, turn, subagent, session, or after failures. Each timing trades cache reuse for precision.

You can pick a model before you start a task. But one task in Claude Code turns into many requests to the model, and sometimes into helpers with their own requests. So when should a decision model choose: once, or again and again while the work changes?

In part 5, "Should a decision model pick your model?", spatz only recommended a model and effort, and you or your agent made the switch. This part is the follow-up: spatz now has a Claude Code mod that can apply its choice, so the question becomes when it should do that. I've since turned the mod off again; the last section explains why.

A hook suggests, a mod can switch

Claude Code hooks (small scripts that Claude Code starts at fixed moments) can't set the model or the effort. A hook can watch and add a hint, nothing more. A mod is different: it's a plugin with JavaScript hooks that run inside Claude Code. Before each request to the model, a mod can change that request's model and effort. When Claude Code starts a subagent (a helper with its own task and its own requests), a mod can also set the helper's model. The TIL Route one Claude Code request to another model shows that point to act.

The spatz (opens in a new tab) mod uses it. It asks the spatz command line tool for a recommendation and then does one of three things, depending on its mode:

ModeWhat the mod does
OffNothing. It makes no recommendation call.
Show (default)It asks spatz and shows the choice under the prompt. It changes nothing.
ApplyIt also sets the model and effort.

Apply has one more switch. By default it rewrites only helpers. It rewrites the main session only when its main option is on. The reason is the prompt cache, which comes up below.

To try the mod, you need Claude Code 2.1.287 or newer. Run these two commands in Claude Code:

/plugin marketplace add lorenzh/spatz
/plugin install spatz-mod@spatz

The first adds the spatz repository as a marketplace (a list of plugins), and the second installs the mod from it. You don't need to install the spatz command line tool on its own. The mod comes with a small launcher that uses an installed spatz if there is one, and otherwise starts it through Bun or Node.js. The first call downloads about 60 MB.

You set all of this inside a Claude Code session with the /spatz command:

/spatz mode apply
/spatz scope subagent
/spatz main off
/spatz status

The first three lines set the mode, the scope (when spatz decides) and the main switch for this session. The changes last until the session ends. The last line shows the current settings and the last decision. In one test run with these settings, it listed mode apply, scope subagent and main off.

The mod never blocks a request. If spatz is missing, fails or takes too long, the request goes out unchanged. For each decision the mod passes the task text to spatz: your prompt for the main session, the brief for a helper. spatz does not store that text.

A session has five decision points

A user turn is one prompt from you and all the work Claude Code does before it answers. That work can include several model requests and several helpers. The mod can let spatz choose at five points, and it calls them scopes:

ScopeWhen spatz choosesWhat the choice changes
StepBefore each model request, in the main session and in helpersThat one request
TurnAt the start of each user turnThe main session's requests in that turn
SubagentWhen a helper starts, from its briefThe helper's requests
SessionAt the first turn with textThe main session's requests for the rest of the session
EscalateLike turn and subagentThe same, plus a step up after repeated failures

Subagent is the default. Every scope works with every mode.

Switching the model costs cache

When Claude Code sends a request, most of it repeats the request before: the instructions, the conversation so far, the tool results. The prompt cache lets the API reuse that repeated start instead of processing it again. Each response reports two numbers for this: how many tokens it read from the cache, and how many it wrote to the cache. A switch to another model drops the cache.

Think of a cook who has laid out everything for a dish on the bench. The next dish that uses the same things is quick. If you hand that dish to a different cook, they start with an empty bench. The cook stands for the model, and the bench for the cached start of the prompt. The analogy stops fitting at what you can see: you can look at a bench, but the cache shows only afterwards, in the token counts of each response.

This is the trade-off between the scopes:

  • Step reacts most closely to the work, but it asks spatz before every request. The model can change inside a turn, so the cache breaks often. The mod docs suggest it for experiments.
  • Turn asks once per turn. A switch between turns breaks the cache once. Helpers are not touched.
  • Subagent asks only when a helper starts. The main session keeps its model and its cache.
  • Session asks once in total, but the choice can't follow a change of task later in the session.

Escalate waits for failure

Escalate starts like turn and subagent. Then it counts failed test or build commands in the same turn or helper run. After repeated failures it moves one step up a ladder of pairs: within a model from lower to higher effort, up to max (the highest level), then to the stronger model. It keeps the new pair for the rest of that turn or run.

Step can also change the pair inside a turn, but it decides every request from scratch. Escalate is the only scope that moves up because something failed. That's its strength and its cost: it reacts to evidence, but a failure has to happen before the stronger pair is used.

What five test runs showed

Five short test runs checked that the mod works at all. Each run asked Claude Code to start one helper, have it answer what 2 + 3 is, and then repeat the number. The session started on Opus, and the only model spatz could pick was Sonnet. spatz ran in a test mode that marks its recommendations as tests. This is where each request went:

RunMain sessionHelper
Step, apply, main onSonnetSonnet
Turn, apply, main onSonnetOpus
Session, apply, main onSonnetOpus
Subagent, applyOpusSonnet
Subagent, showOpusOpus

So turn and session moved only the main session, subagent moved only the helper, and show moved nothing.

The two subagent runs make a pair. They used the same prompt and the same settings except the mode, and their helpers sent their requests one second apart. The helper that stayed on Opus read 31,090 tokens from the cache and wrote none. The helper that was switched to Sonnet read none and wrote 31,098.

That doesn't mean only a switch writes cache. In an earlier batch where every request stayed on Opus, helpers also wrote about 31,090 tokens when no matching cache existed yet. These are single runs, not a controlled comparison.

No real data on success yet

spatz stores each suggestion with its scope, so its stats command can compare the scopes later. A read-only count of the local spatz database on 2026-10-05 found 14 suggestions with a scope. Eight are the test rows from the five runs above. The other six are real: subagent suggestions from my own use of the mod. Six real suggestions, all in one scope, are far too few to compare scopes. Which scope picks better models is pending.

Why hooks after all

The mod can switch, but for now I've turned it off and put the work into hooks. Two things decided it.

First, a switch can surprise you. In the session that ran my benchmark, at least four helpers that I had started on a fixed model reported that another model answered, while apply was on. If the model you asked for can change without notice, it's hard to trust any result from that session.

Second, a recommender only learns when a suggestion gets an outcome. A mod runs only in Claude Code, but Codex has hooks too. So spatz now has one contract for hook events and a small adapter for each tool. When a helper or a session ends, its hook closes every suggestion of that run that has no outcome yet. Without a reported result, the outcome is stored as unknown, which counts as neither a pass nor a fail. The stats then show how many suggestions got an outcome. (spatz pull request #101 (opens in a new tab), merged on 2026-10-07)

Support for more coding agents is planned. Whether the mod comes back to switch the main session is open.

Back to top