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:
- Current token count: Sums input, output, cache read, and cache write tokens (or uses a supplied
total) - Reserved buffer: Subtracts
COMPACTION_BUFFER(20,000 tokens) or the provider-specific max-output limit - 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:
- Creates a synthetic assistant message with
mode: "compaction"andsummary: true - Builds a prompt using the default template (lines 151-176) plus any plugin-supplied context via
Plugin.trigger("experimental.session.compacting")(lines 145-151) - Runs the LLM using the hidden compaction agent (
Agent.get("compaction")) - Stores the result as a new
compactionpart 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 withmode: "compaction"and emittingSessionCompaction.Event.Compactedevents. - 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →