DashboardSession ses_3882bf5d

Session ses_3882bf5d

Full ID: ses_3882bf5d7ffe23xrE7KdYTSuHZ
Messages: 5
Distillations: 0
Time range: 2026-02-19 21:35:39 — 2026-02-19 21:35:53

Conversation (5 messages)

My long-term memory has a note about the compaction overflow check, but not the full mechanism. Let me read the source directly.

glob
/home/byk/Code/opencode/packages/opencode/src/session/compaction.ts
claude-sonnet-4-6 · 2026-02-19 21:35:39 · ~52 tokens
read
<path>/home/byk/Code/opencode/packages/opencode/src/session/compaction.ts</path>
<type>file</type>
<content>1: import { BusEvent } from "@/bus/bus-event"
2: import { Bus } from "@/bus"
3: import { Session } from "."
4: import { Identifier } from "../id/id"
5: import { Instance } from "../project/instance"
6: import { Provider } from "../provider/provider"
7: import { MessageV2 } from "./message-v2"
8: import z from "zod"
9: import { SessionPrompt } from "./prompt"
10: import { Token } from "../util/token"
11: import { Log } from "../util/log"
12: import { SessionProcessor } from "./processor"
13: import { fn } from "@/util/fn"
14: import { Agent } from "@/agent/agent"
15: import { Plugin } from "@/plugin"
16: import { Config } from "@/config/config"
17: 
18: export namespace SessionCompaction {
19:   const log = Log.create({ service: "session.compaction" })
20: 
21:   export const Event = {
22:     Compacted: BusEvent.define(
23:       "session.compacted",
24:       z.object({
25:     ...
claude-sonnet-4-6 · 2026-02-19 21:35:44 · ~2.9K tokens

Now let me also quickly check how compaction is triggered (when isOverflow is checked):

grep
Found 20 matches
/home/byk/Code/opencode/packages/opencode/src/session/message-v2.ts:
  Line 160:     type: z.literal("compaction"),
  Line 498:           if (part.type === "compaction") {
  Line 650:         msg.parts.some((part) => part.type === "compaction")

/home/byk/Code/opencode/packages/opencode/src/session/prompt.ts:
  Line 13: import { SessionCompaction } from "./compaction"
  Line 289:         const task = msg.parts.filter((part) => part.type === "compaction" || part.type === "subtask")
  Line 487:       // pending compaction
  Line 488:       if (task?.type === "compaction") {
  Line 500:       // context overflow, needs compaction
  Line 504:         (await SessionCompaction.isOverflow({ tokens: lastFinished.tokens, model }))

/home/byk/Code/opencode/packages/opencode/src/session/processor.ts:
  Line 15: import { SessionCompaction } from "./compaction"
  Line 274:                   if (await SessionCompaction.isOverflow({ tokens: usage.tokens, model: input.model })) ...
claude-sonnet-4-6 · 2026-02-19 21:35:46 · ~437 tokens
read
<path>/home/byk/Code/opencode/packages/opencode/src/session/processor.ts</path>
<type>file</type>
<content>265:                         files: patch.files,
266:                       })
267:                     }
268:                     snapshot = undefined
269:                   }
270:                   SessionSummary.summarize({
271:                     sessionID: input.sessionID,
272:                     messageID: input.assistantMessage.parentID,
273:                   })
274:                   if (await SessionCompaction.isOverflow({ tokens: usage.tokens, model: input.model })) {
275:                     needsCompaction = true
276:                   }
277:                   break
278: 
279:                 case "text-start":
280:                   currentText = {
281:                     id: Identifier.ascending("part"),
282:                     messageID: input.assistantMessage.id,
283:                     sessionID: input.assistantMessage.sessionID,
284:                     ...
read
<path>/home/byk/Code/opencode/packages/opencode/src/session/prompt.ts</path>
<type>file</type>
<content>480:             synthetic: true,
481:           } satisfies MessageV2.TextPart)
482:         }
483: 
484:         continue
485:       }
486: 
487:       // pending compaction
488:       if (task?.type === "compaction") {
489:         const result = await SessionCompaction.process({
490:           messages: msgs,
491:           parentID: lastUser.id,
492:           abort,
493:           sessionID,
494:           auto: task.auto,
495:         })
496:         if (result === "stop") break
497:         continue
498:       }
499: 
500:       // context overflow, needs compaction
501:       if (
502:         lastFinished &&
503:         lastFinished.summary !== true &&
504:         (await SessionCompaction.isOverflow({ tokens: lastFinished.tokens, model }))
505:       ) {
506:         await SessionCompaction.create({
507:           sessionID,
508:           agent: lastUser.agent,
509:     ...
claude-sonnet-4-6 · 2026-02-19 21:35:49 · ~989 tokens

Here's how OpenCode compaction works:

Trigger: After each AI step finishes (in processor.ts:274), it checks isOverflow(). It also checks on the main prompt loop in prompt.ts:500-513.

Overflow condition (compaction.ts:30-38):

count = input + cache.read + output
usable = model.limit.input ?? (context - output_reservation)
overflow = count > usable

Importantly, this is different from the UI's context inspector formula (which also includes reasoning and cache.write).

What happens on overflow:

  1. A compaction task is queued via SessionCompaction.create() — this adds a synthetic compaction part to the user message.
  2. On the next loop iteration, prompt.ts:488 detects the pending compaction and calls SessionCompaction.process().

process() — what it actually does:

  1. Creates a new assistant message with summary: true.
  2. Takes the full conversation history (MessageV2.toModelMessages(messages, model)) and appends a final user message with the prompt:

    "Provide a detailed prompt for continuing our conversation above. Focus on information that would be helpful for continuing the conversation, including what we did, what we're doing, which files we're working on, and what we're going to do next considering new session will not have access to our conversation."

  3. The model generates a summary/continuation prompt as an assistant message, stored with summary: true.
  4. On subsequent turns, messages before the summary message are dropped — only the summary message + newer messages are included in context.

There's also a prune() step (runs first) that clears old completed tool outputs from older parts to reduce token count before the summary is generated.

So the key difference from your eval's "default" mode: The eval simulates "last 80k tokens" by truncating, but real compaction generates a rolling LLM summary of everything before the cutoff. ...

claude-sonnet-4-6 · 2026-02-19 21:35:53 · ~529 tokens