a coding harness builds what the model sees, decides which actions it can request, runs those actions, and records the result. OpenCode's implementation lets us follow each boundary: what reaches the model, what a call can affect, and what survives a crash.

1. context: what is the model allowed to see?

there are three different states to keep straight. session state records messages, tool results, and jobs. working context is what the model receives for one step. workspace state is the filesystem, processes, and browser tabs. these states can disagree: a file can change without being read, or an old tool result can remain stored after it has left the model's context.

OpenCode selects the active history for each step. the request builder adds instructions and the tool definitions available to this agent, then sends messages and schemas to the model. the model cannot inspect the session store or workspace directly. it needs a tool call to observe or change either one.

How a model view is prepared, including later instructions For one model step, OpenCode selects agent and location, loads active history, combines instructions and admitted tool definitions, applies request hooks, and sends messages and schemas to the model. A later file read or skill load can discover new instruction text. That text enters durable state and can affect the next request, not the request that preceded its discovery. one request select scope agent + location load history active messages prepare request rules + tools + hooks model step messages + schemas what happens after that request can change the next one read file or load skill may reveal new guidance record instruction change eligible for the next request later guidance cannot retroactively affect an earlier tool choice
context is prepared for a particular step. discovered instructions take effect later; their place in history matters. selection · request preparation

instructions arrive at different times. project rules may be present at the start; a skill's description can appear before its body is loaded. a successful file read may discover a nearby AGENTS.md and add its rules for later steps. MCP server guidance can also enter context when relevant server tools are available.

OpenCode records changes to instruction sources in session history. that timing matters: a rule found after an edit could not have guided that edit. a context hook can change the outgoing view without rewriting the stored conversation; the request builder still rejects tool definitions a hook invents and maps permitted renames back to real handlers. this is the concrete version of the selective-context problem described in Anthropic's context engineering notes.

2. tools: the action space is an API

the tool list is the model's action space. for each request, OpenCode collects built-in, plugin, and MCP tools, then hides actions the agent is wholly forbidden to use. read, edit, shell, and subagent have their own schemas; other operations are discoverable through a compact catalog. seeing a schema tells the model how to ask for an action. it does not authorize the action.

From installed capability to one model observation Built-in tools, plugin tools, and MCP tools feed a registry. The registry filters what the selected agent may see and prepares descriptions for a model request. The model chooses a call. Built-in and MCP handlers check relevant actions and resources; custom plugin tools need their own checks. The call runs in an execution environment and returns an observation to the session. install / connect built-in tools plugin tools MCP tools compile the advertised action space agent eligibility + visible permissions + descriptions model selects a call advertised schema or discovered operation handler checks the proposed effect custom tools must supply their own checks normalize and record result observation for a later model step a visible tool description is not permission to execute it
the registry controls what is advertised; the handler and execution environment control what a selected call can do. registry and call path

a call has a second gate. a handler checks the proposed effect against ordered permission rules: the last matching rule wins, no match asks, and a saved approval cannot override an explicit denial. file tools check paths; the shell tool checks command resources and working directory. custom plugin tools need checks for their own effects. this is why hiding a tool from the model and permitting a particular call are separate decisions.

permission also differs from isolation. OpenCode's environment driver decides where file and process operations run. without a separate workspace driver, its local driver uses the host filesystem and process APIs. Codex, by comparison, combines approvals with a sandbox that enforces filesystem and network limits. approval asks whether a call may run; isolation limits where it can act.

OpenCode's compact catalog is exposed through Code Mode. a short JavaScript program can call registered tools and return selected results without putting every tool schema and full result in the request. those calls still reach their handlers. the interpreter's built-in fetch is a separate HTTP capability and needs its own network policy. Anthropic describes the broader context-saving pattern.

the return path needs just as much design. OpenCode normalizes tool content and caps ordinary local text at 2,000 lines or 50 KiB. if it cuts the response, it saves the full text in a temporary file with a seven-day cleanup policy. the model sees a bounded observation but can fetch omitted detail while the file exists. Cursor likewise places long tool and terminal output in files. truncation controls context size; a retrieval path controls what evidence is lost.

OpenCode's step executor can start independent local tool calls while the model's response is still streaming. it waits for all of them to settle before constructing the next model request.

harnesses also differ in how the model finds tools. Cursor stores MCP descriptions in a folder per server for search on demand. Prime Agent provides a persistent Python namespace that can inspect values and call host tools across turns; that worker has OS access. these interfaces change discovery and composition. the handler and runtime still determine authority.

3. plugins, hooks, MCP: three different boundaries

plugins are host-side code. OpenCode loads them with APIs to add tools (tool.transform), register hooks (tool.hook, session.hook), or configure MCP servers (mcp.transform). a plugin runs with the host's privileges.

hooks are callbacks at named points. session.context can change the outgoing request. tool.execute.before can change or reject a call; tool.execute.after can change its result. OpenCode runs callbacks in registration order.

Claude Code's PreToolUse also runs before permission evaluation; it can block a call, while permission policy retains the final denial. Pi extensions expose tools and lifecycle callbacks through one API, including context and compaction. they run with the user's OS permissions.

MCP servers run separately from the host. OpenCode connects over stdio or HTTP and adapts what the server offers. the server does not register host hooks; its tool calls pass through the host's permission and result handling.

consider a server called docs with a search tool. once the model selects it, the host runs a before hook, validates the query, and checks permission for action docs_search and resource *. the adapter then sends the call to the server. on return, it converts the result, runs the after hook, and records an observation for the next model step. the diagram zooms into that handler path; OpenCode may present the operation through its compact catalog.

What happens when an MCP tool is called A model selects an advertised search tool. The host runs a before hook, decodes input, and checks the server-qualified permission. A separate MCP server executes search. The host converts the result, runs an after hook, records an observation, and supplies it on the next model step. A plugin can register host-side hooks or tools; the MCP server is a separate process or service. model OpenCode host MCP server search(query) selected call before hook decode input permission check docs_search / * search server executes the result takes the return path result text / structured convert result after hook record result session state observation next model step plugins register host-side hooks and tools; they are not the server the server can perform remote effects; the host controls admission and observation
the MCP server supplies the operation. the host decides how to intercept, authorize, and return it.

the protocol offers more than callable tools. OpenCode connects over stdio or Streamable HTTP and maps four kinds of server contribution into different parts of the harness:

the adapter converts the remote response into text, media, or structured output the model can receive. it even parses JSON-looking text when the server supplies no output schema. this conversion affects the observation, not the server's authority. schemas and annotations describe intended behavior; they cannot prove that a server is read-only or make returned documents trustworthy instructions.

server availability changes the action space. OpenCode starts configured servers asynchronously, marks them usable after tool discovery, and refreshes the registry when a tool list changes. its expired HTTP-session path reconnects and retries the original request. for a tool with side effects, a lost response leaves an important ambiguity: the first attempt might already have acted.

tool discovery and authorization remain different problems. OpenCode checks the server-qualified name, such as docs_search, with a wildcard resource. it does not inspect the arguments to authorize a particular remote document. Anthropic's code-execution approach addresses the cost of discovering many MCP tools; it does not replace a policy for what each call may touch.

4. browser: desktop execution, server network

OpenCode's experimental browser plugin splits control between server and desktop. the server defines the tool and carries RPC messages; the desktop runs Chromium, CDP, page evaluation, and screenshots. a browser attachment belongs to one session, so a child needs its own connection. calls name a tabID from that attachment's tab inventory.

OpenCode browser plugin execution and network paths A session's server plugin emits connection and request IDs over its browser attachment. The desktop fetches the command through RPC and runs it against a tab in desktop Chromium. Results return through RPC. A separate tunnel routes browser page traffic through the connected server's network, including its loopback and LAN. control and page traffic take different paths server plugin session-bound tool call connection + request IDs desktop attachment fetch command + tab ID return validated result Chromium tab CDP · input · screenshot runs on desktop attachment belongs to one session; a child needs its own server network loopback + LAN reachable page traffic via TCP tunnel
control flows through the session attachment and RPC; Chromium runs on the desktop, while its page requests use the server's network. browser plugin contract · attachment implementation

the desktop fetches each pending command by connection and request ID, then returns its result. uploads cross RPC as bytes from a server-local path into a desktop temporary file; captures travel back. transfers are limited to 5 MiB. this path works even when server and desktop do not share a filesystem.

page traffic takes a different route from control: a TCP tunnel sends it through the server's network, including loopback and LAN. the plugin leaves per-URL and server-file checks to a later layer. checking only the initial navigation URL would miss requests triggered by page code. a timed-out click or upload might already have acted, so an automatic retry could repeat the effect.

other harnesses package browser work differently: oh-my-pi uses an Eval browser facade, Cursor describes a computer-use subagent, agent-browser uses CLI commands, and Playwright MCP exposes tools and accessibility snapshots. each choice changes the interface the model sees; the network boundary needs separate enforcement.

