Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ For local development: clone the repo and
plan offers). The plugin never changes it - the main session is where
planning and decisions happen, so give it the strongest tier you are
willing to pay for. Session effort: left unset, you get the model's own
default - high on most models, xhigh on Opus 4.7, and some models have no
default - high on every model that has the knob, and some models have no
effort knob at all (see [the effort ladder](#model-tiers-and-effort-ladder));
dropping the session to medium is the cost-conscious pick when the main
session mostly coordinates - the bundled agents pin their own either way.
Expand Down Expand Up @@ -342,10 +342,12 @@ the fable-class tier is built for long-horizon frontier work - a
session-model choice, not a dispatch target.

The effort ladder - the second knob. Unset effort means `high` on every
model that supports effort, the one exception being Opus 4.7, which
defaults to `xhigh`. Support itself is an explicit list rather than a
version cutoff: Fable 5, Opus 5, Sonnet 5, Opus 4.8 and Opus 4.7 take the
whole ladder, Opus 4.6 and Sonnet 4.6 take everything but `xhigh`, and a
model that supports effort, with no exception: Opus 4.7 and 4.8 recommend
starting at `xhigh` for coding and agentic work, but a recommendation is
a value you pass, not the level that runs when you pass nothing. Support
itself is an explicit list rather than a
version cutoff: Fable 5, Mythos 5, Opus 5, Sonnet 5, Opus 4.8 and Opus 4.7
take the whole ladder, Opus 4.6 and Sonnet 4.6 take everything but `xhigh`, and a
model absent from that list - Haiku 4.5 among them - has no effort knob at
all. Setting a level a model does not support runs the highest supported
level at or below it, so `xhigh` becomes `high` on Opus 4.6. The vendor
Expand Down
14 changes: 9 additions & 5 deletions hooks/dispatch-counter.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,16 @@ const EFFORT_SUPPORT = [
const EFFORT_LADDER = ["low", "medium", "high", "xhigh", "max"];
const effortLevelsFor = (m) => (m ? EFFORT_SUPPORT.find(([re]) => re.test(m))?.[1] ?? null : null);

// "The default effort is high on every model that supports effort, except Opus
// 4.7, which defaults to xhigh." No support, no default - an unlisted or
// unrecognized session model never receives a fabricated level.
// "The API default is high" - on every model that supports effort, with no
// exception. Opus 4.7 and 4.8 RECOMMEND starting at xhigh for coding and
// agentic work, and this table used to record that recommendation as if it were
// the default, which mislabelled every unset 4.7 session as xhigh when it ran
// high. A recommendation is what you should pass; a default is what runs when
// you pass nothing, and only the second one can be inferred from an empty
// config. No support, no default - an unlisted or unrecognized session model
// never receives a fabricated level.
function defaultEffortFor(sessionModel) {
if (!effortLevelsFor(sessionModel)) return null;
return /opus-4-7/.test(sessionModel) ? "xhigh" : "high";
return effortLevelsFor(sessionModel) ? "high" : null;
}

// "If you set a level the active model does not support, Claude Code falls back
Expand Down
9 changes: 7 additions & 2 deletions hooks/dispatch-counter.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -824,9 +824,14 @@ test("an unset effortLevel records the documented model default", () => {
const opus5 = dispatchWithSettings({ sessionModel: "claude-opus-5" });
assert.equal(opus5.effort, "high");
assert.equal(opus5.effortFrom, "default");
// Opus 4.7 is the documented exception.
// Opus 4.7 is NOT an exception, however much its guidance reads like one.
// The docs recommend starting it at xhigh for coding and agentic work and
// state in the same breath that "the API default is high". Recording the
// recommendation here labelled every unset 4.7 session with a level it never
// ran, which is the one thing this reconstruction must not do.
const opus47 = dispatchWithSettings({ sessionModel: "claude-opus-4-7" });
assert.equal(opus47.effort, "xhigh");
assert.equal(opus47.effort, "high");
assert.equal(opus47.effortFrom, "default");
});

test("no default is invented for a model absent from the support table", () => {
Expand Down
2 changes: 1 addition & 1 deletion hooks/routing-anchor.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ MODEL ROUTING ACTIVE - expensive model thinks, cheap models grind. You cannot sw
- Dispatching implementation work with no approved plan behind it: have the agent return its PLAN first and check it in the main session before it writes code - one short turn beats finding the wrong approach in a finished diff (the advisor pattern). With a plan already approved, dispatch straight to implementation.
- A subagent stuck on the approach hands back for a decision; continue the same agent (SendMessage when available, else re-dispatch with its packaged state).
- A failed or weak subagent RESULT retries exactly one step up (next tier or higher effort), fresh dispatch; a second failure goes to the main session.
- Pins are ceilings, not floors: when a pin sits above the session model, cap the dispatch at the session model via the Agent `model` param. The pin alone does NOT cap - a bare dispatch runs the pinned model.
- Against the session model a pin is a ceiling: when a pin sits above the session model, cap the dispatch at the session model via the Agent `model` param. The pin alone does NOT cap - a bare dispatch runs the pinned model. It is also a floor, at the LOWER of the pin and the session model: capping an opus-pinned agent to a sonnet session is correct, pushing it to haiku is not.
- Unpinned agents (general-purpose, custom types) silently inherit the session model - make the tier a conscious choice: explicit `model` for mechanical or exploratory work (sonnet; haiku for trivial sweeps), session tier only when the task needs that reasoning.
- Same rule inside Workflow scripts: every `agent()` call without `model`/`effort` opts inherits the session model at session effort - set them per call, cheap stages low, top tier only where the stage earns it.
- Repo-specific policies override this routing. If an entire session is one phase, suggest the user switch /model instead.
50 changes: 43 additions & 7 deletions skills/model-routing/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ of `max` and still clear a task that was never hard - a strong model
thinking lightly often beats a weaker model thinking hard. Pick both:
which model, and how hard it thinks.

The full ladder is `low / medium / high / xhigh / max`. On current
models the default is `high` - an unset effort IS high effort, not
medium - with one documented exception: Opus 4.7 defaults to `xhigh`.
The full ladder is `low / medium / high / xhigh / max`. On every model
that supports effort the default is `high` - an unset effort IS high
effort, not medium, and there is no exception. Opus 4.7 and 4.8 are
often misread as one: they RECOMMEND starting at `xhigh` for coding and
agentic work, which is a value you have to pass, not what runs when you
pass nothing.
Which levels exist at all is a per-model list rather than a version
cutoff, and setting a level the model does not support runs the highest
supported level at or below it. The per-model recommendation moves with the generation: Opus
Expand Down Expand Up @@ -154,6 +157,33 @@ actually earns its cost:
low/medium punch well above their weight - when a dispatch feels too
expensive, step the EFFORT down before the tier; when a result is too
shallow, step effort up before tier up.
- **The fable-to-opus price gap is exactly the sticker.** The documented
~30% token inflation is measured against models from BEFORE Opus 4.7,
which is the generation whose tokenizer Fable 5 uses - it is not a gap
between Fable and opus, and the model table lists the same token
density for both. So budget the fable-to-opus gap as the sticker 2x
rather than something wider.
Where the inflation does bite is any comparison against a Sonnet
4.6-era baseline: re-pricing today's token counts at yesterday's rates
understates the difference.
- **Step effort DOWN on Fable before stepping the tier down.** The
documented guidance for Fable is to start at `high` (the default),
use `xhigh` only for the most capability-sensitive work, and step down
to `medium` or `low` for routine work - lower effort on Fable still
performs well and often exceeds `xhigh` on prior models. So effort is
a real control on a fable session and not a rounding error: a routine
phase left at the default pays top-tier rates for depth it did not
need. It does not replace the tier decision - dispatching that phase
to sonnet is cheaper still. Sweep Workflow `effort` opts the same way.
- **A refusal is a redirect, not a weak result.** Fable 5 and Opus 5
both ship safety classifiers that can decline a request outright
rather than answer it badly. A declined dispatch is the one failure
the escalation ladder below does not fix: the documented remedy is
another model family, and opus is the named destination for a declined
fable request - a step sideways, not up. The machinery underneath
(stop reasons, which classifier declined, fallback credit) is API-level
and invisible from inside a dispatch, so this is the whole of the
routing rule.

Research backing: task-type routing outperforms complexity-score routing
(RouteLLM, ICLR 2025); benchmark tier gaps confirm sonnet as the
Expand Down Expand Up @@ -243,8 +273,14 @@ actually ran with `/model-routing:stats`.
take it to the main session. Distinguish this from the stuck-on-approach
handback above: stuck agents hand back BEFORE producing a result and
continue via SendMessage; failed results re-dispatch fresh one tier up,
because the failed attempt's context is part of the problem.
- A pin is also a FLOOR, and undercutting it is not a saving. The pin states
because the failed attempt's context is part of the problem. One case
is not on this ladder at all: a `stop_reason: "refusal"` is a
classifier declining rather than a model falling short, and it has its
own retry path - see the Fable caveats above.
- Against a manual override, the same pin is a FLOOR, and undercutting it is
not a saving. The two readings do not conflict because they answer different
questions: the ceiling asks what the session should pay, the floor asks what
the role needs. The pin states
how much reasoning the role needs, so `reviewer` dispatched with
`model=haiku` is a weaker review rather than a cheaper one, and it still
counts as "cheaper than the session" in every cost figure - which is exactly
Expand All @@ -255,8 +291,8 @@ actually ran with `/model-routing:stats`.
the cheap tier genuinely fits the work, pick an agent pinned for it
(`test-runner`, `verifier`) instead of overriding a role agent downward; the
dispatch report lists below-pin dispatches in their own section.
- Agent pins are ceilings, not floors. A pin says "this task never needs
more than X"; the session model says what the user is willing to pay.
- Against the session model, a pin is a ceiling. A pin says "this task never
needs more than X"; the session model says what the user is willing to pay.
When a pin sits above the session model, cap the dispatch at the
session model via the Agent `model` param - on a sonnet session,
implementer and reviewer run on sonnet. This cap is behavioral, not
Expand Down
Loading