All posts

What is an agent harness? Claude Code vs Codex CLI vs OpenCode

August 6, 2026·13 min readaillmagentsclaude-codedeveloper-tools
On this page

A draft horse in full working harness, with the collar, blinders, reins and traces labelled as the model, context, permissions and tools.

A harness straps a source of power to useful work and decides where that power is allowed to go. Climbing harness, wiring harness, horse harness. The horse is the easiest to picture: the animal supplies the strength, and the harness decides what it can reach, what it's allowed to look at, and how much of that strength arrives at the load.

A coding agent splits the same way. The model is the animal. The agent harness is everything strapped around it: the loop that keeps it working, the tools it can call, what it gets shown, and what it's allowed to touch. And the straps turn out to matter about as much as the animal.

In May a group at Peking University ran 106 tasks through six different agent harnesses over the same pool of models, logging more than five thousand runs. The best harness scored 76.2, the worst 52.4. That 23.8 point gap came from the harness, not the model doing the thinking.

If you want the mechanism rather than the scoreboard, the clearest experiment is older. In 2024 a group at Princeton held one model constant and changed only the interface it used to work on a codebase. Show it a hundred lines of a file at a time and it fixed 18% of real GitHub issues. Show it the whole file and that fell to 12.7%.

More information, worse results. That is the SWE-agent paper, and it called the thing it was varying the agent-computer interface. We say harness now.

I wanted to know what the three big ones actually do differently, so I cloned them rather than reading their marketing. Two are open source. The third had an unusually informative accident.

A harness is a while loop with function calls

Strip the branding off and a language model is one function. Text in, text out. It cannot read a file, run a test, or remember what it said a minute ago unless you paste the conversation back in. It's a very capable person locked in a room, passing notes under the door.

The harness is everything on your side of the door, and at its core it is embarrassingly small:

loop:
    response = model(context)
    if response contains a tool call:
        result = run_tool(tool_call)      # read a file, run bash, edit code
        context += tool_call + result
    else:
        show response to user, wait for input

That's the agentic loop, in full. The model emits text saying "I want to run npm test". The harness actually runs it, pastes the output back into the context, and calls the model again. The model reads the failure, decides what to do next, and round it goes. Read, edit, run, read the error, fix, run again.

That's not a simplification for the sake of a blog post. In OpenCode, the easiest of the three to read, it is a literal while (true) in session/prompt.ts that calls the model once per pass and breaks when the last response contains no unexecuted tool calls. Every coding agent you have used is this loop wearing a different coat.

So why do harnesses differ so much, if the loop is the same? Because the loop is the skeleton and everything interesting is the flesh around it: which tools exist, how they're described, what the model is told before your first message, what runs without asking permission, and what gets thrown away when the context fills up.

Princeton tested those decisions one at a time, and all three tools below have taken a position on the results. The file window from the top was one of them, worth 5.3 points. Keeping the full conversation history rather than just the last five tool outputs cost another 3. Giving the edit tool a linter that refuses syntactically broken changes was worth 3 points on its own.

Notice what kind of decisions those are. The first two win by showing the model less. The third wins by letting it get away with less. Neither touches a model weight. All of it is harness.

Which brings us to the three tools, and to three different answers. It's easiest to see them side by side before reading anyone's source: one animal buried under every strap its maker could think of, one wearing almost nothing, one built from pieces you can unclip and swap out depending on the job.

Three identical draft horses. The first is buried under an elaborate harness hung with dozens of tools, the second wears only a simple bridle and one rein, the third wears a modular harness of interchangeable sections with a spare piece on the ground.

Claude Code: maximal tools, industrial context engineering

The Claude Code logo.

Claude Code is closed source. On March 31, 2026 a packaging mistake shipped a 59.8 MB source map inside @anthropic-ai/claude-code v2.1.88, and the community had reconstructed the TypeScript before it was pulled. The analyses from that window are the reason anyone can discuss its internals, and the prompts have been archived per version ever since. I read the v2.1.223 archive.

Treat what follows as reconstructed material, because that's what it is. It's a community extraction rather than an Anthropic release, and the files in it aren't finished strings. They're templates, full of ${VAR} interpolation and ternaries, assembled at runtime from your sandbox mode, git state, feature flags and whichever model is running. Many are mutually exclusive variants of the same tool.

That last part matters, so here is exactly what I counted:

# in the archive's system-prompts/ directory
cat tool-description-*.md | wc -w   # 28973
cat system-prompt-*.md    | wc -w   # 28373

Two corpora, near enough the same size. No single session ships 29,000 words of tool descriptions, since the conditionals mean only a slice gets selected on any given turn. What the ratio tells you is where the effort went. Anthropic has written roughly as much prose describing its tools as it has writing every other instruction the model receives, put together.

The tool surface is enormous to match: about 45 tool-name slots covering some 95 tools, each described at length. The Bash description alone is spread across 39 template files, 16 of which exist only to reword it depending on sandbox mode.

The Princeton result says a rich, carefully described tool surface beats a bare shell by 64%. Claude Code is what it looks like to believe that finding completely and then fund it for two years.

Two other things stuck with me. The first is that orchestration is written in English. The largest single system-prompt fragment is a coordinator block of 5,916 tokens, and it's pure prose about when to delegate, when to spawn a subagent versus continue an existing one, and how to synthesize what comes back. There's no scheduler anywhere. The architecture diagram is an essay. There's even a dedicated block arguing against delegating, which opens with "Subagents multiply cost and time" and later points out that verification which fits in your own loop belongs in your own loop.

The second is that the prompt is compiled for cacheability. Analyses of the leak describe a split at a constant named __SYSTEM_PROMPT_DYNAMIC_BOUNDARY__. Everything above it is identical for every user on the planet and sits in a shared cache; everything below is your session, your CLAUDE.md, your git status. Without that trick a prompt this size would not be affordable. Claude Code's own shipped documentation hands the same advice to people building agents: never edit the system prompt mid-session, because it invalidates the cached prefix.

Codex CLI: give it a shell and get out of the way

OpenAI's Codex CLI is Apache-2.0 Rust, and it bets the other way on almost everything. Its registry defines around 28 tool names, but most are feature-gated, and a plain local session exposes six to eight: shell_command, apply_patch, update_plan, view_image, web search, and the MCP resource tools if you've configured any servers.

The absences are the point. No read tool. No grep tool. No glob tool. No edit tool. Reading and searching go through the shell, and the only way to change a file is apply_patch, which isn't even a JSON tool but a freeform one with a Lark grammar attached.

You can watch this in OpenAI's own splash screenshot. Read the model's reasoning in the middle of the frame: it's talking itself into ls and rg --files, because that's what it has.

The Codex CLI, from OpenAI's own repo. The model plans to use ls and rg --files, since Codex gives it a shell rather than search tools.

The most telling part is where those missing tools went. They come back as prose.

There is no search tool to describe, so the prompt simply tells the model to reach first for rg or rg --files. There is no write tool, so it warns: "Do not create or edit files with cat or other shell write tricks."

Claude Code puts that kind of knowledge in the tool descriptions. Codex puts it in a sentence. The whole system prompt runs between 1,822 and 3,511 words depending on the model, and it now ships as JSON rather than the markdown files still sitting in the repo.

This is a deliberate bet against the Princeton result, and not a foolish one. That paper's shell-only baseline was a 2024 model. Since then, models have been trained hard on terminal use, and Codex is betting they have absorbed the interface into their weights. If that is right, the scaffolding that bought 7 points in 2024 now costs more in tokens and rigidity than it returns. Whether it is right, nobody seems to have measured properly.

What Codex spends its complexity on instead is isolation, and here it's the most serious of the three by some distance. Three sandbox implementations ship with it: Seatbelt on macOS, bubblewrap plus seccomp on Linux, and a Windows sandbox. The policy is a typed enum of read-only (the default), workspace-write, external-sandbox and danger-full-access. Network access is off unless you turn it on. The macOS policy is generated at spawn time and passed as an argument rather than written to a file, starting from a blanket (deny default).

One small detail I liked: inside every writable root, three directories are forced back to read-only, namely .git, .agents and .codex. The agent can rewrite your source all it likes, but it cannot touch your git internals or its own configuration. Someone thought about that.

The difference that matters is this. Claude Code decides whether a command is safe by parsing it and reasoning about it. Codex decides by asking the kernel to make the dangerous thing impossible. One is a very good lock on the door. The other is a room with no door.

OpenCode: the harness with no favorite model

OpenCode is MIT TypeScript, version 1.18.14 as I write this, and it asks a different question: what if the harness had no allegiance to any lab at all? It resolves models from a catalog with 24 provider packages bundled and installs anything else from npm on demand, so Anthropic, OpenAI, Google and a local model under Ollama are all the same kind of thing to it.

