From b79cad5ec805b6aa61d0f0bdaca5ff15de613320 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Tue, 25 Aug 2026 16:58:39 -0500 Subject: [PATCH] fix(core): respect automatic compaction opt-out on overflow (#45036) Co-authored-by: rekram1-node --- packages/core/src/session/compaction.ts | 2 ++ packages/core/src/session/runner/llm.ts | 1 + packages/core/test/session-runner.test.ts | 30 ++++++++++++++++++++ packages/www/src/docs/content/compaction.mdx | 2 +- specs/v2/session.md | 2 +- 5 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/core/src/session/compaction.ts b/packages/core/src/session/compaction.ts index 381a52822ab..420d6992d33 100644 --- a/packages/core/src/session/compaction.ts +++ b/packages/core/src/session/compaction.ts @@ -103,6 +103,7 @@ export type Outcome = | Pick export interface Interface extends State.Transformable { + readonly enabled: () => boolean readonly required: (input: RequiredInput) => boolean readonly compact: (input: AutoInput) => Effect.Effect readonly compactManual: (input: ManualInput) => Effect.Effect @@ -405,6 +406,7 @@ const make = (dependencies: Dependencies) => { return Service.of({ transform: state.transform, reload: state.reload, + enabled: () => state.get().auto, required, compact, compactManual, diff --git a/packages/core/src/session/runner/llm.ts b/packages/core/src/session/runner/llm.ts index 9a666a4f955..02453f591d0 100644 --- a/packages/core/src/session/runner/llm.ts +++ b/packages/core/src/session/runner/llm.ts @@ -508,6 +508,7 @@ const layer = Layer.effect( // restart the step instead of surfacing the provider error. if ( recoverOverflow && + compaction.enabled() && !publisher.record().outputStarted && isContextOverflowFailure(overflowFailure ?? streamFailure) && (yield* restore(compaction.compact(compactionInput))).status === "completed" diff --git a/packages/core/test/session-runner.test.ts b/packages/core/test/session-runner.test.ts index 6b8f43e4ce6..077ceee53a6 100644 --- a/packages/core/test/session-runner.test.ts +++ b/packages/core/test/session-runner.test.ts @@ -33,6 +33,7 @@ import { Session } from "@opencode-ai/core/session" import { Snapshot } from "@opencode-ai/core/snapshot" import { SessionEvent } from "@opencode-ai/core/session/event" import { SessionContext } from "@opencode-ai/core/session/context" +import { SessionCompaction } from "@opencode-ai/core/session/compaction" import { SessionInbox } from "@opencode-ai/core/session/inbox" import { SessionMessage } from "@opencode-ai/core/session/message" import { SessionModelRequest } from "@opencode-ai/core/session/model-request" @@ -458,6 +459,7 @@ const it = testEffect( Config.node, Snapshot.node, SessionContext.node, + SessionCompaction.node, SessionModelRequest.node, SessionRunnerLLM.node, SessionExecution.node, @@ -2560,6 +2562,34 @@ describe("SessionRunnerLLM", () => { }), ) + it.effect("does not recover provider context overflow when automatic compaction is disabled", () => + Effect.gen(function* () { + const session = yield* setupOverflowRecovery + const compaction = yield* SessionCompaction.Service + yield* compaction.transform((draft) => draft.configure({ auto: false })) + yield* TestLLM.push( + [LLMEvent.providerError({ message: "prompt too long", classification: "context-overflow" })], + TestLLM.text("Must not compact", "text-unexpected-summary"), + TestLLM.text("Must not retry", "text-unexpected-retry"), + ) + yield* admit(session, "Continue") + expect((yield* session.resume(sessionID).pipe(Effect.flip)).message).toBe("prompt too long") + + expect(requests).toHaveLength(1) + expect(yield* session.context(sessionID)).toContainEqual( + expect.objectContaining({ + type: "assistant", + finish: "error", + error: expect.objectContaining({ message: "prompt too long" }), + }), + ) + expect(yield* session.context(sessionID)).not.toContainEqual(expect.objectContaining({ type: "compaction" })) + expect(yield* recordedEventTypes(sessionID)).not.toContain( + Bus.versionedType(SessionEvent.Compaction.Started.type, 1), + ) + }), + ) + it.effect("recovers from provider context overflow without a configured context limit", () => Effect.gen(function* () { const session = yield* setupOverflowRecovery diff --git a/packages/www/src/docs/content/compaction.mdx b/packages/www/src/docs/content/compaction.mdx index 311ba425252..6f8a165e1d4 100644 --- a/packages/www/src/docs/content/compaction.mdx +++ b/packages/www/src/docs/content/compaction.mdx @@ -67,7 +67,7 @@ Add `compaction` to any [OpenCode configuration file](/config): | Field | Default | V2 behavior | | ------------- | ------: | --------------------------------------------------------------------------------------------------------------------------------------------- | -| `auto` | `true` | Runs the preflight context-size check. It does not disable manual compaction or one-shot provider-overflow recovery. | +| `auto` | `true` | Enables preflight context-size checks and one-shot provider-overflow recovery. Disabling it does not affect manual compaction. | | `keep.tokens` | `15000` | Approximate number of tokens from the newest serialized conversation context to retain beside the summary. | | `buffer` | `20000` | Safety reserve below an explicit input limit. Without one, it is the minimum context reserve and the model output allowance wins when larger. | diff --git a/specs/v2/session.md b/specs/v2/session.md index 3e04396041b..3a055108745 100644 --- a/specs/v2/session.md +++ b/specs/v2/session.md @@ -73,7 +73,7 @@ Before each Step, the runner estimates the complete model-visible request agains The full transcript remains durable. Active model history after the compaction boundary contains the summary and retained recent context; provider-native continuation state does not cross that boundary. -If the provider reports context overflow before durable assistant output or tool execution, the runner may perform one overflow-triggered compaction and rebuild the same logical Step. A second overflow or any overflow after durable output is terminal. +If automatic compaction is enabled and the provider reports context overflow before durable assistant output or tool execution, the runner may perform one overflow-triggered compaction and rebuild the same logical Step. A second overflow, any overflow after durable output, or an overflow when automatic compaction is disabled is terminal. ## Durable Events Are Session-Scoped