5. subagents: a new context with a return path

OpenCode advertises eligible child agents in the subagent tool description. the model decides whether to call it; there is no separate task classifier in this path. the handler checks the chosen agent, permission, and depth (one level by default), then creates a child session with its own prompt and history. a later call may continue that child by sessionID, but the ID must belong to the current parent.

A child has its own conversation but may share files The parent model calls subagent. The tool checks agent, permission, and depth, then creates a child session with its own prompt and history. The child can use tools against the same working location. Foreground delegation returns final text; background delegation returns an ID first and sends a completion notice later. A configured command can create a background child through a separate trigger. parent session child session working location model calls subagent chooses named agent + task tool gate agent · permission · depth new child session own prompt + history child works its own model turns + tools shared files unless isolated text, or ID + later notice a configured command is another child-session trigger
a child gets separate model history, but its tools use the parent's working location by default. subagent path · completion

foreground delegation waits for the child's final text. background delegation returns its ID first; completion delivery later adds a notice to the parent. configured commands can also create background children directly, outside the subagent tool's depth and agent-specific gate. both routes use the parent's working location by default: the conversations are separate, the files are shared.

that sharing is the real coordination issue. a child summary does not reconcile concurrent edits to the same file. Claude Code can give a child its own Git worktree; oh-my-pi offers bounded task batches and isolated spawns that merge by patch or branch. these designs specify how edits return, not only how agents start.

6. persistence: what survives the next step or a crash?

OpenCode first records a new prompt in its inbox. a queued prompt waits; a steer can enter at a safe step boundary. delivered messages and settled tool calls become session state, with durable events projected into SQLite. live tokens shown in the UI are a different thing: full payload retention for replay is optional and off in the normal CLI configuration.

the next model request does not need every stored turn. with local compaction, OpenCode writes a checkpoint containing a summary of older work and a recent tail. the history builder can start a later request there; original turns remain stored. the tail targets about 15,000 tokens, clips tool text to 2,000 characters, and describes attachments. those cuts make the working context smaller, but lose detail from the model's immediate view.

a provider-native checkpoint is different: it is an opaque replacement window tied to the provider, model, route, and endpoint that made it. OpenCode replays it only when those match. otherwise it rebuilds the request from eligible original messages. a request hook cannot redirect that window to another endpoint. this matters when changing models mid-session: the stored history can stay the same while the working context changes.

Two ways a later request uses shorter history The stored transcript is retained. A local compaction creates readable summary plus recent tail, and a later request starts from that checkpoint. A provider-native checkpoint holds an opaque replacement window. It is replayed only with matching provenance; otherwise the request is rebuilt from original eligible messages. stored transcript remains available local compaction readable summary + clipped recent tail provider-native checkpoint opaque replacement window next request starts at summary same provenance → replay otherwise → rebuild from original messages
compaction changes the next working context. it does not erase the original session record.

the restart path can resume unfinished work with a notice and a bounded attempt count. it promises at-least-once execution, not exactly once. if a remote service commits an effect and OpenCode crashes before recording success, the session cannot tell whether that call happened. safe retry needs a stable call ID the remote service deduplicates, or a way to check the previous attempt first.

The uncertain interval after a remote side effect The host sends a tool call. A remote service commits an effect, but the host has not yet recorded success. If the process crashes in this interval, the durable session has no settled result even though the remote effect may have happened. On restart, a stable idempotency key or remote reconciliation can resolve the attempt; blindly replaying may duplicate the effect. uncertain interval remote service call accepted effect committed host session call sent success recorded crash here → restart sees no settled result stable call ID / reconcile blind retry may duplicate
a durable transcript can resume the session, but it cannot infer whether an unrecorded remote action already took effect.

Pi stores a branchable JSONL conversation; branching does not roll files back. Prime Agent reconnects clients with event cursors and snapshots, and journals mutating commands by client and command ID. a completed duplicate returns its recorded result; a command without a durable result is reported as uncertain rather than blindly replayed. these are different recovery contracts for conversation state and external effects.

the boundaries that matter most

if you build a harness, define five contracts explicitly: what enters each model request; which tools are advertised; where each call is authorized and executed; what a child inherits; and which effects can be retried after a crash. OpenCode's implementation shows why these choices belong at different boundaries. a permission rule cannot isolate a process, a child transcript cannot isolate files, and a durable log cannot prove that a remote side effect did not happen.

further reading: Stencil’s playbook, LangChain’s anatomy, OpenAI’s harness engineering account, Anthropic on long-running agents, and the broader harness engineering index.