How OpenCode Manages Session History and Compaction: A Deep Dive into the Architecture

OpenCode stores every interaction as structured messages with granular parts, automatically detects token overflow via SessionCompaction.isOverflow(), and uses an LLM-driven compaction process to summarize and prune old tool outputs while preserving conversation context.

OpenCode is an open-source AI coding assistant that manages long-running conversations through a sophisticated session history and compaction system. Understanding how OpenCode handles session history and compaction is essential for developers building on the platform or optimizing token usage in large contexts. The architecture separates session metadata, message logic, and granular parts into distinct layers, enabling efficient retrieval and automatic summarization when contexts grow too large.

Session History Architecture: Messages, Parts, and Metadata

OpenCode models session history as a hierarchy of three components stored across SQL tables and TypeScript types.

Session Records maintain meta-information including titles, timestamps, and compaction flags. The SessionTable schema in src/session/session.sql.ts defines the database structure, while Session.fromRow() and Session.toRow() in src/session/index.ts handle serialization (lines 30-84).

Messages represent logical conversation turns. The MessageV2 type in src/session/message-v2.ts encapsulates user and assistant turns, with MessageV2.stream() providing an async iterator for database rows.

Parts store granular payloads within messages. The MessageV2.Part schema (lines 190-210 of src/session/message-v2.ts) handles text content, tool calls, tool results, and compaction markers—special parts with type: "compaction" that indicate trimmed outputs (lines 298-306 and 620-622).

Retrieving Session History

Accessing historical context uses the Session.messages() method, which delegates to MessageV2.stream().

// Get the most recent N messages for a session (including all parts)
const recent = await Session.messages({ sessionID, limit: 50 });

This call reads database rows in chronological order, reverses them to present the newest last, and returns a fully-typed MessageV2.WithParts[] array. The implementation resides in src/session/index.ts at lines 92-104.

Detecting Token Overflow and Triggering Compaction

OpenCode monitors token usage to determine when session history exceeds safe limits. The SessionCompaction.isOverflow() static method in src/session/compaction.ts (lines 32-48) performs the calculation:

  1. Current token count: Sums input, output, cache read, and cache write tokens (or uses a supplied total)
  2. Reserved buffer: Subtracts COMPACTION_BUFFER (20,000 tokens) or the provider-specific max-output limit
  3. Usable quota: Compares against the model's input limit

When the count meets or exceeds the usable quota, the method returns true, signaling that compaction is required.

Pruning Stale Tool Output

Before generating a compaction summary, OpenCode optionally prunes old tool calls to reduce token pressure. The SessionCompaction.prune() method (lines 55-99 of src/session/compaction.ts) iterates backwards through messages, aggregates token estimates for tool calls, and marks parts for clearing by setting part.state.time.compacted = Date.now().

await SessionCompaction.prune({ sessionID });

This marks tool outputs as compacted without deleting them, allowing the system to distinguish between active and historical tool data.

Creating Compaction Summaries with LLM

When overflow is detected, the SessionProcessor (line 412 of src/session/processor.ts) returns "compact", triggering SessionCompaction.process() (lines 101-229 of src/session/compaction.ts). This method:

  1. Creates a synthetic assistant message with mode: "compaction" and summary: true
  2. Builds a prompt using the default template (lines 151-176) plus any plugin-supplied context via Plugin.trigger("experimental.session.compacting") (lines 145-151)
  3. Runs the LLM using the hidden compaction agent (Agent.get("compaction"))
  4. Stores the result as a new compaction part on the synthetic message

If the agent returns "continue" and the request was auto-triggered, OpenCode adds a follow-up "continue" user message prompting the assistant to proceed or ask for clarification (lines 200-225).

Updating Session State and Events

After compaction finishes, the session_compacting timestamp stored in SessionTable.time_compacting is updated via Session.updateMessage (see insertion at lines 14-20 of the compaction process). The system emits SessionCompaction.Event.Compacted (lines 21-27) to notify UI components that the session has been compacted.

