Skip to content

compact silently lands reasoning-only (empty-body) summaries, causing irrecoverable context loss #44080

Description

@Thexinyi

Summary

When the model used for /compact returns a response containing only reasoning parts and no text part, opencode lands that empty-body message as the session summary and reports compact as successful. The epoch replacement then permanently destroys the original conversation history — yet everything reports success, so the agent continues with an essentially empty (corrupted) context and no warning.

Reproduction — 100% reproducible (2/2 triggers)

  • opencode v1.18.21 (latest)
  • Model: opencode-go/glm-5.3 (reasoning model)
  • Triggered compaction twice on this model; both times compact reported success while the summary message had only a reasoning part and no text part, and the original history was lost.

Steps:

  1. Use opencode-go/glm-5.3 in a session.
  2. Trigger /compact.
  3. Compact reports success; the resulting summary message has no text body; the original history is gone and cannot be recovered.

Relationship to #41571

Shares the same core failure as #41571 (Kimi K3-256k): a reasoning model produces a reasoning-only summary (no text part), opencode marks it summary: true, lands it, and swaps in an empty summary — silently dropping history. #41571 traces this to the v1.18.15 serialize() flattening change in compaction.ts (history flattened to a wall of plain text → reasoning model analyzes instead of summarizing). opencode-go/glm-5.3 on v1.18.21 exhibits the identical symptom at a 100% rate, suggesting the serialize-format issue affects reasoning models broadly, not just k3-256k. (I have not yet read compaction.ts to confirm glm-5.3's exact trigger mechanism, but the symptom + 100% rate + reasoning-model + same serialize code path make a shared root cause highly likely.)

Additional proposals

  • L0 (stream layer): detect/reject reasoning-only (no-text-part) responses as incomplete at stream end — upstream defense before compaction even runs.
  • Structural detection basis: per the @opencode-ai/sdk Part union, TextPart (type:"text") and ReasoningPart (type:"reasoning") are independent union members — a reasoning-only response has no text part at all and is structurally detectable, not a length-threshold heuristic (negligible false-positive risk).
  • Irrecoverability: once the epoch is replaced, the source material is destroyed, so any retry afterward is useless — the failure is silent and permanent. The failure must be caught before epoch replacement.

Expected behavior

  • Compact should fail and retry the LLM while source material still exists when the produced summary has no non-empty TextPart — not land it and replace the epoch.
  • The success/"compacted" marker must not be set until the summary passes body validation.

Suggested fix

Add a hasNonEmptyTextBody(message) check (iterate message.parts; require at least one type === "text" with non-whitespace content) at:

  • L0 (stream layer): at stream end, reject reasoning-only responses as incomplete.
  • L1 (compact landing): before epoch replacement, validate the summary body; on failure, retry the LLM (source still intact) instead of landing.

The same detection function can live in the SDK so plugins/tools share one definition. This is the "at minimum detect empty summaries and fail" bottom-line from #41571, plus an upstream L0 defense.

Impact

  • Severity: high — irrecoverable, silent context loss; the agent continues on a broken session with no warning because the compacted flag is set despite no real content.

Environment

  • opencode v1.18.21 (latest)
  • Model: opencode-go/glm-5.3 (reasoning model)
  • Repro rate: 2/2 (100%)

Activity

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