Repository navigation
session.summarize with a non-session model makes the synthetic post-compaction continue (and the following turns) run on that model #49386
Description
What happens
session.summarize accepts a model for the compaction request. That model is stored on the synthetic compaction trigger message, and OpenCode's post-compaction autocontinue then reuses it for the synthetic continue turn:
packages/opencode/src/session/compaction.ts (v1.18.31, lines ~497–548):
const continueMsg = yield* session.updateMessage({
id: MessageID.ascending(),
role: "user",
sessionID: input.sessionID,
time: { created: Date.now() },
agent: userMessage.agent,
model: userMessage.model, // <-- the summarize request's model
})
const text = /* ... */ "Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed."and SessionCompaction.create (same file, ~559–582) stores the summarize request's model on the trigger message. So when a caller summarizes with a model different from the session's working model, every turn after the auto-continue runs on the summarize model instead of the model the user/session is actually using — and the session only returns to the working model when the next real user message arrives.
This is not reachable through the built-in auto-compact path (which passes lastUser.model), but it is reachable through the public session.summarize API/SDK, which is exactly what orchestration plugins use to summarize on a cheaper/faster model while the session keeps running on its selected model.
Reproduction
- Session on model A (
providerID/modelIDof your choice), a few turns of context. POST /session/{id}/summarize(or SDKclient.session.summarize) with{ providerID, modelID }of model B ≠ A,auto: true.- Observe the recorded messages:
| role | model | notes |
|---|---|---|
| user | B | trigger message, carries a compaction part with auto: true |
| assistant | B | summary, agent: "compaction", summary: true — expected |
| user | B | synthetic continue: "Continue if you have next steps, or stop and ask for clarification if you are unsure how to proceed." |
| assistant ×N | B | the task continuation runs on B |
| user (next real prompt) | A | session returns to its working model |
Message rows from a live session (opencode.db) showing exactly this (B = zai-coding-plan/glm-5.3-flash, A = opencode-go/deepseek-v4.1-flash), 2026-09-16:
17:23:18 user zai-coding-plan/glm-5.3-flash [part: compaction, auto:true]
17:24:20 assistant zai-coding-plan/glm-5.3-flash agent=compaction summary=true
17:31:38 user zai-coding-plan/glm-5.3-flash "Continue if you have next steps, ..."
17:31:38 assistant zai-coding-plan/glm-5.3-flash agent=<working agent> <- wrong model
17:33:25 assistant zai-coding-plan/glm-5.3-flash agent=<working agent>
17:36:11 user opencode-go/deepseek-v4.1-flash (next real prompt — everything after is back on A)
Expected
The synthetic continue turn (and the turns it triggers) should run on the session's working model — i.e. the model of the last non-compaction user/assistant message — regardless of which model was used to produce the summary. Summaries can legitimately run on a different model; continuations should not silently switch the session's model.
Suggested fix
Resolve the continue message's model from the last non-compaction user message instead of the trigger message, e.g.:
const continueModel = lastNonCompactionUserMessage?.model ?? userMessage.modelor pass the working model explicitly through SessionCompaction.create/processCompaction separately from the summarize model.
Note on the plugin workaround (why the hook is not enough)
The experimental.compaction.autocontinue plugin hook can only enable/disable the synthetic continue — the payload contains the model but there is no way to correct it. Downstream plugins therefore have to disable the synthetic continue and re-issue the continue prompt themselves via session.promptAsync with the working model. That workaround has been implemented for the oh-my-openagent plugin in code-yeongyu/oh-my-openagent#8397; a core-side fix would make the workaround unnecessary and would also cover every non-plugin caller of session.summarize.
Environment: opencode-ai 1.18.31 (Windows, x64); source references are from tag v1.18.31.
Activity
mradwankhalil commentedon Oct 10, 2026
This remains reproducible at current v1 source: the synthetic continuation is constructed with model: userMessage.model, so a public session.summarize request using B carries B into continuation instead of the prior working model A. Could maintainers confirm whether this API contract is intended? If not, a regression test should distinguish summary-model B from continuation-model A; the built-in auto-compact path should be tested separately.
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 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊
mradwankhalil commentedon Oct 23, 2026
Thanks for clarifying the V1 triage boundary. The old report alone does not establish a V2 regression; a V2-specific reproduction would be needed before pursuing it again.