anomalyco / anomalyco/opencode

bug(tui): persisted per-model variant state in model.json shadows agent.variant config for primary agents

Open
#38,363 3 comments 1 reaction 1 assignee View on GitHub

@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):

  1. The TUI keeps a per-model variant map in ~/.local/state/opencode/model.json (packages/tui/src/context/local.tsx:155-189).
  2. 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.
  3. 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 configured a.model), variant resolution (local.tsx:362-374) never consults the agent's configured variant.
  4. 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:

  1. Config: agent.build.model = "openai/gpt-5.5", agent.build.variant = "high". Start the TUI, send one message — it correctly runs with high (the state map is still empty, so config applies). The stored message carries model.variant: "high".
  2. Restart the TUI and resume/switch to that session. model.json now contains "variant": { "openai/gpt-5.5": "high" }, seeded without any user action.
  3. Change the config to "variant": "medium". Restart the TUI.
  4. Send a message. Actual: still high (persisted map beats ag.variant). Expected: medium.
  5. Pick medium via /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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.