Skip to content

Compaction produces empty summary for Kimi K3-256K since v1.18.15 — conversation history silently dropped #41571

Closed as not planned

Description

@Ruifeng-Zhang

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, ## Objective text 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

  1. Use kimi-for-coding/k3-256k (any variant) in a long session.
  2. Trigger compaction (auto at ~131k tokens, or manually: POST /session/{id}/summarize with {"providerID":"kimi-for-coding","modelID":"k3-256k"}).
  3. Compaction assistant message has reasoning part only, 0 text parts, output≈40-60.
  4. 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

  1. Zaczero commented on Aug 18, 2026

    @Zaczero

    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 and stop is that compaction still goes out with no system prompt.

    PROMPT_COMPACTION is registered on a hidden agent and never sent. The only assignment is here; there is no other reference in the tree:

    draft.update(Agent.ID.make("compaction"), (item) => {
    item.name = Agent.Name.make("Compaction")
    item.mode = "primary"
    item.hidden = true
    item.system = PROMPT_COMPACTION

    The request itself is one user message, tools: [], no system, no generation.maxTokens:

    .stream(
    LLM.request({
    model: plan.model,
    promptCacheKey: SessionPromptCacheKey.make(plan.session.id),
    http: { headers: SessionModelHeaders.make(plan.session, dependencies.app) },
    messages: [Message.user(plan.prompt)],
    tools: [],
    }),

    title.ts resolves its hidden agent and ships PROMPT_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. Passing agent.info.system (and generation: { maxTokens: 4096 }) on this request is the sibling of title.ts:63-68. #41578 is why agent.compaction.variant is also discarded — the agent is never resolved.

  2. Thexinyi commented on Aug 22, 2026

    @Thexinyi

    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 a reasoning part with no text part, 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 emits reason: stop without 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:

    1. hasNonEmptyTextBody() as the bottom-line defense — iterate message.parts, require ≥1 type === "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).

    2. 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/sdk Part union, TextPart (type:"text") and ReasoningPart (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.

  3. vladislav-miroshnikov commented on Sep 9, 2026

    @vladislav-miroshnikov

    @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?

  4. thdxr commented on Oct 11, 2026

    @thdxr
    Member

    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 𝕺𝖕𝖊𝖓𝕮𝖔𝖉𝖊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions