Model: openai/gpt-5.4
Generated: 2026-04-01
Book: Claude Code VS OpenCode: Architecture, Design and The Road Ahead
Chapter: 4 — Tool System Design
Token Usage: ~7,800 input + ~1,550 output
4.4 Tool Output Handling
Defining tools is only half the problem. Once a tool runs, the system must decide how to carry its result back into the agent loop. This is harder than it looks. A tool can return too much text, produce metadata the model needs but the user should not see directly, stream progress over time rather than finish instantly, or emit output that is useful for storage but too large for prompt inclusion. Tool output handling is therefore a core part of context engineering.
All three systems in this comparison recognize the same basic constraint: raw tool output cannot be passed back to the model without bounds. A single large command result, giant file read, or verbose web fetch can consume the context window, drown out relevant information, and degrade the next reasoning step. For that reason, each system implements some combination of truncation, structured metadata, and staged rendering.
OpenCode handles this directly in tool/tool.ts. After a tool executes, Tool.define() automatically routes the textual output through Truncate.output(...) unless the tool has already declared its own truncation behavior by setting result.metadata.truncated. This is an important framework-level decision. Instead of trusting each tool author to remember output budgeting, OpenCode makes bounded output the default behavior of the tool runtime.
The return object in OpenCode is also structured rather than ad hoc. A tool returns title, metadata, output, and optionally attachments. The metadata() callback exposed in the execution context lets the tool annotate itself during execution, while the final post-processing layer can append truncation indicators and an outputPath when the content is too large and has been offloaded. The result is then stored in session state as part of a multi-part message structure. In other words, tool output is not just printed; it becomes a typed artifact in the conversation log.
This is one of OpenCode’s understated strengths. By treating tool results as structured session parts rather than transient terminal noise, it preserves the possibility of later compaction, replay, summarization, and UI-specific rendering. The tool result is both machine-usable and persistence-friendly.
Claude Code generalizes this idea much further. In /src/Tool.ts, tools return a typed ToolResult, and the interface includes explicit mechanisms for mapping content into Anthropic tool-result blocks, rendering result messages, extracting searchable transcript text, rendering progress messages, and grouping parallel tool uses. This is a broader notion of output handling than simple truncation. Claude Code treats tool output as something that may need to be represented differently for different audiences: the model, the transcript indexer, the live UI, the brief view, and the progress renderer.
A major Claude Code addition is progress events. Many tools do not simply start and end. They fetch, stream, search, spawn sub-processes, or orchestrate subagents. Claude Code therefore includes structured progress types such as BashProgress, MCPProgress, SkillToolProgress, TaskOutputProgress, and WebSearchProgress. A tool can emit intermediate progress updates while still producing a final result object at the end. Architecturally, this is significant because it separates execution observability from final output.
Claude Code also explicitly tracks maximum result size through maxResultSizeChars. When a result exceeds that bound, the system can persist the full output to disk and give the model a preview plus a file path instead. This pattern is similar in spirit to OpenCode’s outputPath, but it is more deeply integrated into the product’s rendering and transcript systems. The runtime is not only preventing overflow; it is deciding how overflow should still remain inspectable.
Another important detail is that Claude Code distinguishes between model-facing serialization and user-facing rendering. The content used in mapToolResultToToolResultBlockParam() is not necessarily identical to the content shown in the terminal UI or indexed in transcript search. This is good ACI design. The model needs concise, structured, semantically important information. The user may want richer formatting or progress summaries. The search layer needs flattened visible text. Treating these as separate surfaces avoids forcing one representation to serve incompatible purposes.
OMO inherits OpenCode’s basic tool result model, but because it is implemented through the plugin layer it encounters a specific problem: OpenCode’s generic fromPlugin() wrapper can overwrite plugin metadata with truncation-related metadata. OMO compensates for this through its own metadata store in src/features/tool-metadata-store/store.ts. During execution, a tool can stash pending metadata keyed by sessionID and callID. Then, in src/plugin/tool-execute-after.ts, OMO consumes that stored metadata and merges it back into the tool result before the session processor finalizes the message.
This is a small but revealing design move. It shows what happens when an orchestration layer has to preserve richer semantics than the host runtime originally exposed through the plugin bridge. OMO is effectively repairing a metadata-loss boundary so that higher-level orchestration tools can carry titles, session identifiers, and custom annotations through the full execution pipeline.
OMO also adds a dedicated post-tool truncation hook in src/hooks/tool-output-truncator.ts. The hook targets selected high-volume tools such as grep, glob, lsp_diagnostics, ast_grep_search, interactive_bash, skill_mcp, and webfetch, with tighter limits for especially noisy sources like web pages. The truncator operates after execution and can be configured to apply broadly. This gives OMO a second chance to enforce context discipline even when the base tool or host runtime is not enough.
This layered output handling is especially important in a multi-agent system. A background agent may produce a large amount of intermediate reasoning or tool output that the parent agent does not need in full. Without aggressive output shaping, subagent orchestration rapidly becomes a context-window disaster. OMO’s post-tool hooks are therefore not just convenience features. They are essential for keeping multi-agent work economically viable.
Across all three systems, a common pattern emerges. First, tool output is structured rather than free-form. Second, large outputs are truncated, summarized, or spilled to disk. Third, metadata is preserved as a separate channel from human-readable text. Fourth, there is a growing separation between execution progress, final machine-facing result, and final user-facing rendering.
This has a direct implication for agent design. When people discuss tool quality, they often focus on input schema and capability coverage. But output handling may be equally important. A great tool with sloppy output handling can still harm the system by flooding context, obscuring key facts, or losing essential metadata. Conversely, a well-designed output pipeline can make even noisy external systems usable by converting them into bounded, structured observations.
The larger lesson is that tool output is part of the agent’s perception system. If tool definitions specify what the agent can do, output handling specifies what the agent can successfully understand afterward. In an autonomous coding agent, that distinction is foundational.