Model: openai/gpt-5.4 Generated: 2026-04-01 Book: Claude Code VS OpenCode: Architecture, Design and The Road Ahead Chapter: 5 — Session and Context Management Token Usage: Approx. 1,520 output tokens for this section
5.2 Message Structure
If session persistence defines where an agent remembers, message structure defines what it remembers. Modern coding agents do not store a conversation as a simple list of strings. They store heterogeneous, multi-part records containing user text, assistant output, reasoning traces, tool calls, files, patches, snapshots, attachments, and system events. This richer structure is essential because coding work is not just dialogue. It is dialogue interleaved with actions.
OpenCode: MessageV2 as a typed multi-part object
OpenCode’s most explicit message model appears in session/message-v2.ts. The key design is a parts array governed by discriminated unions. In plain English, a discriminated union is a typed structure where every variant contains a field such as type, and that field tells the program which shape of data follows. This pattern is common in modern typed programming, although the term itself is not always highlighted in introductory CS textbooks.
In OpenCode, the message does not flatten everything into raw text. Instead, it decomposes content into typed parts such as:
textreasoningtoolfilesnapshotpatchcompactionretrystep-start/step-finishsubtaskagent
This is a very agent-native design. A file attachment is not forced into plain text. A patch is not merely prose that says “I changed three files”; it is represented as a patch part with file metadata. A reasoning block is not mixed with user-visible output. A tool call has state transitions such as pending, running, completed, and error, each with its own structure.
The result is a message model that can support sophisticated behavior: structured rendering in a TUI, selective compaction, replay, tool-result pruning, cost accounting, patch recovery, and machine-readable analytics. It also means OpenCode can convert stored messages into provider-facing model messages later, rather than storing only the provider-ready form.
OpenCode’s message model is therefore not just a transport format. It is an internal semantic representation of agent work.
Claude Code: TranscriptMessage plus content blocks
Claude Code’s structure is different but no less sophisticated. The persistent layer uses TranscriptMessage in src/types/logs.ts, while the runtime message system uses message variants such as user, assistant, attachment, and system. Each of those can carry structured content blocks rather than a single string.
This block-oriented design closely follows Anthropic-style API messaging. A single assistant message may contain visible text, thinking blocks, tool-use blocks, and other structured content. Likewise, a user message may include text, tool results, images, or documents. In other words, Claude Code stores message content in a shape that is already aligned with model interaction.
This has two consequences. First, Claude Code’s transcript format is naturally suited to replaying the exact conversation into the next model call. Second, it makes the persistent transcript feel like an execution log of API-compatible blocks. OpenCode, by contrast, feels more like a typed internal domain model that can later be transformed into API messages.
The Claude Code model also extends beyond ordinary dialogue. logs.ts defines transcript-adjacent entry types for worktree state, content replacement, task summaries, context-collapse commits, attribution snapshots, and more. So while the main conversation is block-based, the full session history is actually a mixed event stream.
Extended thinking and the separation of reasoning from output
One of the most important message-structure questions in modern agents is how to handle reasoning. Claude-family systems often distinguish between user-visible answer text and internal or semi-internal “thinking” content. In Claude Code, the codebase explicitly handles thinking and redacted_thinking blocks during token estimation and compaction. This shows that reasoning is stored as its own content type rather than being fused into ordinary assistant prose.
That separation is architecturally significant. It allows the system to:
- render only final output to the user,
- preserve or strip reasoning depending on policy,
- estimate token costs more precisely,
- compact conversations without losing visible intent,
- recover from malformed ordering problems involving thinking and tool blocks.
OpenCode has a parallel concept through reasoning parts in MessageV2. Again, the idea is not that the assistant’s chain-of-thought is dumped into one blob. Instead, reasoning is recognized as a distinct layer of the message. That makes it easier to keep reasoning structurally separate from the answer text while still tracking it as part of the execution history.
This separation is one of the defining differences between agent-era message systems and classic chatbot transcripts. In a simple chatbot, “the answer” is a string. In a coding agent, “the answer” may be only one visible slice of a larger structured action record.
Why multi-part messages matter
The practical value of multi-part messages becomes obvious in coding workflows.
When an agent reads a file, the event is not just “assistant said something.” It may involve a tool-use block, a tool-result block, a file attachment, a reasoning segment explaining why the file matters, and a later patch block. If all of this is flattened into plain text, the system loses operational meaning. It becomes much harder to compact safely, recover from interruption, or resume a tool chain without ambiguity.
Typed parts preserve semantics. They let the platform answer questions like:
- Which content came from a tool?
- Which content was visible output?
- Which content was hidden reasoning?
- Which files were attached or edited?
- Which message boundary should survive compaction?
- Which tool results can be cleared without harming continuity?
This is why modern agent systems increasingly resemble miniature event-sourced operating systems rather than chat logs.
Explaining JSONL in context
Claude Code persists transcript entries in JSONL. Since this term is often unfamiliar outside systems practice, it is worth defining carefully. JSONL means JSON Lines: one JSON object per line in a plain text file. Each line stands on its own as valid JSON, and the file as a whole is a sequence of records rather than one enclosing array.
Example idea:
{"type":"user", ...}
{"type":"assistant", ...}
{"type":"summary", ...}
Why use it? Because it is easy to append. A program can write one new line at a time without rewriting the whole file. That makes JSONL ideal for streaming systems, logs, and large incremental transcripts. It is simple in practice, but it is better understood as an engineering convention than as a classic textbook data model.
Comparative reading
OpenCode’s MessageV2 is more explicit as an internal ontology of agent actions. Claude Code’s TranscriptMessage ecosystem is more log-oriented and closer to API block semantics. OMO inherits OpenCode’s part-based structure and then overlays orchestration behaviors on top of it.
The tradeoff is similar to the persistence tradeoff in the previous section. OpenCode optimizes for typed internal manipulation. Claude Code optimizes for durable replay and incremental transcript growth. Both are valid, but they reflect different priorities.
Design lesson
The deeper lesson is that message structure should be designed for the actual unit of work in an agent system. That unit is not a sentence. It is a bundle of intent, reasoning, tool interaction, and artifact mutation. Systems that preserve that structure gain better compaction, better recovery, better analytics, and better UI rendering.
If future coding agents converge on a common message standard, it will likely look less like “chat messages” and more like a typed graph of conversational and operational blocks. OpenCode and Claude Code approach that future from different directions, but both already show that plain text transcripts are no longer enough.