Put its screenshot next to the Codex one and you can see the difference without reading a line of source. Where Codex reasoned its way toward rg --files, this one issues Grep, Glob and Read as named tools. Look at the status bar too: that's Claude Opus, running inside a harness with no relationship to Anthropic.

The OpenCode terminal UI, from its own repo. The agent calls named Grep, Glob and Read tools, and the status bar shows it running Claude Opus.

Its tool surface sits between the other two at 17 tools, with the descriptions kept in separate .txt files totalling 2,757 words. But it's the most interesting of the three on the Princeton findings, because it implements two of them almost literally.

The linter guardrail is real. In tool/edit.ts, right after applying an edit, it calls lsp.touchFile() and then lsp.diagnostics() and folds the result into what the model reads back. That's the paper's 3-point finding shipped in production, and better than the paper's version: not a syntax check but your actual language server, the same diagnostics your editor shows you.

The forgetting is deliberate and tunable. session/compaction.ts has two mechanisms with named constants. Prune truncates old tool outputs in place at TOOL_OUTPUT_MAX_CHARS = 2_000 while keeping the last DEFAULT_TAIL_TURNS = 2 turns whole. Compact summarizes when the context overflows. Keep the recent observations, gut the old ones, with a config file attached.

There's one thing here I haven't seen anywhere else: the harness reshapes itself around the model. In the tool registry, GPT-family models are handed apply_patch and have edit and write removed, while everything else gets the inverse. The system prompt is chosen per model family too, with separate files for Anthropic, GPT, Codex and Gemini. OpenCode has quietly concluded that there is no best tool surface, only the best tool surface for a given model. Of the three, that is the sharpest reading of the Princeton result.

The trade-offs are real. There is no OS-level sandbox at all. Permissions are in-process checks, evaluated last-matching-rule-wins with a default of ask, and the bash tool parses commands with tree-sitter to derive per-command permission patterns rather than asking once for all shell access. That's decent engineering, and it's still a lock on a door rather than a room without one. Architecturally it's the only one of the three with a real client-server split: the agent runs as a headless HTTP server whose OpenAPI spec generates the client SDK, and the terminal UI is just one consumer of it.

The thing two of them are quietly building next

Here's what I didn't expect to find, in two codebases at once.

Codex's three newest models are configured with "tool_mode": "code_mode_only". In that mode the whole tool surface collapses into a single freeform tool called exec, and its payload is source code that calls the tools as an ordinary API. Not a menu the model picks from one item at a time, but a program it writes. Codex also supports deferred tools that aren't included in the request at all, which the model finds on demand through a tool_search call. OpenCode has the same idea behind an experimental flag, a tool named execute, which swaps the per-tool listings for a typed catalog the model writes code against.

Think about what that does to context. Instead of paying for every tool description on every turn, you pay for one and let the model spend its own reasoning composing the rest. It's the Princeton lesson taken to the end of the line: the harness's job isn't to show the model everything it might need, it's to show as little as possible while keeping everything reachable.

What I'd actually take away

Skip the leaderboard screenshots. Terminal-Bench positions between Claude Code and Codex have swapped every few months, and will have moved again by the time you read this. OpenCode has no score of its own at all, because its score is whichever model you plugged in.

The durable finding is the strange one. A harness earns its keep mostly by being strict. Show the model a window instead of a whole file. Drop stale tool output instead of hoarding it. Refuse a broken edit at the tool boundary instead of letting the model find out three turns later. Every one of those is a restriction, and every one of them scored higher. Which is an odd lesson for an industry currently selling each other ever larger context windows.

The three tools bet differently on it, and you can read the bets in the source. Claude Code believes in the tool surface, and spends more words describing its tools than instructing the model, with a caching pipeline built to make that affordable. Codex believes modern models already know the terminal, so it ships a handful of tools, a prompt in the low thousands of words, and three kernel sandboxes. OpenCode believes the answer depends on the model, so it swaps its own tools and prompts based on which one you loaded, and hands you the server to run yourself.

If you're picking one today: Claude Code for the deepest tooling if you don't mind the token bill, Codex for speed and a sandbox you can actually defend, OpenCode to own the stack and bring your own model. But the choice matters less than the habit. Once you know a harness is a loop, a tool list and a set of decisions about what to throw away, you can read any of these repos in an afternoon and stop guessing why your agent went round in circles.

Which puts us back with the horse. Everyone is arguing about which animal is strongest, and the measurements keep suggesting that a good deal of the work is being done by the straps.