Repository navigation
Description
core: compaction ignores agents.compaction.model since "shared model request" refactor (v2 beta)
Summary
Since the Aug 19–21 refactor that routes title and compaction through a shared model-request flow, manual compaction in v2 beta always uses the session's current model and silently ignores agents.compaction.model. The config key is accepted (it shows up in opencode2 debug agents) but never consulted for model selection — a silent regression for v1 configs that set a dedicated compaction model.
Environment
- opencode version: 0.0.0-beta-17898
- OS: Windows 11 (Windows_NT 10.0.26200.9168, x64)
- Terminal: Git Bash / mintty (TERM=xterm-256color)
- Shell: bash (Git Bash, /usr/bin/bash)
- Install/channel: beta (npm
@opencode-ai/cli0.0.0-beta-17898, auto-updated) - Active plugins: none found in config
Reproduction
- Configure a dedicated compaction model, e.g. in
opencode.json:{ "agents": { "build": { "model": "opencode-go/deepseek-v4-flash#max" }, "compaction": { "model": "opencode-go/mimo-v2.5" } } } - Restart the service (
opencode2 service restart) and verify the merged registry:
opencode2 debug agents→ compaction agent does show"model": { "id": "mimo-v2.5", "providerID": "opencode-go" }. - Trigger manual compaction (keybind
session.compact, orPOST /api/session/{sessionID}/compact). - Observe the actual model used: the request follows the session's model (e.g.
deepseek-v4-flash#max), not the configured compaction model (observable via cost/token accounting deltas matching the session model's pricing, and via the summary request behavior).
Expected Behavior
When agents.compaction.model is set, compaction should use that model (as v1 did, and as the dev branch code still does). At minimum, an accepted-but-ignored config key should produce a warning instead of silent no-op.
Actual Behavior
Compaction model resolution is hard-wired to the session model:
packages/core/src/session/compaction.ts(betabranch):models.resolve(input.session)→modelRequests.prepare({ scope: { session, agentID: Agent.ID.make("compaction"), model: plan.resolved }, ... })packages/core/src/session/runner/model.ts:resolve(session)returns the session'ssession.model(falling back to a default only when none is set).packages/core/src/session/model-request.ts:prepareusesinput.scope.modeldirectly (line ~284const resolved = input.scope.model);scope.agentIDis used only as a request label (agent: input.scope.agentID), never to look up the agent's ownmodel.
So agents.compaction.model never participates in model selection in beta builds.
Additional Context
- Docs (opencode.ai/v2/docs/compaction) state compaction uses the session's current model with no separate model setting — that matches beta behavior, but the config key is still accepted without warning, which silently breaks migrated v1 configs (including the v1 built-in compaction agent model field).
- The dev branch still has the old behavior:
packages/opencode/src/session/compaction.ts(dev, last change Aug 12) resolves:const agent = yield* agents.get("compaction") const model = agent.model ? yield* provider.getModel(agent.model.providerID, agent.model.modelID) : yield* provider.getModel(userMessage.model.providerID, userMessage.model.modelID)
- Likely related commits on the
betabranch:ace822308f(2026-08-19) "refactor(core): move compaction config into state"ebc2504ef3(2026-08-20) "refactor(core): route title and compaction through shared model request"e5da5bfab2/3a1fb5ae65(2026-08-21) "simplify effect workflows" / "simplify projection state"
Feature request: in the shared model-request flow, honor agents.compaction.model (the agent model, like the dev branch) when set — e.g. resolve the compaction scope's model from the compaction agent first, falling back to the session model. Otherwise the beta channel can never use a cheaper/larger model for compaction while keeping the session model for chat.
Activity
github-actions commentedon Aug 22, 2026
This issue might be related to an existing issue. Please check:
agents.compactionconfig fields are ignored during compaction — that issue covers thevariantfield being silently ignored; this issue coversmodelbeing ignored on thebetabranch after the shared model-request refactor. They share the same root area and may benefit from a combined fix.zce commentedon Aug 30, 2026
Hit this after migrating too. Some context that might help the design discussion:
The usual instinct for keeping the session model on compaction is cache preservation, but the compaction request can't hit the session cache anyway: it's a serialized transcript in one user message with an empty system prompt, sharing no token prefix with the conversation. Post-compaction requests are also rebuilt from the checkpoint, so the session model's cache is invalidated regardless of which model wrote the summary. A dedicated cheaper model loses nothing, while the session model pays full input price on a prompt that can approach the entire context window.
If this gets fixed, two adjacent pieces probably need to land with it, or a smaller-window compaction model won't be usable in practice:
required()derives the preflight ceiling from the session model's limits only; it should also account for the compaction model's context window (Manual /compact sends the full history to the compaction model without a context-fit check #46164, Compaction request exceeds context window on high-output models (v2) #42448)max_tokensunset, so providers can reserve the full output limit (Compaction request exceeds context window on high-output models (v2) #42448)Usage recording would follow for free, since it already bills against
plan.resolved.For what it's worth, the workaround people run today is rewriting
body.modelin asession.http.requesthook. It works, but can't fix usage accounting or the trigger math, and fails silently back to the session model when the request shape changes.rcdailey commentedon Sep 27, 2026
Two other reports of the same problem:
This is a regression from v1, where a dedicated compaction model worked fine. I rely heavily on cheaper models from other providers for compaction, so right now every compaction runs on my most expensive model.
From what I can tell, it started with #43555 (route title and compaction through shared model requests).
SessionCompaction.prepareinpackages/core/src/session/compaction.tspassescontext.modelandcontext.agent.id, so thecompactionagent's model and variant are never read.I realize this repo gets a huge volume of issues and PRs, and that makes regressions like this easy to miss. I'm hoping this gets fixed soon.
mcoffin commentedon Sep 28, 2026
I'm following this issue because I hit the exact same...
agents.title.modelis set to a lightweight model for title generation but silently falls back to the primary model every time. I've already traced the root cause to theModel.Service.read()caching bug wherecached?.data === datareturnstruebecause the internalMapis mutated in place rather than replaced, so the stalebyProviderlookup never rebuilds.eriknovikov commentedon Oct 1, 2026
I also need this in V2 (currently on v2.0.16).
It would be really helpful if
session.compact(/compactorCtrl+x+c) honored agents.compaction.model instead of always using the currently selected session model.The idea is to always use a faster/cheaper model for compaction. It should not change the model used for subsequent turns.
Thanks in advance!
sergiopadure commentedon Oct 2, 2026
Still reproducible on the current
v2branch (d113a40, 2026-10-02) and on 2.0.22. In case it helps whoever picks this up, the gap seems to be:plugin/agent.tsregisters thecompactionagent (name,primary,hidden), soagents.compaction.modelis merged and shown bydebug agents.session/context.tshasselectTitle, which honours the title agent's model (if (agent.model) return yield* model.get(...)). There is no equivalent for compaction, and nothing in core looks upAgent.ID.make("compaction").SessionCompaction.compact: manual (session/runner/llm.ts,context.select(sessionID)pluscontext.resolveModel(selected.session)), auto and overflow (the step's loaded context).session/compaction.tsthen usescontext.modelfor the ceiling and the request (agent: context.agent.id,model: context.model).So a
selectCompactionalongsideselectTitle, used to build the compaction context's model, would probably cover it. The ceiling should arguably stay on the session model's window, while the request runs on the compaction model.Measured impact here: 0 of 255 compactions ran on the configured compaction model. They all ran on the session model, which for Claude sessions means the full older history is billed at that model's cache-write price (see #52761 for the cost figures). A cheaper compaction model is the main lever we have against that.
rekram1-node commentedon Oct 3, 2026
We can honor it, the reason it wasn't was because the cache hit is normally cheaper (since it uses same model).
rcdailey commentedon Oct 3, 2026
@rekram1-node I agree if the cache is warm in most of the time compaction is used. The problem is, you don't even offer that choice to people very easily today. The current design assumes warm cache is the majority case. For Anthropic, which as I believe 5 minute TTL on cache, it means most of my compaction is a cache miss which for Opus, is more expensive than Deepseek Flash for compaction. And for me personally, most of the time I do compaction it's usually when returning to my desk from work the day prior, which for most providers is a cache miss since hours have passed.
Also note that not every provider reduces cost based on caching such as the NanoGPT subscription; input tokens count toward rate limits no matter if it's cached or not. Probably not a common case; but still a reason the flexibility is appreciated.
What I'll typically do is use a very very cheap model for compaction so that cache doesn't matter; and keep my main model selection at Opus 5.5 for everything between. It's less tedious to remember to swap my model before I run
/compactwhich was the case for v1.I appreciate you taking the time to respond here!
sergiopadure commentedon Oct 3, 2026
Thanks for looking at this. One data point on the cache argument: on V2 today, compaction mostly does not get the warm cache, even when the cache is warm.
In #52761 (same setup, OpenCode 2.0.12 to 2.0.22), 132 compactions ran within 5 minutes of a model call that had read at least 80% of its input from the cache. Only 1 of those 132 compactions read 50% or more of its input from the cache. The median read share was 7.4% on the OpenAI route and 4.2% on Anthropic Messages routes, where about 96% of the compaction input was billed as a new cache write. So at the moment, compacting on the session model pays close to the full input price.
Honouring
agents.compaction.modelonly when it is set would keep the same-model default for people who benefit from a warm cache, and would give the rest of us the cheaper option. Once #52761 is fixed, the trade-off may well favour the default again.sergiopadure commentedon Oct 3, 2026
@rekram1-node, in case it helps: #52875 implements this. It adds
selectCompactionnext toselectTitleand moves only the summary request to the compaction model. The decision when to compact stays on the session model, and behaviour is unchanged when no compaction model is configured.The PR description contains
Fixes #44094, but the PR targetsv2, which is not the default branch, so GitHub does not register the closing link. That is why the issue-first check labelled itneeds:issue.Would you like any changes to it?
rekram1-node commentedon Oct 4, 2026
@sergiopadure there is a known issue for the anthropic case but for other providers it should get a cache hit normally but that's unrelated to this issue
sergiopadure commentedon Oct 4, 2026
Thanks, agreed that it's separate from this issue. One note so it doesn't get lost: the data in #52761 is not only the Anthropic case.
On the built-in
openaiprovider (gpt-6-luna,gpt-6-sol,gpt-6-astra, OpenCode 2.0.16–2.0.21), 113 automatic compactions read a median of 7.4% of their input from the cache. In each case, the previous call in the session ran at most 5 minutes earlier and read at least 80% of its input from the cache.Happy to continue in #52761. If there is an issue for the known Anthropic case, I can add the numbers there too.
rekram1-node commentedon Oct 5, 2026
True, ik why that happens itll get resolved soon.
rekram1-node commentedon Oct 5, 2026
@sergiopadure merged a slightly simpler version of ur pr. closing issue. Will release later.