The Session Is the Product: History, Context, and Continuation
This chapter examines the OpenCode Session as a durable workflow rather than a transcript window. The practical question is what remains coherent when a user steers a running task, a tool fails, a client reconnects, or the model’s context window fills. The answer requires separating four things that look similar from the outside: input that has been admitted, history the model has actually seen, execution owned by the current process, and the compact representation used for a later provider turn.
Admission, history, and active execution
A short chat can treat a user message as one event: receive text, call a model, show a reply. A coding task needs a more careful sequence because requests can arrive while another request is still running. OpenCode’s V2 session API addresses this with a durable input inbox. A submitted prompt can be accepted first and remain pending until the runner can promote it into model-visible history.
That distinction gives “received” and “seen” different meanings. Admission records that OpenCode accepted the input. Promotion records the later transition that makes the user message part of the chronological history used by a provider turn. If context initialization is temporarily unavailable, the prompt can remain pending rather than forcing the model to work from an incomplete baseline. If a user sends a steering message during a continuing task, the runner can promote it at the next safe provider-turn boundary instead of changing the request that is already in flight.
Queued input has a different purpose from steering. A steering input is eligible while the current drain still needs another provider turn; a queued input waits until the current work would otherwise become idle, then is promoted in order. The distinction is a control decision, not a user-interface ornament. Steering changes the direction of an active line of work. Queuing asks OpenCode to finish the current line before beginning another one. The source describes both as durable inbox work, so a process can reason about them after the immediate request has ended.
Once input is promoted, the Session runner loads the current Location, selects the relevant agent and model, assembles context and tools, and performs provider turns. Local execution is coordinated separately from the durable session record. The process-local coordinator serializes execution for each Session while allowing different Sessions to run concurrently. A repeated wake can join active work or coalesce into one follow-up rather than launching overlapping drains for the same Session.
That coordinator is deliberately not another durable entity in the transcript. The source calls a running span a Session drain: it promotes eligible input and performs the provider turns needed until no immediate continuation remains. The active registry tells the API which foreground Sessions this process currently owns, but it is empty after a process restart. This is an important limit. The durable Session can tell a later process what was admitted and projected; it does not, by itself, prove exactly what an interrupted provider request had already caused outside the database.
The difference between a durable record and a live execution span becomes clearer in the event model. OpenCode assigns aggregate sequences to durable Session events and commits them transactionally. Events such as prompt admission, prompt promotion, agent or model changes, tool calls, tool successes or failures, compaction, and revert boundaries can be projected into queryable session and message records. The projection is the convenient read model; the event sequence is what gives clients an ordered history to replay and follow.
A projected message list is useful for a transcript, but it is not enough for every client. The API exposes session history as durable events after an aggregate sequence and can stream new durable events after the replay point. This supports a reconnect pattern: a client remembers the last durable sequence it observed, asks for events after that point, and then continues with newly committed events. Live-only fragments such as streaming text deltas are separate from this durable cursor. They can make a connected interface feel immediate, but they are not silently promoted into replayable history.
The event log and projection also have different failure surfaces. A projector can maintain a readable session representation from committed facts, while the event service retains the ordering and typed payloads needed to replay or rebuild it. Session tables store useful summaries such as title, selected model and agent, token counters, permissions, location, and timestamps; message and part tables store the projected conversation; input and context-epoch tables preserve pending work and the active context boundary. The point is not to make every internal table public. It is to give each consumer the representation appropriate to its job.
A client can therefore observe more than a final answer. It can list a Session, inspect projected messages, follow durable events, wait for execution to become idle, interrupt active work, or send another prompt. These operations are meaningful because the Session owns both conversational state and the transitions around it. The model remains a participant in the workflow, but it does not own the workflow’s admission, persistence, or execution identity.
The diagram shows why the Session is more than a conversation list. A prompt can be admitted without being visible to the model, a provider turn can loop through tool settlement, and compaction can change the active representation without erasing the historical record. Interruption can end current execution while leaving durable work for a later wake or explicit resume.
Compaction and the edge of recovery
Long-running repository work eventually creates a pressure problem. Every tool result and assistant turn can add useful evidence, but a provider has a finite context window. OpenCode’s answer is not to claim that the transcript is infinite. Before a V2 provider turn, the runner estimates the complete model-visible request and compares it with the selected model’s context limit after reserving headroom for output and configured compaction space.
When the request is too large and older complete turns are available, OpenCode creates a rolling checkpoint with a separate model call. The compaction prompt asks for a structured summary of the objective, important details, completed and active work, blockers, next moves, and relevant files, while preserving a bounded slice of recent context. Repeated compactions update the previous summary with newly compacted material. The full transcript remains a durable historical record, but the active model representation becomes a summary plus recent context rather than the entire prefix.
Compaction is a state transition with a commit boundary. The source publishes a started event and live progress, but only a completed compaction event changes the projected active history. The next provider attempt observes the completed checkpoint and renders a fresh baseline for the new Context Epoch. This prevents a half-written summary from becoming the only account of the earlier task. If the summary call fails or is interrupted, the previous active boundary remains in force.
A Context Epoch matters because a model request has more than ordinary user and assistant messages. OpenCode’s runtime context can include a baseline containing environment facts, instructions, and selected guidance. When compaction changes the active prefix, the provider should not be handed a stale baseline or opaque provider-native reasoning metadata that was valid only for the old request shape. The V2 lowering rules are conservative: native continuation metadata is reused only when the historical assistant model matches the selected continuation model and the relevant turn succeeded. A model switch can preserve visible reasoning text while dropping provider-specific metadata that may no longer be valid.
The same reasoning applies when a Session moves to another Location. The destination may have different files, instructions, skills, references, and provider-facing context. The source therefore treats movement as a reason to clear the active epoch and initialize a complete baseline for the destination before another prompt is promoted. Context is not a global string attached to the Session forever; it is a scoped, refreshable input to a particular provider turn.
Compaction also has an overflow recovery path. If a provider rejects a request for context overflow before durable assistant output or tool execution, the runner can attempt one overflow-triggered compaction even when its local estimate did not predict the problem. A successful checkpoint rebuilds the same logical turn with a remaining physical attempt. A second overflow, unavailable compaction, or overflow after durable output is treated as a terminal failure rather than an excuse to loop indefinitely. This is a careful boundary between recovering a request that has not produced side effects and replaying one whose effects are ambiguous.
Tool output creates a related but different pressure. OpenCode’s V2 output store bounds the text sent through the provider-facing channel. Its documented default boundary is 2,000 lines or 50 KiB, whichever is reached first. When a textual result exceeds that limit, the store keeps a beginning-and-end preview in the model-facing result and writes the complete received text to a managed output file. Structured metadata and supported media are preserved separately.
That managed file is not the same thing as durable Session history. It is temporary retained output and may be cleaned up after its retention period. The durable record is the bounded preview, which tells the model what was available without making the provider request unbounded. Nor is this store a universal process-memory safeguard. A producer that has already discarded bytes cannot later claim that the full output was retained, so capture and spooling limits belong at the producer boundary as well.
The boundaries become most important during interruption. When a provider stream or local tool execution is interrupted, the runner can clear active tool fibers, publish failures for projected calls that never settled, and finalize an incomplete assistant turn. The durable inbox is preserved for later work, but the runtime does not infer that an ambiguous provider request is safe to repeat. The repository explicitly defers general post-crash continuation recovery, durable multi-node ownership, and some retry/watchdog policy. An explicit resume can continue from durable projected history, but that is a deliberate operation, not proof that every external side effect will be replayed safely.
This is a restrained design for a system with real side effects. It accepts that the database can know some facts with high confidence and cannot know others without provider cooperation. A committed tool success is different from a tool call that was sent to a provider but whose response was lost. A durable prompt admission is different from a provider request that may have been dispatched. A live stream delta is different from a durable completion event. Keeping those distinctions visible makes the system more honest and gives clients a basis for recovery instead of encouraging duplicate actions.
The practical judgment is therefore not “Does OpenCode remember everything?” It is more precise: which part of the task is durable, which part is currently owned by the process, which context representation is active, and what transition is safe after failure? OpenCode’s Session architecture provides useful answers for admission, projection, compaction, and durable event replay. It does not promise universal crash recovery, unlimited context, or sandboxed side effects. Those limits are part of the Session’s meaning, not exceptions to it.
The next architectural question follows naturally. If a Session can preserve a task while changing context and continuing across turns, it also needs a stable way to select different models and providers without moving every provider quirk into the Session loop. That is the role of the catalog and adapter boundary.