Skip to content

feat(config)!: every model is a role: [models.<role>] replaces the fixed role slots and [models.named] - #125

Open
tylerdavis wants to merge 3 commits into
mainfrom
unify-model-roles
Open

feat(config)!: every model is a role: [models.<role>] replaces the fixed role slots and [models.named]#125
tylerdavis wants to merge 3 commits into
mainfrom
unify-model-roles

Conversation

@tylerdavis

Copy link
Copy Markdown
Owner

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.

[models.default]
provider = "anthropic"
model = "claude-sonnet-5"

[models.judge]
provider = "anthropic"
model = "claude-haiku-4-5"

[models.scout]
provider = "anthropic"
model = "claude-haiku-4-5"
description = "Repository context scout: many parallel grep/read rounds over a whole repo."
  • Standard roles (default, chat, planner, solver, repair, judge) are the keys the engine consults. Each falls back to default.
  • Custom roles are any other key. Same keys, selectable by name from a prompt tool's model, builtin__infer's model input, and an infer gate's model override. A custom role never falls back: an unknown name fails the call listing what is configured.
  • Planner catalog: builtin__infer advertises every role that carries a description. 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.
  • The inline (default = { ... }) and block ([models.default]) spellings are the same TOML table. Docs, the graph config init template, and the fixtures now use the block form everywhere.

Config version 2

  • CONFIG_FORMAT is 2; the window is 1..=2.
  • Migration hoists every models.named.* entry to models.* (header comments travel with the entry; inline named = { ... } is handled too), refuses a v1 file where models.named.X and models.X are both set, and drops dimensions with a note.
  • use_case_solver, embedder, and dimensions were never resolved by any call site and are gone from the model. An embedder entry in an existing file still loads as a plain role.
  • Frozen v2 twins for all three v1 fixtures; the golden-pair test proves they load identically.

Code

  • graph-config: ModelRoles is a transparent BTreeMap<String, ModelChoice> newtype with get, resolve, resolve_role, described, known_names. RESERVED_MODEL_NAMES and the load-time shadowing check are gone. Role keeps the five live variants plus as_str/from_name/ALL.
  • graph-llm: ModelRouter::resolve_named resolves through the one map; named_models() becomes described_models().
  • graph-core: the builtin__infer catalog enumerates described roles; planner-facing field descriptions and control_step_rules.md say "role".

Verified

  • mise run lint and mise run test pass.
  • Live: a v1 scratch config with [models.named.scout], an inline embedder with dimensions, and a comment above the named entry migrates with graph config migrate to [models.scout] under its comment, dimensions dropped with the note printed, and graph version reports config 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.toml stays at version 1 with [models.named] on purpose: the dogfood-files job validates it inside the pinned v0.13.0 image, which reads only config 1. After this ships in a release, a ci: PR moves the image pin and re-stamps .graph/ in block form.

BREAKING CHANGE: config version 2 (graph config migrate)

…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)
@graph-graph

graph-graph Bot commented Sep 2, 2026

Copy link
Copy Markdown

Note

graph_review_9000 · A config-model refactor collapsing fixed model-role slots and [models.named] into one uniform ModelRoles map, with a v1→v2 migration, updated call sites, fixtures, and docs.

At a glance

Pass Area Status
1 Stated Intent ✅ Pass
2 Pipeline/Control-Step Invariants ✅ Pass
3 Template/Shape Engine ⚪ N/A
4 Rust/Async/Storage Footguns ✅ Pass
5 Plan Composition/Model Routing ✅ Pass
6 Test Coverage ✅ Pass
7 Repo Conventions/Docs Parity 🟡 Unverified / Intentional
8 CLI Surface/Copy ✅ Pass

🧵 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 .graph/config.toml intentionally stays at v1 per the description's stated exclusion, which is unverifiable here but explicitly called out as deliberate.

N/A · ✅ Pass

Stated 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: ModelRoles::resolve falls back to default only for standard-role names (via Role::from_name) and never for custom names, matching the stated no-fallback rule for custom roles; failover error surfacing in roles.rs is unchanged.

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.

@mintlify

mintlify Bot commented Sep 2, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
Graph 🟢 Ready View Preview Sep 2, 2026, 10:52 PM

💡 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.
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