Repository navigation
Description
Summary
Since v1.18.15, compaction produces empty summaries for Kimi K3-256K (kimi-for-coding/k3-256k): the compaction assistant message contains only a reasoning part and no text part (0 text parts, ~40-60 output tokens), then finishes with reason: stop. The conversation history is then replaced by an empty summary, silently dropping all context.
Other models (gpt-5.x, k2p7, deepseek-v4) compact normally on the same session — this is specific to k3-256k (a reasoning model).
Regression Window
- v1.18.13 / v1.18.14: compaction works (structured output summary,
## Objectivetext part present) - v1.18.15: compaction starts degrading (summary text becomes non-summary content, or empty)
- v1.18.16: fully broken —
output=41~60,0 text parts
The compaction.ts file is identical between 1.18.15 and 1.18.16, and identical between 1.18.13 and 1.18.14. The change was introduced in v1.18.15 ("Repeated compaction now keeps earlier tool-call history in summaries instead of dropping orphaned results").
Root Cause (from code diff v1.18.14 → v1.18.15)
packages/opencode/src/session/compaction.ts, in SessionCompaction.process:
Before (v1.18.14):
const modelMessages = yield* MessageV2.toModelMessagesEffect(msgs, model, {
stripMedia: true,
toolOutputMaxChars: TOOL_OUTPUT_MAX_CHARS,
})
...
messages: [
...modelMessages, // structured history: system/user/assistant roles + tool result blocks
{ role: "user", content: [{ type: "text", text: nextPrompt }] }, // prompt as separate user msg
]After (v1.18.15+):
const conversation = msgs.map(serialize).filter(Boolean).join("\n\n") // NEW: flatten to plain text
...
messages: [{
role: "user",
content: [{ type: "text", text: [nextPrompt, "The following is the conversation history:", conversation].join("\n\n") }]
}]A new serialize() helper flattens the whole history into one big plain-text string ([User]: ..., [Assistant]: ..., [Assistant tool call]: ..., [Tool result]: ...) and merges the compaction prompt into a single user message with no role structure.
Why k3-256k breaks
k3-256k is a reasoning model. Given a huge wall of unstructured plain text (11万+ tokens in the failing case), it enters reasoning mode and starts analyzing the content as if it were a coding task instead of summarizing the conversation — the reasoning part in the failing compaction says e.g.:
"The first occurrence (with
mapped ||preceding) was in classifyPostgresPayment; the second in classifyPaymentReversal (memory)..."
Then it emits reason: stop without ever producing a summary text part. opencode marks the message summary: true, treats compaction as complete, and swaps in an empty summary — all prior context is lost. Stronger-following models (gpt-5.x, k2p7, deepseek) still produce ## Objective summaries despite the format change, which is why this looks model-specific.
Repro
- Use
kimi-for-coding/k3-256k(any variant) in a long session. - Trigger compaction (auto at ~131k tokens, or manually:
POST /session/{id}/summarizewith{"providerID":"kimi-for-coding","modelID":"k3-256k"}). - Compaction assistant message has
reasoningpart only,0text parts,output≈40-60. - History is replaced with empty summary — context lost.
Verified on v1.18.16 both via auto-compaction and manual /summarize on a 42万-token session.
Suggested Fix
Restore the structured message format for the compaction prompt (use MessageV2.toModelMessagesEffect output as separate role-typed messages, with nextPrompt as a standalone trailing user message), or keep serialize() only as a fallback for models that cannot consume structured tool results. At minimum, detect empty summaries (no text part) and fail compaction instead of silently discarding history.
Environment
- opencode v1.18.16 (also affected: v1.18.15)
- macOS arm64
- provider: kimi-for-coding / k3-256k
Activity
Zaczero commentedon Aug 18, 2026
The 1.18.15
serialize()flatten made the payload denser with[Assistant tool call]lines. The reason a reasoning model then emits 40–60 tokens of thinking andstopis that compaction still goes out with no system prompt.PROMPT_COMPACTIONis registered on a hidden agent and never sent. The only assignment is here; there is no other reference in the tree:opencode/packages/core/src/plugin/agent.ts
Lines 143 to 147 in 9e22c40
The request itself is one user message,
tools: [], nosystem, nogeneration.maxTokens:opencode/packages/core/src/session/compaction.ts
Lines 274 to 281 in 9e22c40
title.tsresolves its hidden agent and shipsPROMPT_TITLE(which says “Never use tools”). Compaction does not. A Kimi reasoning model handed a wall of tool traces, with tools silently gone and nothing saying so, continues the trace instead of summarizing it.finish: stop+ 0 text parts is that outcome. The empty summary then replaces history.Same failure on DeepSeek is #42363 / #42371. The docs already claim “tools disabled and at most 4096 output tokens”; only the
tools: []half exists. Passingagent.info.system(andgeneration: { maxTokens: 4096 }) on this request is the sibling oftitle.ts:63-68. #41578 is whyagent.compaction.variantis also discarded — the agent is never resolved.Thexinyi commentedon Aug 22, 2026
Confirming this affects more than k3-256k:
opencode-go/glm-5.3(reasoning model) on v1.18.21 reproduces the identical symptom at a 100% rate (2/2 triggers) — compact reports success, the summary message contains only areasoningpart with notextpart, and history is silently dropped.This suggests the v1.18.15
serialize()flattening affects reasoning models broadly: given the wall of unstructured plain text, the reasoning model analyzes instead of summarizing and emitsreason: stopwithout a text part. glm-5.3 is just another reasoning model hitting the same path.I filed #44080 with the same core failure, plus two additions worth considering for the fix:
hasNonEmptyTextBody()as the bottom-line defense — iteratemessage.parts, require ≥1type === "text"with non-whitespace content; fail compaction (and retry while source material still exists) instead of landing the empty summary. This is your "at minimum detect empty summaries and fail" suggestion, and it covers all reasoning models, not just k3-256k. Crucially it must run before epoch replacement — once the epoch is swapped, the source material is destroyed and any retry is useless (silent + permanent).L0 stream-layer detection — detect/reject reasoning-only (no-text-part) responses as incomplete at stream end, as upstream defense before compaction even runs. Per the
@opencode-ai/sdkPart union,TextPart(type:"text") andReasoningPart(type:"reasoning") are independent union members, so a reasoning-only response is structurally detectable (not a length-threshold heuristic, negligible false-positive risk).Happy to dedup #44080 into this one if you prefer a single tracker — the core fix overlaps; I kept #44080 for the L0 + Part-union structural-detection framing. Just let me know.
vladislav-miroshnikov commentedon Sep 9, 2026
@nexxeln #42063 prevents an empty compaction summary from becoming the history boundary. V1 still accepts a finished, error-free summary without checking for text; Core/V2 handles empty text separately. The PR has regression coverage and no conflicts, but code CI is waiting for approval. Could you review that guard and enable the checks?
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 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