Skip to content

feat(config): model roles — semantic indirection between phase policy/ladders and concrete model strings #975

Description

@admarble

Blocked by: #974 (role shape spike), #973 (driver is_error fix — supplies the loud-failure contract)

Why

Model rosters churn faster than config. Three surfaces currently bake the concrete roster into sequant: the values users write into run.phases.<phase>.model (#914), the proposed run.modelLadder (#971, currently exampled as ["sonnet","opus","fable"]), and the stale agents.model enum + "opus = most capable" doc (settings.ts:389, settings.ts:52 — both already wrong post-Fable). Every roster change invalidates user settings, skill docs, and memory files that name models directly.

The fix is one level of indirection: config expresses roles; a single map resolves roles to model strings; everything else (phase policy, ladder, benchmark policies) references roles and survives roster changes untouched.

Phase-0 findings this design builds on (model-string contract spike, 2026-08-28, Claude Code 2.1.251)

  1. Family aliases already track releases: --model sonnet resolved to claude-sonnet-5, haikuclaude-haiku-4-5-20251001, fableclaude-fable-5 (observed via modelUsage keys). Shipped role defaults should therefore be aliases, never dated IDs — point releases then need no map update at all.
  2. An unrecognized model does NOT silently fall back — the API 404s (unrecognized_model), nothing runs on a wrong model. But the failure is currently swallowed by the driver (result subtype:"success" + is_error:true → reported as phase success, fix(driver): result subtype "success" with is_error:true is reported as phase success — unrecognized model yields a zero-work "successful" phase #973). With fix(driver): result subtype "success" with is_error:true is reported as phase success — unrecognized model yields a zero-work "successful" phase #973 fixed, a bad resolution fails loudly at dispatch, which means the role map can stay thin — no roster-validation layer in sequant.
  3. The resolved concrete model is observable per run in the SDK result's modelUsage keys — so metrics can record ground truth without sequant maintaining a roster.

Design

Not in scope

Acceptance Criteria

Done when

A roster change (new family, renamed tier) requires editing only the modelRoles map — no change to phase policy, ladder config, skills, or docs — and a stale map fails loudly instead of silently succeeding.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestplannedImplementation plan approvedready-for-reviewReady for code review

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions