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

> Explore OpenCode's efficient session history management and compaction. Discover its LLM-driven approach to summarize and prune tool outputs, preserving context and avoiding token overflow.

- Repository: [Anomaly/opencode](https://github.com/anomalyco/opencode)
- Tags: deep-dive
- Published: 2026-02-16

---

**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`](https://github.com/anomalyco/opencode/blob/main/src/session/session.sql.ts) defines the database structure, while `Session.fromRow()` and `Session.toRow()` in [`src/session/index.ts`](https://github.com/anomalyco/opencode/blob/main/src/session/index.ts) handle serialization (lines 30-84).

**Messages** represent logical conversation turns. The `MessageV2` type in [`src/session/message-v2.ts`](https://github.com/anomalyco/opencode/blob/main/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`](https://github.com/anomalyco/opencode/blob/main/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()`.

```typescript
// 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`](https://github.com/anomalyco/opencode/blob/main/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`](https://github.com/anomalyco/opencode/blob/main/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`](https://github.com/anomalyco/opencode/blob/main/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()`.

```typescript
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`](https://github.com/anomalyco/opencode/blob/main/src/session/processor.ts)) returns `"compact"`, triggering `SessionCompaction.process()` (lines 101-229 of [`src/session/compaction.ts`](https://github.com/anomalyco/opencode/blob/main/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

```typescript
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/main/src/session/index.ts)](https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/session/index.ts#L92-L104).

### Manually Triggering Compaction

```typescript
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/main/src/session/compaction.ts)](https://github.com/anomalyco/opencode/blob/dev/packages/opencode/src/session/compaction.ts#L101-L229).

### Custom Compaction Prompt via Plugin

```typescript
// 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`](https://github.com/anomalyco/opencode/blob/main/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`](https://github.com/anomalyco/opencode/blob/main/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`](https://github.com/anomalyco/opencode/blob/main/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`](https://github.com/anomalyco/opencode/blob/main/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`](https://github.com/anomalyco/opencode/blob/main/src/server/routes/session.ts).