Skip to content

V2: compaction fires on first request / loops when compaction.buffer >= model context limit #56783

Closed

Description

@aner-dev

Summary

With compaction.buffer set to a value greater than or equal to the model's context limit, V2 starts automatic compaction on the very first request and re-triggers it on every turn. This emits meaningless structured summaries at session start and sometimes surfaces an internal Session.StepFailedError: Nothing to compact yet.

Environment

  • opencode v2.0.32 (Linux x64; also confirmed config is read by V1 1.19.0)
  • Session model: opencode/mimo-v2.6-flash-free

Reproduction

opencode.json:

{
  "compaction": {
    "auto": true,
    "buffer": 600000,
    "keep": { "tokens": 15000 }
  }
}
  1. Start a V2 session (any directory with an AGENTS.md).
  2. Send a single first message.
  3. Observe the message stream: a compaction message is created immediately after the user message (reason: "auto"), summarizing an empty history, and repeats every 1-2 turns.

Startup log line:

ERROR "Failed to drain Session" cause="Session.StepFailedError: Nothing to compact yet
    at SessionRunner.runStep
    at SessionRunner.drain
    at ServerProcess.start"

Expected

  • An oversized buffer should be clamped to < context limit (or rejected with a clear warning at config load).
  • Compaction should not run when there is no older history to compact.
  • The internal StepFailedError should not surface as a user-facing failure at server start.

Actual

The trigger is estimated_tokens > context_limit - max(requested_output_tokens, buffer) (docs state buffer default = 10% of the limit). When buffer >= context_limit, the right-hand side is negative, so the condition is always true and compaction fires on every step, including the first. Because buffer is a single global value evaluated against each model's own limit, a value chosen for a large-context model (e.g. 400000 for a 1M model) breaks every smaller model.

Additional context / feature request

buffer and keep.tokens are only configurable globally in the top-level compaction block; per-model settings.compaction only accepts type: native|summary. This forces a single compaction policy across models with very different context windows. It would help to allow a per-model (or per-provider) buffer, and/or support an absolute "compact at N tokens" threshold, so users can pick different compaction points for a 200k model vs a 1M model without a global value that is unsafe for one of them.

Activity

  1. reynold-hu commented on Oct 18, 2026

    @reynold-hu
    Contributor

    Opened #56786 with a fix: the ceiling now keeps at most 90% of the window free, automatic compaction waits for an older assistant reply before it summarizes, and an automatic no-op is reported as skipped instead of a failed compaction. The new test fails on the parent commit (both cases complete) and passes with the fix; session-runner's existing buffer cases still pass.

  2. opencode-agent commented on Oct 18, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I was able to reproduce this on v2.0.32, and it still happens on the latest dev (da9d436).

    Steps:

    1. Use a model with a 128k context window.
    2. Set "compaction": { "auto": true, "buffer": 600000, "keep": { "tokens": 15000 } }.
    3. Send hello, then a second message.

    An automatic compaction starts straight after each user message, including the very first one, and neither message gets a normal reply. The recording and screenshot below show it. The script below reproduces it with a simulated model, so the compaction summaries fail on a template check. What matters is that compaction runs at all on a near-empty session. I didn't see the exact Nothing to compact yet log line in this run, but compaction fired on every turn as you described.

    As you worked out, the automatic trigger uses window - buffer with no lower limit. A buffer at or above the model's window makes compaction due on every turn. Until this is addressed, keeping buffer below the smallest context window you use avoids it.

    The per-model buffer part of your request is tracked in #43703, and #56443 is an open PR for it.

    recording.mp4

    Screenshot

    Reproduction script (drive.ts)
    // Repro for anomalyco/opencode#56783: compaction.buffer >= model context limit
    // makes auto compaction fire on every turn, starting with the very first one.
    // Simulated model has limit.context = 128000; buffer = 600000.
    import { Effect } from "effect"
    import { Llm, OpenCodeDriver } from "opencode-drive"
    
    export default OpenCodeDriver.use(
      {
        ...(process.env.OC_DEV ? { opencode: { dev: process.env.OC_DEV } } : {}),
        tui: { recording: true },
        project: { git: true, files: { "AGENTS.md": "# Project\n" } },
        config: {
          compaction: { auto: true, buffer: 600000, keep: { tokens: 15000 } },
        } as any,
      },
      ({ ui, llm }) =>
        Effect.gen(function* () {
          // Enough responses for the reply and any (unexpected) compaction summaries.
          for (let i = 0; i < 6; i++) yield* llm.queue(Llm.text(`Reply number ${i + 1}.`))
          yield* ui.submit("hello")
          yield* Effect.sleep(6000)
          yield* ui.screenshot("after-first-message")
          yield* ui.submit("second message")
          yield* Effect.sleep(6000)
          yield* ui.screenshot("after-second-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.

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