Most of us know the basic idea behind an LLM by now: you give it some text, and it keeps predicting the most likely next token until it stops. That’s all it does, which is why something didn’t add up for me when I asked Claude Code to fix a failing test and it ran my test suite, read the error, opened two files, edited one, and ran the tests again. Tokens are pieces of words, and words can’t run anything.
Sure, you can write a program that takes some text and passes it to the shell, but how does that program know which part of the model’s output is a command and which part is the model talking to you? And where does the intelligence come from that makes the command right, instead of some random words?
That program is called a harness, and Claude Code, Codex, OpenCode, Hermes Agent, and pi are all harnesses. The model writes text in a format it was trained on, and the harness pulls the commands out of that text, decides if they’re allowed, runs them, and sends the output back to the model. So most of what feels like “the AI did something on my computer” is the harness doing it.
It bugged me enough that I spent a few evenings reading the docs and the open source code, and digging through the logs these tools leave on my laptop, until I could explain all of this to myself.
what is a harness?
Claude Code is a harness and Claude Opus is the model it talks to, while OpenCode is a harness that can talk to Claude, GPT, or a free model from some other lab. Anthropic’s Claude Code docs put it like this:
Claude Code is the layer around the model that provides the tools and manages the context the model sees. This surrounding layer is what the term agentic harness refers to.
In plain words, the harness decides two things: which actions the model can ask for (running a command, reading a file, searching the web), and what text the model gets to read on every call. I like Simon Willison’s definition of an agent because it fits in one line: “An LLM agent runs tools in a loop to achieve a goal.” The harness is the thing running that loop.
The closest thing I can think of is a browser running a site’s JavaScript. The browser runs the code, but it asks you before the site gets your camera and keeps it away from your files, so the model is the site and the harness is the browser.
Unlike a browser, though, a harness isn’t strict by default. It’s a normal program you started yourself, so out of the box it can do anything you can do in your terminal. I’ll come back to that, because honestly it’s the part I care about most.
how do tokens turn into a command?
The model never runs anything itself, because all it can do is write a request in a fixed format, and some other code reads that request and does the work.
You can see this in a real open model like Qwen3. I took its official chat
template
(the rules that turn a conversation into one long piece of text), gave it one
action called bash, and asked which markdown file in a folder has the most
lines. This is the text the model reads, cleaned up and cut down a little:
system:
You may call one or more functions to assist with the user query.
<tools>
{"name": "bash", "description": "Run a shell command and return its output."}
</tools>
For each function call, return a json object with function name and arguments
within <tool_call></tool_call> XML tags.
user:
Which markdown file in this folder has the most lines?
assistant:
<tool_call>
{"name": "bash", "arguments": {"command": "wc -l *.md"}}
</tool_call>
user:
<tool_response>
85 health-checks-lie.md
113 sqlite-queue.md
168 v-cache-llm-inference.md
</tool_response>
assistant:
Everything in there is plain text, which surprised me more than it should have.
The list of actions is only a paragraph of instructions at the top. To ask for a
command, the model writes <tool_call> and the JSON after it one token at a
time, the same way it writes any other reply, and once the command runs, its
output comes back as one more message and the model carries on predicting from
the bottom.
That fixed format is how the program tells a command apart from normal talk:
anything inside <tool_call> tags is a request, and anything outside is text
for you. The server running the model watches the tokens as they come out. After
the tool call, the model writes its end-of-turn token, so the server stops
generating, reads the name and arguments out of the tool call, and hands them to
the harness as structured data. Step through it here:
what the model writes
Let me count the lines. <tool_call> {"name": "bash", "arguments": {"command": "wc -l *.md"}} </tool_call><|im_end|>
what you see
Let me count the lines.
what the harness gets
nothing yetname: bash command: wc -l *.md
Press play, or step through the reply one token at a time.
Normal text. It streams back as it's written, and the harness shows it to you.
The model opened a <tool_call>. Everything from here is a request, so it's held back from your screen.
The tool call is closed, so the request is complete.
<|im_end|> is Qwen's end-of-turn token. The server stops generating, reads the name and arguments out of the request, and hands them to the harness.
Some models get their own special token for this, like <|call|> in OpenAI’s
open model gpt-oss, which its
docs list as a
“stop token”. APIs can also force the arguments to match the action’s schema
exactly (Anthropic calls this strict: true on a tool), so a tool call can’t
come back as broken JSON.
So the harness never has to guess which words to run, because it only runs what came back as a tool call, and only after its own checks. Anthropic’s tool use docs are pretty direct about it:
The model never executes anything on its own. It emits a structured request, your code (or Anthropic’s servers) runs the operation, and the result flows back into the conversation.
where does the intelligence come from?
I think of it as two halves: training, and the text the harness puts in front of the model.
Training happens in two rounds, and in the first one the model reads a huge
amount of text, a lot of which is code, shell commands, error messages, and
people fixing them. That’s how wc -l ends up as a likely next token when the
question is about counting lines. The second round is agent work: the model
tries a real task using actions like bash, a program checks whether the task
got done, and the model’s weights get nudged toward the attempts that worked.
Alibaba trained Qwen3-Coder like this on “20,000 independent environments in
parallel”, so 20,000 practice
setups running at once, and Anthropic says Claude “has been optimized on
thousands of successful trajectories” (recorded runs) that use its exact bash
and file-editing actions.
After that much practice, the most likely next tokens in a coding session look a lot like the next useful command. I don’t think anything in there plans the way a person plans. The model has seen this kind of conversation so many times that “run the tests, read the error, open the file” is the likely way for the text to go on.
The other half is context, because whatever the model reads, the harness put there: the list of actions, instructions about how to behave, which folder you’re in, which shell you use, your project notes, and the output of every command so far. If the harness tells the model the wrong folder or the wrong shell, even a good model writes the wrong command.
what does a harness look like up close?
Under all the features, every harness I looked at runs the same loop:
send the model: the instructions + the list of actions + your question
repeat:
read the model's reply
if it's a final answer: show it to you and stop
if it asks for an action:
check if that action is allowed
run it on your computer
add the result to the conversation
send the whole conversation to the model again
Thorsten Ball wrote a good post on building one of these yourself, where he sums it up as “an LLM, a loop, and enough tokens.” His version is “less than 400 lines of code, most of which is boilerplate.”
Two lines in that loop cleared up a lot for me. “Run it on your computer” is boring, normal code (start a program, read a file, make a web request), so all the safety lives in what that code agrees to do. And “send the whole conversation again” is why agents burn so many tokens, since the model doesn’t remember anything between calls and every call carries the instructions, the list of actions, your question, and every command and output so far.
I wanted to see this on my own machine, so I put three of my old posts in an
empty folder and asked Claude Code which one had the most lines, with its log
turned on and only wc and ls allowed. Here’s the whole run:
- you
- harnessClaude Code
- modelClaude Opus 5.5
- your computer
The whole thing took 3 calls to the model, 7.7 seconds, about 84,600 tokens, and about 17 cents. Each call was about 28,000 tokens because each one sent the whole conversation again, and 65,840 of those tokens were cheap because the API gives a big discount on text it has seen very recently (prompt caching). The log also listed 39 actions the model could ask for, and 13 of them come from add-ons I’ve connected over time, each of which gets described on every call.
This is the exact refusal Claude Code sent back to the model, in place of any output:
find with '-exec' executes commands or modifies files — cannot be auto-allowed by a Bash(find:*) prefix rule
Claude Code blocked the first command because it reads every command before
running it, and -exec lets find run any other program on every file it
finds. If it allowed anything that starts with find, it would also allow
find . -exec rm {} +, which deletes files, and since I ran it without a human
to click “yes”, it said no. I liked what happened next: the model read that refusal
as plain text and wrote wc -l *.md instead, which matched what I allowed.
Then I asked OpenCode the same question. It lets you pick any model, and by
default it used a free one called minimax-m3.1-flash-free:
| Claude Code 2.1.287 | OpenCode 2.0.32 | |
|---|---|---|
| model | Claude Opus 5.5 | minimax-m3.1-flash-free |
| calls to the model | 3 | 3 |
| first command | find ... -exec wc -l {} + | find ... -exec wc -l {} + |
| what happened to it | blocked | ran without asking |
| tokens sent, all calls | 84,610 (65,840 cheap) | 32,443 (22,970 cheap) |
| total time | 7.7 s | 7.6 s |
| right reply | yes | yes |
This is the row I keep thinking about. Two different models wrote almost the same command, one harness blocked it and the other ran it right away, and neither model had any say in which.
Don’t read much into the token row, btw. Most of the difference is my own setup, since every add-on I’ve connected rides along on every call, and Mario Zechner’s harness pi goes the other way: he says its instructions and actions together “come in below 1000 tokens.” One run each isn’t a proper test either, ofc, but I only wanted to watch the loop, and it was the same loop in both (the setup details are at the end of the post).
how does it know i use zsh?
The harness checks your computer and writes what it found into the conversation. Codex is open source, so we know it has a shell detector that looks up which shell you log in with. If that fails on a Mac, it falls back to zsh and then bash, and on Windows it uses PowerShell. Every command then runs through that shell, set up the same way your own terminal is.
Then it tells the model, and since Codex keeps its sessions in log files on my laptop, I dug out the note it sent in one of my sessions last month (shortened):
<environment_context>
<cwd>/Users/rajat/dev/plans/restate</cwd>
<shell>zsh</shell>
<current_date>2026-09-15</current_date>
<timezone>Asia/Kolkata</timezone>
</environment_context>
So the model writes zsh commands because one line in its input says
<shell>zsh</shell>, and if the harness wrote the wrong shell there, the model
would happily write commands for the wrong shell.
Claude Code does it a little differently. When a session starts, it reads your
shell’s settings file once (like ~/.zshrc) and saves your aliases and
functions, and then it runs each command on its
own, one at
a time. That finally explained some odd agent behavior I’d seen before:
-
If the model moves into another folder with
cd, the next command starts there too, as long as it stays inside your project. -
If the model sets something with
export, the next command doesn’t have it, because the command that set it has already finished. -
A command gets 2 minutes by default (10 at most), and if it runs longer, Claude Code moves it to the background instead of killing it.
-
On Windows, it uses Git Bash if you have it, and PowerShell if you don’t.
who decides what it’s allowed to touch?
The harness does, because the model never touches your files. And since the harness is a normal program that you started, it can do whatever you can do: read your SSH keys, push to GitHub, or delete your home folder. Two kinds of guards stop that from happening.
The first guard lives inside the harness, and it checks every request against a
list before running anything: allowed, not allowed, or ask the human. That’s the
guard that blocked my find. Claude Code has a few
modes for this, from “ask me
about everything” to “never ask”, and its docs say the never-ask mode is for
“Isolated containers and VMs only”, meaning a throwaway computer and not your
laptop. You can also add your own rules, like “always allow npm test”.
OpenCode is much more relaxed, and its
docs say “Most permissions default to
"allow"”, which matches what I saw. It only stops to ask before touching
folders outside your project, or when the model repeats the same action 3 times
in a row.
This first guard has a weak spot because it reads a command and tries to work
out what it will do, and there are a hundred ways to write the same command.
Claude Code is careful (it caught -exec), but I don’t think any checker can
catch more than the tricks it already knows about.
The second guard is the operating system, and I trust this one a lot more. The
harness locks itself in before it runs anything, and macOS or Linux enforces the
lock, so nothing has to guess. Codex does this by default: on a Mac it uses a
lock built into macOS, with a rule
file
that starts with (deny default), which means everything is blocked unless the
file allows it. In my logs, Codex could write inside the project folder and the
temp folders while the project’s .git folder stayed read-only, and the
internet is off by default too.
Claude Code has the same kind of lock, called
/sandbox, and Anthropic says it
cut permission questions by
84% in their own
use. It’s off by default, though, and it only covers shell commands, so reading
and editing files, fetching web pages, and add-ons all happen outside it. Even
with it on, it can still read most of your computer, including your ~/.ssh
folder, unless you change that.
Back to the browser: Codex behaves like a browser, locked down unless you say otherwise, while Claude Code and OpenCode behave more like a browser extension that asked for access to every website when you installed it. If I could change only one setting after all this, it would be turning on the sandbox before turning off the “are you sure?” questions, because those questions get annoying fast, and an annoyed human clicks “yes” without reading.
how does it edit files without breaking them?
Rewriting a whole 600-line file to change one line is slow and expensive, and the model might drop something on the way, so every harness gives the model a smaller way to describe a change.
Claude Code and OpenCode use find and replace: the model sends the exact old text and the new text, and if the old text isn’t in the file, or it’s there twice, the edit fails and the model gets an error and tries again. Codex uses its own small patch format:
*** Begin Patch
*** Update File: src/app.py
@@ def greet():
-print("Hi")
+print("Hello, world!")
*** End Patch
OpenAI’s GPT-4.1 guide calls this a format “on which the model has been extensively trained,” and Aider, one of the oldest open source harnesses, goes as far as picking a different edit format for each model, based on which one that model handles best.
I haven’t tested this, but since the model practiced with certain actions in a certain format, my guess is that a model trained on Codex’s patches is clumsier with find and replace, and the other way around.
which harnesses are open source, and what are they written in?
As of October 2026, from their own pages and code, plus a look at the programs on my laptop:
| harness | made by | can you read the code? | written in | which models |
|---|---|---|---|---|
| Claude Code | Anthropic | no | JavaScript | Claude |
| Codex | OpenAI | yes | Rust | mostly OpenAI’s |
| OpenCode | the team behind the SST framework | yes | TypeScript | any |
| Gemini CLI | yes | TypeScript | Gemini | |
| Hermes Agent | Nous Research | yes | Python | any |
| pi | Mario Zechner | yes | TypeScript | any |
| Aider | Aider-AI | yes | Python | any |
| Goose | started at Block, now under the Linux Foundation | yes | Rust | any |
| Cline | Cline Bot Inc. | yes | TypeScript | any |
Claude Code isn’t open source, even though it has a busy GitHub page full of bug reports, add-ons, and examples, and its license says “All rights reserved.” The program on my laptop is one 217 MB file, and when I searched the text inside it, I found “Bun v1.4.3”. Bun can compile a JavaScript app into a single binary, so that’s how Claude Code ships, which makes sense given that Anthropic bought Bun in December 2025.
Codex started in April 2025 as a JavaScript app, and OpenAI rewrote it in Rust a few weeks later, partly because needing a recent Node.js was “frustrating or a blocker for some users”. My copy is a single 235 MB program.
OpenCode comes in two halves, which I didn’t expect. When you run it, it starts a small server in the background, and the screen you type into talks to that server, so other apps like your code editor can drive the same agent. It also keeps a hidden copy of your project’s history on the side, and since every step in my log had a snapshot ID, I’m fairly sure that’s what its undo uses.
Hermes Agent is the odd one out. You can talk to it through Telegram, Discord, or Slack, it writes its own reusable instructions as it learns, and its commands can run on your laptop, inside Docker, or on another server.
where does herdr fit?
herdr comes up in the same conversations, but it’s not a harness. It sits one level above and runs your harnesses (Claude Code, Codex, pi, OpenCode, Hermes, 22 of them in total) in their own terminals, inside a server that keeps going in the background, so you can close your laptop, come back later, and they’re still running.
It also watches each terminal and marks every agent as working, blocked, or idle, so you can see at a glance which one is waiting for you, and agents can use it too, to start other agents and send them work. Their own line puts it well: “Herdr doesn’t wrap them or replace them, it just owns their terminals.” The way I think about it, a harness is the program around one model, and herdr is where you keep a bunch of harnesses running at once.
does the harness change how good the model is?
By a lot, if you go by public numbers. Terminal-Bench is a test where agents do real tasks in a terminal, and in version 2.1 they ran the same models two ways: inside the model company’s own harness, and inside Terminus 2, a plain harness the test makers built. The numbers are the share of tasks finished:
| model | own company’s harness | Terminus 2 | difference |
|---|---|---|---|
| GPT-5.4 | 77.3% (Codex) | 54.8% | 22.5 points for Codex |
| GPT-5.4 mini | 66.1% (Codex) | 36.9% | 29.2 points for Codex |
| Opus 4.6 | 70.1% (Claude Code) | 63.8% | 6.3 points for Claude Code |
| Gemini 3.1 Pro | 67.1% (Gemini CLI) | 70.7% | 3.6 points for Terminus 2 |
The page doesn’t say how many times they ran each test, and in the previous version (2.0), Opus 4.6 did better in Terminus 2 than in Claude Code, 62.9% to 58.0%. Version 2.1 also fixed problems in 28 of the 89 tasks, so you can’t compare the two versions directly.
Even so, GPT-5.4 landed 22.5 points apart with the same weights, only because the program around it changed, and that surprised me more than anything else I found. Inside Codex, the gap between GPT-5.4 and the much smaller GPT-5.4 mini is 11.2 points, so on this test, the harness mattered about twice as much as the model size.
My guess is that OpenAI tunes its models inside Codex, with Codex’s actions and patch format, and they stumble when they get a different set. OpenAI described GPT-5-Codex as “a version of GPT-5 further optimized for agentic coding in Codex”. Gemini going the other way fits this too: Google moved free users to its newer Antigravity CLI in June 2026, and the Gemini CLI README now says it “is not optimized for the latest Gemini models.”
Mario Zechner, who made pi, argues the other side. He thinks today’s models have practiced so much that they “inherently understand what a coding agent is”, so a tiny harness with four actions (read, bash, edit, write) does fine. I think both can be true, because a small, clean harness doesn’t confuse the model, and a model’s home harness has the exact actions it practiced with. If I had to guess the worst setup, it would be a big harness full of actions the model never practiced with.
For what it’s worth, here’s how I’d pick. If I mostly used one company’s models, I’d use that company’s harness, because that’s where the model practiced. If I switched models a lot, or wanted to read the code that runs on my computer, I’d go with an open one like OpenCode or pi. And whichever one I used, I’d turn the sandbox on before turning the “are you sure?” questions off.
how i ran these tests
Both runs were on October 19, 2026, on my Mac, in a folder with three
markdown files. Claude Code 2.1.287 used Claude Opus 5.5, allowed only wc and
ls (--allowedTools "Bash(wc:*)" "Bash(ls:*)"), and ran without a human to
approve anything (-p). OpenCode 2.0.32 used its default settings. The token
counts and times come from each tool’s own log, and the 17 cents is Claude
Code’s own estimate at API prices. I also tried Codex 0.162.1, but its login
had expired, so the Codex details here come from its code and my older logs.