anomalyco / anomalyco/opencode
bug(tui): persisted per-model variant state in model.json shadows agent.variant config for primary agents
@kommander is already working on this.
Since Jul 22, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
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
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.