Skip to content

compaction: manual compaction keeps full conversation in next request #54910

Open

Description

@zeetkt

Summary

Manual compaction (/compact) on v2.0.27 completes and produces a summary, but the next request still contains the entire previous conversation. The context size does not drop. Compaction triggered as recovery after a provider "too long" rejection works correctly, so the regression appears specific to the manual path.

Environment

  • opencode version: 2.0.27 (updated today; previous version unknown)
  • OS: Linux 7.2.9-2-cachyos (x64)
  • Terminal: TERM=xterm-256color, COLORTERM=truecolor
  • Shell: /bin/fish
  • Install/channel: latest (install script, ~/.opencode/bin/opencode)
  • Active plugins: none configured; MCP server playwright (local) configured
  • Model: local strata (llama.cpp) server via @ai-sdk/openai-compatible (openai-compatible), serving a Qwen 3.8 Next iQ3_XS quantization, limit.context: 200000, limit.output: 32000. (The model name/GGUF path declared in opencode.jsonc is stale and does not match the model actually served.)

Reproduction

  1. Start a session with a local openai-compatible provider (llama.cpp, context 200k, output 32k) and grow the conversation to ~130k prompt tokens.
  2. Run manual compaction (/compact). The summary is generated and the compaction message is recorded with status: "completed", reason: "manual".
  3. Send the next user message and inspect the prompt tokens of the next assistant request (e.g. tokens.input + tokens.cache.read on the assistant message).

Expected Behavior

After compaction, the next request should contain system prompt + summary + the recent window (~keep.tokens = 15k), i.e. roughly 25–30k tokens.

Actual Behavior

The next request contains the full previous conversation plus the summary:

The session then keeps growing and the provider rejects requests, e.g.:

prompt (171843 tokens) + max tokens (28167) exceeds the context (200000); requests are never truncated

Additional Context

Timeline from one session today (session ses_ecebb6ce5ffeDOXAodWeNMLYUi), all three compactions recorded as reason: "manual", status: "completed":

Time Event Next request prompt size
18:33:17 opencode binary updated to 2.0.27; service restarted 18:33:23 —
18:35:11 Manual compaction #1, summary 2 008 output tokens 150 878 ❌
18:39:16 Provider rejection: prompt (171843) + max tokens (28167) exceeds the context (200000) —
18:40:14 Compaction #2 (recovery after rejection), summary 2 362 output tokens 27 215 ✅
19:02:36 Manual compaction #3, summary 2 762 output tokens 106 441 ❌
19:04:25 Next user message → 106k prompt → request error at 19:05:14 —

Notable detail: the summarization request input sizes differ sharply between the two paths:

This suggests the manual path computes the compaction anchor at the start of the conversation (summarizing the recent window and keeping the older conversation) instead of at the end.

Other notes:

  • The problem appeared today, right after updating to 2.0.27 (binary mtime 18:33:17, first manual compaction 18:35:11). Earlier sessions on the previous version compacted correctly (e.g. 96k → 30k, 129k → 39k, 94k → 35k prompt tokens).
  • opencode update checks fail today: UpgradeError: The update service returned HTTP 500. (so a fix may exist in a newer release).
  • Workaround applied: rely on automatic compaction with a larger buffer ("compaction": { "auto": true, "buffer": 40000 }) so the working recovery/auto path runs before the provider rejects the request; avoid manual /compact on 2.0.27.

