8. Use effort bands instead of model names in agent frontmatter¶
Date: 2026-06-21
Status¶
Amends 4. Pre-dispatch model tier resolution enforced by a PreToolUse hook
Superceded by 26. Adopt native model:/effort: agent frontmatter, retire the band resolver
Context¶
ADR 0004 established that each agent
declares a tier alias (haiku, sonnet, opus) in its model: frontmatter,
and a PreToolUse hook resolves that alias to a concrete snapshot before
dispatch. The enforcement topology works. The vocabulary does not.
-
The alias leaks the vendor's model lineup. Naming the field
model:and its valueshaiku/sonnet/opusbakes Anthropic's current three-tier naming into ~33 agent files plus templates. A different environment — a proxy with only two models, a single-model endpoint, a future four-model lineup — cannot be expressed without re-editing every agent. -
The abstraction is already half-leaked and inconsistent.
/agent-addand/agent-createalready accept--tier small|mid|frontier, and/agent-auditdeclares the valid values aresmall|mid|frontier— yet the resolver's allowlist only acceptshaiku|sonnet|opus, andagents/test-modernization-review.mdshipsmodel: mid, which the resolver rejects (the hook then fail-opens and dispatches the literal string"mid"as a model ID). The contradiction is a direct symptom of binding frontmatter to vendor model names. -
The fallback target was a fixed tier, not the user's actual session. When a requested model is unavailable, the right fallback is the model the user is already running this session — not a hardcoded
haiku → sonnet → opuscascade, and not a network probe of the provider's model list (which only worked on Anthropic-shape endpoints).
What an agent actually wants to express is how much reasoning effort its task needs — a relative, vendor-neutral property of the task — and let the environment decide which model serves that effort.
The full mechanism (the environment model ladder, the effort→ladder mapping, the session-model fallback, and the SessionStart banner) was documented in the plugin's model-routing reference. This ADR records the decision; that reference recorded the mechanism.
Retirement note (2026-07-22): ADR 0026 retired the mechanism this ADR describes — the model-routing reference it pointed to no longer exists. This ADR is preserved verbatim as a historical record.
Decision¶
Replace the vendor-named model: tier alias with a vendor-neutral effort
band in agent frontmatter.
- Each agent declares
effort: low | medium | high— a statement about the task's reasoning demand, not about any model. - A per-environment, capability-ordered model ladder
(
.claude/model-ladder.json, hand-written) maps effort bands to the concrete models that exist in that environment. Absent a ladder, effort bands resolve via the shipped default map inknowledge/model-routing.json(low→haiku, medium→sonnet, high→opus) — the identical mapping to before the rename, not the session model. - The model the user selected at session start is the fallback when an effort
band cannot be mapped, and the reference point for flagging upgrades. It is
not a ceiling: a
highagent runs on the top of the ladder even when the session started lower, and the SessionStart banner announces that. - The opt-in network model-availability probe is removed; availability comes from the ladder.
- The resolver accepts the legacy
haiku/sonnet/opusnames for one deprecation release (mappedhaiku→low,sonnet→medium,opus→high, warned by/agent-audit), then drops them at the next major. Everything the plugin ships migrates toeffort:immediately.
Every agent — current and future — must declare a valid effort: band. This is
enforced by a structural test (tests/agents/agent_effort_frontmatter_tests.bats)
that fails CI if any agent file is missing the field or carries a value outside
the allowed set, so the contract cannot silently rot the way model: mid did.
Consequences¶
Easier:
- Agents are portable across providers and model lineups; a new environment is
described once in
.claude/model-ladder.json, never by re-editing agents. - Snapshot churn is contained to the ladder and
knowledge/model-routing.json, not scattered across frontmatter. - The
model: midlatent bug is fixed as a side effect, and thesmall/mid/frontier↔haiku/sonnet/opussplit is unified into one vocabulary. - Fallback is to a model the user is provably already running, so dispatch cannot fail for lack of a reachable model.
Harder / risks:
- Breaking change for downstream agent authors. Anyone who wrote an agent
with
model: sonnetmust migrate toeffort: medium. Mitigated by the one-release legacy-acceptance window and afeat!major version bump with a migration note. - Atomicity. Frontmatter, resolver, and tests must change together or a drift window opens where resolution fails. Mitigated by landing the rename, resolver change, and the effort-frontmatter test in one change set.
- Effort is coarse (three bands). A future four-model ladder may want a fourth band; the mapping rounds an effort band to a ladder position, so the band set and ladder length can drift apart. Mitigated by keeping the band set small and explicit, and revisiting only when a 4+ model ladder is common.
- Upgrade cost visibility. Because the session model is not a ceiling, a
highagent can run above the session model. Mitigated by the SessionStart banner flagging every upgrade; deferred decision on whether to also warn per-dispatch.
Amendment (2026-07-20)¶
The effort-band routing this ADR introduces is enforced by the PreToolUse
model-resolution hook it inherits from
ADR 0004: the hook is what maps a
declared effort: band to a concrete model before dispatch. That enforcement
guarantee was inert as shipped. The plugin's PreToolUse hook layer did not
load at all on current Claude Code, so the resolver never ran and effort bands
were never mapped pre-dispatch — agents ran on the session model regardless of
their declared band. This is the finding
ADR 0022 recorded in passing
while running the #1099 orchestration benchmark: "the shipped hook layer
(including effort-band routing) does not load at all on current Claude Code
(#1178) … Shipped band-routing economics had never actually been exercised."
The gap was closed in v10.12.1 ("load plugin hooks via hooks/hooks.json …
closes #1178"); before that fix, the vendor-neutral band vocabulary was correct
but had no working enforcer. See #1178 and ADR 0022.