feat(config)!: every model is a role: [models.<role>] replaces the fixed role slots and [models.named] - #125
feat(config)!: every model is a role: [models.<role>] replaces the fixed role slots and [models.named]#125tylerdavis wants to merge 3 commits into
Conversation
…xed role slots and [models.named] Standard roles (chat, planner, solver, repair, judge) and custom roles now share one config shape and one namespace under [models]. A standard role falls back to default; a custom role is any other key, selectable by name from prompt tools, builtin__infer, and infer gates, and never falls back. The planner catalog advertises every role carrying a description. The unused use_case_solver and embedder roles and the dimensions key are gone. BREAKING CHANGE: config version 2 (graph config migrate)
|
Note graph_review_9000 · A config-model refactor collapsing fixed model-role slots and At a glance
🧵 0 new · 0 open · 0 resolved this run · 0 declined 🟡 Needs verification / intentional (1)Repo Conventions/Docs Parity — Docs were updated extensively across models/config/plan-schema pages in the same change, consistent with CLAUDE.md's docs-parity rule; the repo's own ⚪ N/A · ✅ PassStated Intent — The diff matches the description closely: ModelRoles becomes a transparent map, RESERVED_MODEL_NAMES/named-namespace are removed, CONFIG_FORMAT bumps to 2 with a migration, and docs/fixtures/tests are updated in step. Pipeline/Control-Step Invariants — No changes to gate grammar, body semantics, execution-gate dispatch, or replan/error-policy code paths beyond the model-name plumbing (Option<&str> model field passed through unchanged); control-step invariants are untouched. Template/Shape Engine — Diff does not touch the template engine, shapes.rs, or the shape cache. Rust/Async/Storage Footguns — No unwrap/panic added on reachable paths; the toml_edit migration logic uses Option-returning early exits and Result propagation throughout format.rs. Plan Composition/Model Routing — Verified via dossiers: Test Coverage — New migration behavior (hoisting, collision refusal, dimensions drop, retired-role note, inline/table forms) each has a dedicated test in format.rs, and the new ModelRoles API (resolve/resolve_role/described/known_names) is exercised in load.rs tests. CLI Surface/Copy — Updated CLI starter config comments and tool/gate description strings read clearly and consistently; no stdout/stderr or exit-code changes are present in the diff. |
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
…nd runs on the reviewer role The judge-role model answered the old two-clause question with yes on a PR whose bump it had itself described as accounting for the change, and after reordering it still misread description-string edits in the pipeline files as a plan-schema change. The question now decides per file kind with the bump checked first, and the verdict runs on the reviewer role.
…, note retired roles, and list only resolvable names in the unknown-role error Review follow-ups on PR #125: the migration now carries the decor of an explicit [models.named] header and of inline entries under it, notes embedder and use_case_solver as retired, and its collision error names the file. known_names() drops standard roles that would not resolve without a default. The format_drift gate treats plan and tool documents as separate kinds. Stale embedder and named-model wording in the review plan and the models page is updated, and two authored test comments are removed.
What changes
Model configuration had two shapes for one thing: fixed struct slots at
[models.<role>]for the standard roles and a separate[models.named.<name>]namespace for user-defined entries, kept apart by a reserved-name check. This makes every model a role with one shape and one namespace.default,chat,planner,solver,repair,judge) are the keys the engine consults. Each falls back todefault.model,builtin__infer'smodelinput, and aninfergate'smodeloverride. A custom role never falls back: an unknown name fails the call listing what is configured.builtin__inferadvertises every role that carries adescription. The description is the opt-in and the routing signal; a role without one stays selectable by name but is not offered to the planner.default = { ... }) and block ([models.default]) spellings are the same TOML table. Docs, thegraph config inittemplate, and the fixtures now use the block form everywhere.Config version 2
CONFIG_FORMATis 2; the window is 1..=2.models.named.*entry tomodels.*(header comments travel with the entry; inlinenamed = { ... }is handled too), refuses a v1 file wheremodels.named.Xandmodels.Xare both set, and dropsdimensionswith a note.use_case_solver,embedder, anddimensionswere never resolved by any call site and are gone from the model. Anembedderentry in an existing file still loads as a plain role.Code
graph-config:ModelRolesis a transparentBTreeMap<String, ModelChoice>newtype withget,resolve,resolve_role,described,known_names.RESERVED_MODEL_NAMESand the load-time shadowing check are gone.Rolekeeps the five live variants plusas_str/from_name/ALL.graph-llm:ModelRouter::resolve_namedresolves through the one map;named_models()becomesdescribed_models().graph-core: thebuiltin__infercatalog enumerates described roles; planner-facing field descriptions andcontrol_step_rules.mdsay "role".Verified
mise run lintandmise run testpass.[models.named.scout], an inlineembedderwithdimensions, and a comment above the named entry migrates withgraph config migrateto[models.scout]under its comment,dimensionsdropped with the note printed, andgraph versionreportsconfig 2 (reads 1-2).graph plan run graph_review_core --input base=main: no findings, no blockers.Not in this PR
The repo's own
.graph/config.tomlstays at version 1 with[models.named]on purpose: thedogfood-filesjob validates it inside the pinned v0.13.0 image, which reads only config 1. After this ships in a release, aci:PR moves the image pin and re-stamps.graph/in block form.BREAKING CHANGE: config version 2 (graph config migrate)