Practical Code Examples

Fetching Session History with Compaction Markers

import { Session } from "@/session";

async function printHistory(sessionID: string) {
  const msgs = await Session.messages({ sessionID });
  for (const msg of msgs) {
    console.log(`${msg.info.role.toUpperCase()} – ${msg.info.id}`);
    for (const part of msg.parts) {
      const tag = part.type === "compaction" ? "[COMPACTED]" : "";
      console.log(`  ${part.type} ${tag}: ${part.state.output?.slice(0, 80)}…`);
    }
  }
}

Uses Session.messages → [src/session/index.ts](https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/session/index.ts#L92-L104).

Manually Triggering Compaction

import { SessionCompaction } from "@/session/compaction";

async function forceCompact(sessionID: string) {
  // Force a compaction even if overflow detection is disabled
  const lastUserMsg = await Session.messages({ sessionID, limit: 1 })
    .then(m => m.find(m => m.info.role === "user")?.info.id);
  if (!lastUserMsg) throw new Error("No user message to compact");

  await SessionCompaction.process({
    parentID: lastUserMsg,
    messages: await Session.messages({ sessionID }),
    sessionID,
    abort: new AbortController().signal,
    auto: false,           // manual mode
  });
}

Calls SessionCompaction.process → lines 101‑229 of [src/session/compaction.ts](https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/session/compaction.ts#L101-L229).

Custom Compaction Prompt via Plugin

// In a plugin file
export const experimental = {
  "session.compacting": async ({ sessionID }, ctx) => ({
    prompt: `Summarize the last 5 user actions for session ${sessionID}.`,
    context: [],               // optional extra context parts
  })
};

When the compaction runs, Plugin.trigger("experimental.session.compacting", …) merges this output (see lines 145‑151 of compaction.ts).

Summary

  • OpenCode stores session history as hierarchical records in SessionTable, with messages containing granular parts that include text, tool calls, and compaction markers.
  • Token overflow detection uses SessionCompaction.isOverflow() to compare current usage against model limits minus a 20,000-token buffer.
  • Pruning marks stale tool outputs via SessionCompaction.prune() before summarization, preserving structure while reducing tokens.
  • Compaction generates LLM-driven summaries through SessionCompaction.process(), creating synthetic assistant messages with mode: "compaction" and emitting SessionCompaction.Event.Compacted events.
  • The architecture supports manual triggering via the compaction API and custom prompts through the plugin system.

Frequently Asked Questions

How does OpenCode determine when to compact a session?

OpenCode calculates token usage against model-specific limits using SessionCompaction.isOverflow() in src/session/compaction.ts. The method sums input, output, and cache tokens, then compares this total against the usable quota (model input limit minus a 20,000-token COMPACTION_BUFFER). When the count meets or exceeds this threshold, the system triggers automatic compaction.

What happens to tool outputs during compaction?

Before generating a summary, OpenCode optionally prunes stale tool calls via SessionCompaction.prune() (lines 55-99 of src/session/compaction.ts). This method iterates backwards through the session history, estimates token usage for tool outputs, and marks parts for clearing by setting part.state.time.compacted = Date.now(). The actual content is preserved in storage but excluded from future context windows.

Can developers customize the compaction behavior?

Yes, OpenCode supports custom compaction prompts through its plugin system. Developers can export an experimental.session.compacting handler that returns a custom prompt string and optional context parts. When SessionCompaction.process() runs, it triggers Plugin.trigger("experimental.session.compacting") (lines 145-151 of src/session/compaction.ts) to merge custom instructions with the default template before sending to the LLM.

How can I manually trigger compaction for testing?

Use the SessionCompaction.process() API with auto: false to force compaction regardless of token limits. First retrieve the session history via Session.messages(), identify the parent message ID (typically the last user message), then call SessionCompaction.process() with the session ID, messages array, and abort signal. This is the same method invoked by the HTTP endpoint POST /session/:id/compact defined in src/server/routes/session.ts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →