anomalyco / anomalyco/opencode
session.summarize with a non-session model makes the synthetic post-compaction continue (and the following turns) run on that model
@jlongster is already working on this.
Since Sep 16, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
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.model
or 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.
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.