Skip to content

Compaction keeps reasoning text in recent, which can make the context bigger than before compaction #51818

Closed

Description

@1sfb

Description

When auto compaction writes out the recent part, it includes every reasoning block in full:

if (part.type === "reasoning") return part.text ? [`[Assistant reasoning]: ${part.text}`] : []

Tool results get truncated, but reasoning doesn't. On our setup, earlier reasoning never reaches the model in live requests: Ollama drops reasoning_content from assistant messages before it applies the chat template (we checked that separately with a token-count probe). Once compaction turns that reasoning into plain text in recent, nothing drops it any more. So with a model that thinks a lot, the retained text can be several times larger than what the same messages cost before compaction.

Numbers from one of our sessions (qwen3.8-27b via Ollama, n_ctx 262144, reasoning on). Over 33 steps in one turn, input grew from 119,130 to 183,344 tokens. So the whole turn cost about 64k tokens in the live context. For example, one step produced 16,271 output tokens, and the next step's input only grew by 881.

When compaction fired, the recent it kept for that same turn was 776,649 chars:

[Assistant reasoning]   33   699,372 chars   90%
[Tool result]           42    52,238 chars    7%
[Assistant tool call]   42    19,803 chars    3%
[Assistant]             19     4,835 chars   <1%

The request right after compaction was 248,032 tokens. Before compaction it was about 200k (183k in + 16k out). So compaction made it bigger, and we ended up within about 2k tokens of the limit. Two days earlier the same thing happened (91% reasoning in recent that time), but it went over the limit at 382k and killed the session.

The size of recent is mostly #43250 (the walk-back to the last user message ignores keep.tokens). But this is a separate problem. Even with keep.tokens honoured, one reasoning block here was 57k chars, about 14k tokens by Token.estimate, so a single step nearly fills a 15k budget on its own.

The new overflow path already leaves reasoning out (flattenMessage, and the comment at L446 says so explicitly). It seems like messageToText should do the same, or at least truncate reasoning the way it does tool output.

Plugins

None

OpenCode version

2.0.18

Steps to reproduce

  1. Use a reasoning model with a large context (we use qwen3.8-27b via Ollama, 262k).
  2. Give it one task that runs for a long time without further user messages.
  3. Let auto compaction fire.
  4. Compare the input tokens of the steps before compaction with the first step after it, and look at the [Assistant reasoning] share of the compaction's recent.

Screenshot and/or share link

No response

Operating System

Fedora 44

Terminal

GNOME Terminal

Activity

  1. github-actions commented on Sep 28, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

  2. 1sfb commented on Sep 28, 2026

    @1sfb
    Author

    I don't think this is a duplicate. #4895 is an old question from the v1 days and it's closed. #42363 is related, since it's the same root cause (reasoning gets serialized as [Assistant reasoning] text), but it's a different path with a different symptom. There, the reasoning in the summarizer input derails the compaction request itself (1.18.x, DeepSeek). Here the compaction succeeds, and it's the verbatim recent it keeps afterwards on 2.0.18 that makes the context bigger than before. Leaving reasoning out wherever history is serialized to text would probably cover both.

  3. scaven-light commented on Sep 29, 2026

    @scaven-light

    Can confirm the other half of this from the provider side.

    opencode sends "reasoning_content" back on every request to z.ai (GLM-5.3-flash), and unlike Ollama, z.ai counts it in input usage. Measured over 400+ turns in one session - basically all reasoning re-enters the prompt and gets billed.

    Did an experimental high-thinking low-output round - max-effort turn produced 30,141 reasoning tokens and 92 output tokens. The very next request's input+cache went from 64,904 to 95,188. every following turn is billed at ~95k input from now on. NICE..

    Either way it's dead weight: providers that strip it (Ollama) ignore it until compaction bakes it into "recent", providers that count it (z.ai) charge for it every single turn. A config option to not send reasoning back for providers that don't need it would fix both and would be obviously benefical and relatively low effort..

  4. robertjmcintyre commented on Oct 8, 2026

    @robertjmcintyre

    Confirming from the compaction-input side on 1.18.35 with a thinking-heavy local model (Qwen 3.8 via litellm, openai-compatible, 200k context). Two separate costs, both real:

    1. Reasoning traces inflate the summarizer input. serialize() embeds reasoning parts verbatim ([Assistant reasoning]: ..., no truncation — unlike tool outputs at 2k). On a real session, auto-compaction failed with "Session too large to compact - context exceeds model limit even after stripping media" at exactly 84% of context (= trigger point, context − maxOutputTokens). A plugin that strips reasoning parts from the compaction-path messages.transform reduced the summarizer call from ~165k to 130,693 input tokens — ~35k tokens of thinking that was feeding the summarizer on that session alone.

    2. The summarizer inherits the session's thinking variant and pays output-side too. The compaction message hardcodes the user message's variant, so a thinking session summarizes at the session's effort level with the full 32k output reservation. 168k input + 32k reserved = exactly 200k: vLLM aborted mid-generation, and the failure surfaced as the misleading "stripping media" error (see #50187) with the session killed mid-task.

    Current workaround is two plugins (chat.params to force enable_thinking: false + delete reasoningEffort; a flag-gated messages.transform to drop reasoning parts on the compaction path only). Both are workarounds for the absence of what's requested here: not serializing reasoning into any summarizer/recent text. A config option covering the serialize path would make the second plugin unnecessary and would fix #42363's symptom class too.

  5. martinclauss commented on Oct 16, 2026

    @martinclauss

    Same issue with GLM-5.3 on a self-hosted OpenAI-compatible endpoint (opencode 1.18.18). The endpoint ignores earlier reasoning_content: adding 100 KB of it leaves prompt_tokens unchanged. The bytes are still sent, though, and they hit our gateway's ~1 MB body limit. Requests failed with HTTP 413 at only 86k–143k tokens, and 46–69 % of each body was replayed reasoning.

    +1 for not sending earlier reasoning back unless the model needs it.

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