Activity

  1. opencode-agent commented on Oct 12, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report and the token timeline. I was able to reproduce this on 2.0.27, and it still happens on the latest dev (80fb31eb).

    Setup: a simulated model, default keep.tokens (15k), three short exchanges, then one user message followed by a single very long assistant turn (about 60k tokens) with no user message after it. Then /compact and one more message:

    • The summarization request was about 21.5k tokens and did not include the long turn.
    • The first request after compaction was about 66k tokens and included that whole turn verbatim. The conversation went from about 81k tokens before compaction to about 66k after, instead of summary + about 15k.

    The script below reproduces it and logs every request's size; the recording shows the TUI side.

    The likely cause is how compaction picks what to keep. It measures keep.tokens from the end, then walks back to the nearest earlier user message and keeps everything after it as text. When the work since your last message is long (for example a long agent run), most of the conversation is kept verbatim and only the older part is summarized. That would also explain why your recovery compaction worked: it ran right after a new message, so the last user message was near the end. The cause is most likely the same as #43250; #51818 (reasoning kept in full) can make it worse.

    Until this is fixed, two things should help:

    • Send a short message right before running /compact.
    • Keep using the larger compaction.buffer you already set up.
    recording.mp4
    Reproduction script (drive.ts)
    // Repro for anomalyco/opencode#54910: after manual /compact, the next request still carries
    // (almost) the whole conversation, because the "recent" part kept verbatim is far larger than keep.tokens.
    import { appendFileSync } from "node:fs"
    import { Effect, Stream } from "effect"
    import { Llm, OpenCodeDriver } from "opencode-drive"
    
    const LOG = "/workspace/repro/requests.log"
    const filler = (tag: string, chars: number) => {
      let s = ""
      let i = 0
      while (s.length < chars) s += `${tag} line ${i++}: the agent inspected another part of the codebase and kept working. `
      return s
    }
    const SUMMARY = [
      "## Objective", "- test", "## Requirements", "- (none)", "## Decisions", "- (none)",
      "## Work State", "### Completed", "- (none)", "### Active", "- (none)", "### Blocked", "- (none)",
      "## Next Move", "1. (none)", "## Relevant Files", "- (none)", "## Important Context", "- (none)",
    ].join("\n")
    
    export default OpenCodeDriver.use(
      {
        ...(process.env.OC_DEV ? { opencode: { dev: process.env.OC_DEV } } : {}),
        tui: { recording: true },
        config: { compaction: { auto: false } },
      },
      ({ ui, llm }) =>
        Effect.gen(function* () {
          yield* llm.serve((request, index) => {
            const body = JSON.stringify(request.body)
            const isSummary = body.includes("You MUST use this format")
            const kind = isSummary ? "SUMMARIZE" : "CHAT"
            const hasBig = body.includes("BIG-TURN")
            appendFileSync(LOG, `#${index} ${kind} bodyChars=${body.length} approxTokens=${Math.round(body.length / 4)} containsBigTurnText=${hasBig}\n`)
            if (isSummary) return Stream.make(Llm.text(SUMMARY, { chunkSize: 500, delay: 0 }))
            if (body.includes("start the long task")) return Stream.make(Llm.text(filler("BIG-TURN", 240_000), { chunkSize: 8000, delay: 0 }))
            if (body.includes("question")) return Stream.make(Llm.text(filler("early", 20_000) + "\nDONE-" + index, { chunkSize: 4000, delay: 0 }))
            return Stream.make(Llm.text("ok after compaction " + index))
          })
          for (let i = 1; i <= 3; i++) {
            yield* ui.submit(`question ${i}`)
            yield* Effect.sleep(3000)
          }
          appendFileSync(LOG, "--- long autonomous turn (~60k tokens) ---\n")
          yield* ui.submit("start the long task")
          yield* Effect.sleep(8000)
          appendFileSync(LOG, "--- /compact ---\n")
          yield* ui.submit("/compact")
          yield* Effect.sleep(8000)
          yield* ui.screenshot("after-compact")
          appendFileSync(LOG, "--- next user message after compaction ---\n")
          yield* ui.submit("continue please")
          yield* Effect.sleep(6000)
          yield* ui.screenshot("after-next-message")
        }),
    )

    Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.

  2. Finn763 commented on Oct 19, 2026

    @Finn763
    Contributor

    I'd like to work on this — manual compaction completes but the following request still carries the full history, while recovery compaction truncates it. I'll trace the two paths and pin the difference with a test.

  3. zeetkt commented on Oct 20, 2026

    @zeetkt
    Author

    3. I'd like to work on this — manual compaction completes but the following request still carries the full history, while recovery compaction truncates it. I'll trace the two paths and pin the difference with a test.

    I haven't managed to replicate the problem recently (well, I haven't really TRIED to replicate it), but it hasn't occurred naturally lately, I suspect there's been some kind of fix.

  4. xxwtiancai commented on Oct 25, 2026

    @xxwtiancai

    Re-ran the reproduction script from the bot's comment on current dev (e0b6039e1) — still reproduces. The first request after /compact is ~66k tokens and contains the long turn verbatim, instead of summary + ~15k keep:

    #4 CHAT      approxTokens=20903
    --- /compact ---
    #5 SUMMARIZE approxTokens=81629 containsBigTurnText=true
    --- next user message after compaction ---
    #6 CHAT      approxTokens=65952 containsBigTurnText=true
    

    One difference from the original repro: the summarize request now includes the whole conversation (~82k tokens, presumably after #57091), but the keep window still preserves the long autonomous run verbatim.

    @Finn763 are you still planning to work on this? If not, I'd like to take it.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions