Repository navigation
Description
Description
Changing agent.<name>.variant in opencode.json has no effect on primary agents driven from the TUI, even after a full restart. The config is persisted and served correctly (GET /config shows the new variant), but requests keep running with the old one.
Cause — a precedence inversion between the TUI's persisted per-model variant memory and the agent config (file/line refs against the v1.18.0 tag):
- The TUI keeps a per-model variant map in
~/.local/state/opencode/model.json(packages/tui/src/context/local.tsx:155-189). - The map is auto-seeded: opening or switching to a session copies the last user message's variant into it and saves (
local.model.variant.set(msg.model.variant),packages/tui/src/component/prompt/index.tsx:328). No native variant pick is needed — using a session once is enough. - Every prompt sends the map value as the explicit variant (
prompt/index.tsx:988). Unlike model resolution (local.tsx:236-245, which falls back to the agent's configureda.model), variant resolution (local.tsx:362-374) never consults the agent's configured variant. - Server-side the client-sent variant always wins:
input.variant ?? (ag.variant && ...)(packages/opencode/src/session/prompt.ts:654).
So once the map has an entry for a model — automatic after the first message — later agent.<name>.variant changes are silently shadowed forever for TUI-driven agents. Restarting doesn't help (model.json is persisted). Picking via /variants "works" because it writes the map that actually wins, which makes the config path look broken by comparison.
A per-model memory also can't represent this config at all — several agents legitimately share a model with different variants:
"agent": {
"orchestrator": { "model": "openai/gpt-5.6-sol", "variant": "low" },
"sdd-design": { "model": "openai/gpt-5.6-sol", "variant": "medium" },
"sdd-spec": { "model": "openai/gpt-5.6-sol", "variant": "high" }
}One "openai/gpt-5.6-sol": "medium" entry in model.json overrides all three.
Expected behavior — mirror the model precedence: treat a native pick as an ephemeral per-agent override (like the in-memory /model override), fall back to the agent's configured variant otherwise, and don't auto-seed the persisted map from session sync (or key it per agent+model and invalidate it when the configured variant changes).
Related: #13718 reported this same shadowing on v1.2.4 (closed not_planned — this adds the root-cause trace and confirms it on v1.18.0). #28803 is the adjacent session-lock case (SessionTable.model consulted first, session/prompt.ts:615-627). #36250 covers the subagent vector (handleSubtask resolves taskModel before taskAgent, prompt.ts:267 vs :313, and stamps the parent's variant at :275 — also still present at v1.18.0).
Plugins
None — reproduces on a clean install with no plugins
OpenCode version
1.18.0
Steps to reproduce
With isolated XDG_CONFIG_HOME / XDG_STATE_HOME / XDG_DATA_HOME:
- Config:
agent.build.model = "openai/gpt-5.5",agent.build.variant = "high". Start the TUI, send one message — it correctly runs withhigh(the state map is still empty, so config applies). The stored message carriesmodel.variant: "high". - Restart the TUI and resume/switch to that session.
model.jsonnow contains"variant": { "openai/gpt-5.5": "high" }, seeded without any user action. - Change the config to
"variant": "medium". Restart the TUI. - Send a message. Actual: still
high(persisted map beatsag.variant). Expected:medium. - Pick
mediumvia/variants— now it applies, confirming the inversion.
Also observed on a real setup: config orchestrator → openai/gpt-5.6-sol variant=low, model.json holding "openai/gpt-5.6-sol": "medium", orchestrator observably running at medium across restarts.
Screenshot and/or share link
No response
Operating System
macOS 26 (Darwin 25.4.0)
Terminal
Ghostty
Activity
samiralibabic commentedon Jul 23, 2026
Confirmed on current
upstream/dev. I traced the behavior toed0c0d90be(feat: add variants toggle), with the global per-model variant state carried into the v2 TUI bye6f660fecf.The terminal TUI stores active variants in
~/.local/state/opencode/model.jsonunderprovider/model, and restoring a session writes its variant into that global store. This contaminates other sessions and agents sharing the model. The desktop/web app already keeps{ agent, model, variant }state per session.A complete fix appears to be:
model.json; retain only recents/favorites.I can prepare a focused PR implementing this approach if that matches the intended semantics.
andresnator commentedon Jul 27, 2026
What do you think, mate @kommander ? Should we open the PR?
fobiat commentedon Sep 17, 2026
Current dev 88c6c7a still has distinct client behavior: full TUI local.model.variant.current() reads the per-model saved map, while currentModel resolves an explicit per-agent model override before the agent model. The prompt footer and submission both consume that variant getter. Restoring a session writes its variant back into the saved map. This can be a resolution bug, not merely a stale label; #47076 is the existing fix to coordinate with.
The web app instead keeps per-session selection and applies agent.set to the selected model/variant. A source explanation and running-versus-next-turn display need a separate design decision; they should not silently change explicit-override precedence. Core session-runner-model tests pass locally, but that alone does not verify client-to-provider agreement.
thdxr commentedon Oct 11, 2026
Thanks for the report! OpenCode 2.0 is now out and development is focused on V2, so we're closing issues filed against OpenCode 1. Please upgrade (
opencode upgrade --major, details: https://anoma.ly/notes/opencode-2-0), and if this is still relevant on 2.0, reopen this issue or open a new one with your 2.0 version.— from 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