Skip to content

fix(agent): per-provider default subagent models; refresh retired Anthropic ids - #832

Merged
ericleepi314 merged 1 commit into
mainfrom
fix/subagent-default-models
Aug 12, 2026
Merged

fix(agent): per-provider default subagent models; refresh retired Anthropic ids#832
ericleepi314 merged 1 commit into
mainfrom
fix/subagent-default-models

Conversation

@ericleepi314

Copy link
Copy Markdown
Collaborator

Problem

Subagents resolved their models through a stale global alias table (haikuclaude-3-5-haiku-20241022, sonnetclaude-sonnet-4-20250514). Both ids have been retired from the live Anthropic API (verified 2026-08-12 against GET /v1/models), so:

  • Every Explore spawn on an Anthropic session died with 404 not_found_error — and the natural retry with model: "sonnet" died the same way (the goal screenshot: seven parallel verification agents, all 404).
  • On DeepSeek sessions, haiku resolved to nothing servable and fell back to inherit, so every subagent fan-out silently ran (and billed) the expensive deepseek-v4-pro session 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 through Provider.getSmallModel + the small_model config knob. Each provider's PROVIDER_INFO row may now declare:

provider subagent_model (unspecified default) subagent_tier_models
anthropic claude-haiku-4-5 (cheapest current tier, $1/$5) fable/opus/sonnet → claude-{fable,opus,sonnet}-5, haiku → claude-haiku-4-5
deepseek deepseek-v4-flash opus → v4-pro, sonnet/haiku → v4-flash

get_agent_model precedence: CLAUDE_CODE_SUBAGENT_MODEL env > tool model param > agent-def model: > provider default subagent model > inherit.

  • Explicit inherit always 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 as spec.model or agent_definition.model or "inherit" so agent() calls keep the documented "inherits the main-loop model" contract without clobbering an agentType agent's declared tier.
  • The unspecified-model default is a deliberate divergence from both references (they inherit), per explicit user directive: fan-out subagents default to the provider's cheap tier. Escape hatches: model: inherit in a def or tool call, providers.<id>.subagent_model in config (an alias or inherit resolves like any user pin; providers is an untrusted-tier-blocked config key, so a committable repo config can't hijack it), or CLAUDE_CODE_SUBAGENT_MODEL.
  • Same-tier-alias-keeps-parent precedes the table (a claude-sonnet-4-6 session asked for sonnet keeps 4-6). ANTHROPIC_DEFAULT_{OPUS,SONNET,HAIKU}_MODEL pins 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 for haiku/sonnet (TS checkIsClaudeNativeProvider parity, incl. its opus asymmetry).
  • Alias spellings are never shipped raw: known aliases resolve to their canonical target and take the availability gate (degrading to inherit, never a 404); only ids no alias table knows are trusted literally (proxy deployment names).
  • Routing is visible: the Agent tool result and task registry carry the resolved model, the agent-progress emits (including terminal ones) carry it, and the TS bridge forwards it so the /agents overlay shows the real model instead of inherit.

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)

  • aliases: sonnet → claude-sonnet-5, opus → claude-opus-5, haikuclaude-haiku-4-5 (bare id resolves server-side — probed), new fable; 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.
  • registry: claude-sonnet-5 (1M ctx per docs, 128K output cap probed live, permanent $2/$10 launch pricing — a new tier, not 3/15) and claude-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.
  • availability catalogs (both PROVIDER_INFO and AnthropicProvider.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.
  • Stale side-call defaults refreshed: settings (model → claude-sonnet-4-6, small_fast_model → claude-haiku-4-5), fast-mode, session-title/rename side calls, exec-agent-hook, count-tokens.
  • Agent tool model enum: ["sonnet", "opus", "haiku", "fable", "inherit"], with the description noting the default is a fast/inexpensive tier and inherit is the full-capability escape.
  • Deleted src/models/agent_routing.py — a dead parallel resolver whose inherit-parent semantics (and passing tests) contradicted the shipped path.

Verification

  • Live DeepSeek e2e: real headless run; session per-model usage shows the main loop on deepseek-v4-pro and the Explore subagent on deepseek-v4-flash ($0.0014 for the subagent turn).
  • Live Anthropic probes: /v1/models catalog (old ids gone, new ids served), output caps read off the API's own 400s, bare claude-haiku-4-5 proven to resolve server-side. (A full Anthropic e2e was blocked by an account extra-usage quota gate — a billing 400, not a model 404.)
  • Full suite: 10052 passed, 0 failed, 12 skipped; ui-tui vitest 91 passed; tsc --noEmit clean.
  • Critic loop: six adversarial review rounds to APPROVE. They caught real bugs at every stage — the config knob shipping alias spellings/inherit verbatim 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 claim inherit, 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-test TestPerProviderSubagentDefaults, runner slot test, bridge test, reporting tests, invariant test).

Follow-ups (deliberately out of scope)

  • models/bedrock.py has no Claude 5-era rows (bedrock routing untouched by this path).
  • The workflow journal records spec.model (often None) rather than the resolved model.
  • LocalAgentTaskState.model is recorded but no backend endpoint serves it yet (the TUI type DelegationStatusResponse.active[].model is ready for it).
  • Main-session default model stays claude-sonnet-4-6 (live, works); bumping it to sonnet-5 is a product decision.

🤖 Generated with Claude Code

…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>
@ericleepi314

Copy link
Copy Markdown
Collaborator Author

CI note: the test (ubuntu-latest, 3.11) failure is test_opencode_compat_providers.py::test_xai_requests_go_to_chat_completions — no HTTP request was attempted, which is pre-existing on main: the identical failure appears on merged PR #830's run (main's current HEAD, run 31571128350) and is an openai-v3-in-CI environment issue with a dedicated fix already in flight (fix/ci-windows-and-openai3). Local full suite on this branch: 10052 passed, 0 failed (4 consecutive runs). The Windows legs are the known pre-existing failures.

@ericleepi314
ericleepi314 merged commit 7e973d3 into main Aug 12, 2026
3 of 6 checks passed
@ericleepi314
ericleepi314 deleted the fix/subagent-default-models branch August 12, 2026 11:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant