fix(agent): per-provider default subagent models; refresh retired Anthropic ids - #832
Merged
Merged
Conversation
…hropic ids
Subagents resolved models through a stale global alias table (haiku ->
claude-3-5-haiku-20241022, sonnet -> claude-sonnet-4-20250514) whose
targets were retired from the live API: every Explore spawn on an
Anthropic session 404'd, and DeepSeek fan-outs silently inherited (and
billed) the expensive session model.
Port the reference design (TS getDefault{Opus,Sonnet,Haiku}Model per
provider; opencode Provider.getSmallModel + small_model knob):
- PROVIDER_INFO rows may declare subagent_model (unspecified default)
and subagent_tier_models (bare-alias targets). anthropic: default
claude-haiku-4-5 (cheapest current-gen tier; the bare id resolves
server-side to the dated snapshot, probed live), tiers
fable/opus/sonnet -> claude-{fable,opus,sonnet}-5; deepseek: default
deepseek-v4-flash, opus tier -> deepseek-v4-pro.
- get_agent_model precedence: CLAUDE_CODE_SUBAGENT_MODEL env > tool
param > agent-def model > provider default > inherit; explicit
'inherit' always wins; same-tier alias keeps the parent's exact
model; ANTHROPIC_DEFAULT_*_MODEL pins honored; custom Anthropic
endpoints disable the table (TS checkIsClaudeNativeProvider parity);
providers.<id>.subagent_model config knob trusted literally; registry
values availability-gated so a stale row degrades to inherit.
- BaseProvider.provider_id identity attr on all provider classes.
- Alias/table refresh: sonnet/opus/haiku/fable -> Claude 5-era ids;
claude-sonnet-5 registered (1M ctx, 128K output cap probed live,
permanent 2/10 pricing tier) and claude-haiku-4-5-20251001 (200K ctx
and 64K cap probed live); availability lists + stale side-call
defaults (settings, fast mode, titles, hooks, count_tokens) updated.
- Agent tool model enum gains fable + inherit.
Verified live on DeepSeek e2e: main loop on v4-pro, Explore subagent on
v4-flash (session per-model usage). Full suite: 10043 passed, 0 failed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Collaborator
Author
|
CI note: the |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Subagents resolved their models through a stale global alias table (
haiku→claude-3-5-haiku-20241022,sonnet→claude-sonnet-4-20250514). Both ids have been retired from the live Anthropic API (verified 2026-08-12 againstGET /v1/models), so:404 not_found_error— and the natural retry withmodel: "sonnet"died the same way (the goal screenshot: seven parallel verification agents, all 404).haikuresolved to nothing servable and fell back to inherit, so every subagent fan-out silently ran (and billed) the expensivedeepseek-v4-prosession model.Fix — per-provider subagent defaults (reference-project mechanism)
Both references resolve subagent models per provider: TS Claude Code through its
getDefault{Opus,Sonnet,Haiku}Model()functions, opencode throughProvider.getSmallModel+ thesmall_modelconfig knob. Each provider'sPROVIDER_INFOrow may now declare:subagent_model(unspecified default)subagent_tier_modelsclaude-haiku-4-5(cheapest current tier, $1/$5)claude-haiku-4-5deepseek-v4-flashget_agent_modelprecedence:CLAUDE_CODE_SUBAGENT_MODELenv > toolmodelparam > agent-defmodel:> provider default subagent model > inherit.inheritalways forces the session model — Plan and fork agents pin it, the coordinator path pins it at the tool layer (workers can't pass a model param and do implementation work), and the workflow runner fills its slot asspec.model or agent_definition.model or "inherit"soagent()calls keep the documented "inherits the main-loop model" contract without clobbering anagentTypeagent's declared tier.model: inheritin a def or tool call,providers.<id>.subagent_modelin config (an alias orinheritresolves like any user pin;providersis an untrusted-tier-blocked config key, so a committable repo config can't hijack it), orCLAUDE_CODE_SUBAGENT_MODEL.claude-sonnet-4-6session asked forsonnetkeeps 4-6).ANTHROPIC_DEFAULT_{OPUS,SONNET,HAIKU}_MODELpins are honored on the anthropic provider only (a pin must not leak onto another vendor's wire). Custom Anthropic endpoints disable the table and inherit forhaiku/sonnet(TScheckIsClaudeNativeProviderparity, incl. its opus asymmetry).model, the agent-progress emits (including terminal ones) carry it, and the TS bridge forwards it so the/agentsoverlay shows the real model instead ofinherit.BaseProvider.provider_id(new, set on all concrete + registry-generated classes) keys the lookup; fusion wrappers are unwrapped first.Model-table refresh (same bug class)
sonnet→ claude-sonnet-5,opus→ claude-opus-5,haiku→claude-haiku-4-5(bare id resolves server-side — probed), newfable;claude-4-sonnet/s4→ claude-sonnet-4-6,claude-4-opus/o4→ claude-opus-4-8 (newest live 4.x); explicit legacy pins (claude-3.5-haiku,h35) kept and now degrade to inherit for subagents via the gate.claude-sonnet-5(1M ctx per docs, 128K output cap probed live, permanent $2/$10 launch pricing — a new tier, not 3/15) andclaude-haiku-4-5-20251001(200K window and 64K output cap probed live; prefix base covers the bare spelling). Prefix-fallback placement preserved — unknown 4.x sonnets keep the conservative 200K row.PROVIDER_INFOandAnthropicProvider.get_available_models) pruned to the live 2026-08-12 catalog + the probed bare haiku id: a listed-but-dead id turns the gate's "degrade to inherit" into a shipped 404. An invariant test pins every subagent target against both lists.model→ claude-sonnet-4-6,small_fast_model→ claude-haiku-4-5), fast-mode, session-title/rename side calls, exec-agent-hook, count-tokens.modelenum:["sonnet", "opus", "haiku", "fable", "inherit"], with the description noting the default is a fast/inexpensive tier andinheritis the full-capability escape.src/models/agent_routing.py— a dead parallel resolver whose inherit-parent semantics (and passing tests) contradicted the shipped path.Verification
deepseek-v4-proand the Explore subagent ondeepseek-v4-flash($0.0014 for the subagent turn)./v1/modelscatalog (old ids gone, new ids served), output caps read off the API's own 400s, bareclaude-haiku-4-5proven to resolve server-side. (A full Anthropic e2e was blocked by an account extra-usage quota gate — a billing 400, not a model 404.)tsc --noEmitclean.inheritverbatim onto the wire, the workflow runner first taking the cheap default then (in my fix) clobbering agent-def tiers, non-family alias spellings trusted raw, the TS bridge dropping the model field so the overlay would claiminherit, retired ids surviving in the availability catalogs, coordinator workers with no opt-out, and cross-provider env-pin leakage. Each fix is pinned by a test (18-testTestPerProviderSubagentDefaults, runner slot test, bridge test, reporting tests, invariant test).Follow-ups (deliberately out of scope)
models/bedrock.pyhas no Claude 5-era rows (bedrock routing untouched by this path).spec.model(often None) rather than the resolved model.LocalAgentTaskState.modelis recorded but no backend endpoint serves it yet (the TUI typeDelegationStatusResponse.active[].modelis ready for it).claude-sonnet-4-6(live, works); bumping it to sonnet-5 is a product decision.🤖 Generated with Claude Code