Repository navigation
Description
Summary
After a compaction (and again after a revert to just-after that compaction), the model role-flips into OpenCode's compaction summarizer: it first emits the <conversation-checkpoint>…</checkpoint> serialization itself as a user-visible answer, then degenerates to answering every following turn with an empty <conversation-checkpoint><summary>(no content)</summary></checkpoint>. No tool parts, no substantive text — yet every step is stored finish=stop and closes idle outcome=succeeded, so nothing looks broken from the outside. Related: #50814 (flattened [Assistant tool call] transcripts after compaction). This is a distinct degenerate loop observed in the same session.
Environment
- opencode version: 2.0.31 (install/channel: latest)
- OS: Windows 11 Home 10.0.26200 (win32 x64)
- Terminal: TERM=xterm-256color; desktop client (TERM_PROGRAM not set)
- Shell: C:\Windows\system32\cmd.exe
- Active plugins: none (no
pluginkey in~/.config/opencode/opencode.json, no local plugin dirs) - Models:
stepfun-ai/step-5-previewvia a custom@ai-sdk/openai-compatibleprovider (NVIDIA NIM endpoint);mimo/mimo-v2.6-flashalso affected earlier in the same session
Reproduction
- Run a long
buildsession until auto-compaction fires (sessionses_eca4fc28dffdfCI4tEPJ6I7UhI; compaction record at seq 37478, 2026-10-19 15:47 local, 148 KB). - Revert the session to just after that compaction (the revert removed seq 37485–37608, i.e. the polluted turns — verified by diffing session rows before/after).
- Send a message that touches the compaction summary (e.g. asking how summary formatting is produced).
- Keep sending normal follow-up turns.
Observed (all verified against the session SQLite DB, not the TUI rendering):
| seq | time (local) | model output | stored result |
|---|---|---|---|
| 37617 | 16:33:06 | reasoning only | finish=error, turn interrupted |
| 37655 | 16:36:36 | full <conversation-checkpoint><summary>…</summary><recent-context>…</recent-context></checkpoint> block (8,694 output tokens) |
finish=stop, 0 tool parts |
| 37677 | 16:51:51 | <conversation-checkpoint><summary>(no content)</summary></checkpoint> |
finish=stop, idle succeeded |
| 37689 | 16:53:42 | same, empty | finish=stop, idle succeeded |
| 37701 | 16:54:45 | same, empty | finish=stop, idle succeeded |
| 37713 | 17:00:05 | same, empty | finish=stop, idle succeeded |
After the revert the session produced 0 real tool calls. It was effectively dead while every turn reported success.
Expected Behavior
- The assistant answers user turns normally and emits structured tool parts; it must not reproduce compaction serialization as chat output.
- A turn with zero tool parts and no substantive text must not be recorded as a successful turn.
Actual Behavior
- The model emits the compaction scaffold (
<conversation-checkpoint>/<summary>/<recent-context>/</checkpoint>) as its answer, then collapses to empty checkpoint blocks for every subsequent turn. <conversation-checkpoint>appears in none of the 80 stored compaction records, nor ininstruction_blob,kv, or any config file on this machine (verified by search). The model reproduces the server-side serialization from context/memory, then imitates its own prior degenerate output.- The single full checkpoint it emitted (seq 37655) also re-asserted fabricated statistics from the compaction: model emits tool calls as literal text after compaction — tools never run, session returns no output #50814 failure mode ("23 files / 41,857 chars → 10–14K tokens per turn"; the real measurement is 9 files / 210,189 chars), promoting a hallucinated number into a checkpoint-shaped "authoritative" summary that later turns then inherit.
Additional Context
- Evidence collected from a read-only copy of the session DB:
session_messagerows (type,contentpart types,finish,tokens, snapshot),type='compaction'rows,instruction_blob,kv. - Recovery dead-ends observed:
- Reverting to just-after the compaction keeps the polluted compaction record as the context base; the model relapses.
/compactis not a user message, so revert/undo cannot target it.POST /api/session/{id}/forkcopies projected history before a message, so any fork point after the first polluted compaction carries the pollution into the child session — forking cannot produce a clean continuation of this session at all.
- Suggested mitigations:
- Integrity check: an assistant message whose text/reasoning contains
[Assistant tool call]:,[Tool result]:, or<conversation-checkpoint>while having zero tool parts should fail the step (or at least warn in the TUI + log), not closeidle/succeeded. - Make the compaction scaffold visibly non-imitable / out-of-band, since the model demonstrably role-flips into "summarizer" once the conversation touches compaction.
- Structural representation of prior tool calls in post-compaction prompts (tracked in compaction: model emits tool calls as literal text after compaction — tools never run, session returns no output #50814).
- Integrity check: an assistant message whose text/reasoning contains
Happy to provide the full DB rows (seq 37655, 37677–37713) if useful.
Activity
opencode-agent commentedon Oct 20, 2026
Thanks for the detailed write-up and the DB-level evidence.
The
<conversation-checkpoint>wrapper isn't stored anywhere. OpenCode builds it from the compaction record each time it assembles a prompt (packages/core/src/session/runner/to-llm-message.ts) and sends it as a user message labelled as historical context. That's why your searches of the compaction records,instruction_blobandkvdidn't find it. What you saw is the model (step-5-preview/ mimo) copying that injected block and then repeating its own empty output. Those turns contain text and finish withstop, so marking them as succeeded is how step completion currently works.I've filed this as a feature request for the safeguards you suggest: flagging or failing assistant turns whose text looks like compaction or transcript scaffolding but which have no tool parts, and making the checkpoint scaffold harder for models to imitate. Maintainers will consider it; I can't promise anything.
Related issues:
stopturns recorded as success. The PR only catches completely empty output, so it wouldn't catch these checkpoint-shaped replies