---
title: "Understanding Coding Agent Harnesses"
date: "2026-10-28"
excerpt: "How coding agents work, layer by layer: the model that decides what to do, the harness that runs the tools, and the context files that steer them both."
seoDescription: "How coding agents work, layer by layer: the model decides, the harness runs the tools, the context files steer."
ogImage: "https://aroramrinaal.com/ai/understanding-coding-agent-harnesses/social/seo-understanding-coding-agent-harnesses.png"
published: true
tags:
  - Agents
  - Harnesses
releaseCard:
  heading: "New deep dive"
  description: "The model decides, the harness acts, the context steers."
  penNote: "new one!"
  visual: harness-window
  theme: lesson
---

## What is a coding agent?

Not long ago, in 2024, ChatGPT could already write decent code. But we engineers still did the busywork: copy the function, paste it into the editor, run it, paste the error back into the chat, and repeat until it worked.

Today you type `/goal migrate the test suite to pytest and don't stop until CI is green` into Codex, close your laptop, and go to sleep. In the morning the task is done and a PR is waiting for your review.

The two leading AI labs, OpenAI and Anthropic, both ship coding agent harnesses this strong: Codex and Claude Code. The loop built around them can keep an agent working for hours until the task is finished.

## The one-line definition

So what exactly is a coding agent?

> A coding agent is an AI system that takes a programming goal and plans, writes, runs, tests, and fixes code in a loop, using tools like a terminal, a file editor, and search, until the task is done.

## The three layers at a glance: Model, Harness, Context

I like to think of today's coding agents as having three layers:

1. **The model** decides what to do next.
2. **The harness** acts on those decisions and remembers what happened.
3. **The context** is the set of files the harness loads to tell the model how to work.

I've used almost every state-of-the-art coding agent harness for months and pushed more than 7 billion tokens through them. One thing I'm confident about: as models like OpenAI's GPT-6 Astra and Claude Opus 5.5 get more capable, the third layer, the context layer (I sometimes call it the external layer), is becoming the one that matters most. That layer includes `CLAUDE.md`, `AGENTS.md`, skills, and memory. These files steer the model, and they shape how you work with the agent day to day.

## Layer 1: The Model

Most of what follows is what I learned by asking Claude, inside Claude Code, to explain its own harness to me, and then checking the answers against the Anthropic and OpenAI docs. I went in with a rough mental model. About half of it was right.

The model is the part that lives on the lab's servers. When I use Claude Code, the model is Claude running on Anthropic's machines, and the harness on my laptop talks to it over the API. The model never touches my files. It never runs a command. It is a function: tokens in, tokens out.

### It only reads text and writes text

Every call to the model is one request. The harness sends one long piece of text: a system prompt, a list of tools, the context files, and the whole conversation so far. The model reads all of it and writes back a reply.

The reply is text, a tool request, or both. That is the full range. The model has no hands. It cannot open `src/math.js`. It can only write "please read `src/math.js`" in a format the harness understands.

### It remembers nothing between calls

This was the first thing I had wrong. I assumed the model kept some memory of the session on its side. It does not. The Messages API is stateless, which means every request has to carry the full conversation history, and the model reads it fresh each time.

So when Claude Code "remembers" what I said ten minutes ago, that is not the model remembering. It is the harness sending my message again along with everything else. The memory lives in the harness.

### It asks for tools but never runs them

I also assumed tool calls were just text that looked like bash, which the harness then parsed. That is close, but not how it works. The model is trained to use tools, and a tool request is a structured block, not loose text. With every call, the harness sends the list of tools it offers, each with a schema. When the model wants one, it replies with something like this:

```json from=model to=harness title="tool request"
{ "type": "tool_use", "name": "Bash", "input": { "command": "npm test" } }
```

The API returns that as its own block, separate from the normal reply text. A chat app with web search and a coding agent use the exact same mechanism. The only differences are which tools are on the list and who runs them.

## Layer 2: The Harness

The harness is the program you actually install: Claude Code, Codex CLI, and the rest. Anthropic's docs describe Claude Code as the layer around the model that provides the tools and manages the context the model sees, and say that this surrounding layer is what the term agentic harness refers to.

OpenAI's harness engineering post describes the same idea from the other side. The job is to design environments, specify intent, and build feedback loops so agents can do reliable work. Everything around the model that does that is the harness.

### It runs the loop

The loop is the heart of it. Written out, it looks like this:

```text diagram=agent-loop
1. Build the request: system prompt + tool list + context files + conversation so far
2. Send it to the API
3. Read the reply
   - no tool request: show the text to me, wait for my next message
   - tool request: check permissions, run the tool, add the result, go to 1
```

One thing I had wrong here: I thought the harness called the API on some interval. There is no timer. It calls the API right after each tool finishes. A fast `ls` means the next call comes in a second. A five-minute test run means the harness waits five minutes, then calls. The loop is driven by events, not time.

### It runs tools on your machine

When the model asks for `Bash`, `Read`, `Edit` or `Grep`, the harness runs that on my laptop, with my user account, in my real shell. Then it captures the output and appends it to the conversation. Each tool result becomes new input for the next call, which is how the model finds out whether its fix worked.

This is also why safety lives in the harness. The permission prompt ("Claude wants to run `rm -rf dist`, allow?") is a harness feature. The model only asked.

### It keeps the conversation and sends it all back every time

The harness is the part that keeps state. It stores every message, every tool call, and every tool result. Claude Code writes all of it to a plain JSONL file under `~/.claude/projects/`, which is how `--resume` works.

And on every single call, the harness sends the whole thing back. Every `npm test` output and every file read stays in the conversation and gets resent on later calls, until the harness decides to compact it. More on that below.

## Layer 3: The Context

This is the layer I find most interesting, and the one I had the most muddled view of.

### CLAUDE.md, AGENTS.md, memory and skills

The context layer is a set of markdown files on disk:

- **`CLAUDE.md` or `AGENTS.md`**: instructions you write for the project. Build commands, conventions, things the agent should always know. `AGENTS.md` is the open format OpenAI introduced, and most harnesses now read it, Claude Code included.
- **Memory**: notes the agent writes for itself across sessions, like your preferences and the corrections you gave it.
- **Skills**: folders with a `SKILL.md` inside, each one a reusable set of instructions for a particular kind of task, like how we deploy or how to write a release note.

None of these are code. They are plain text, and that is the point. You steer the agent by editing text files in your repo.

### Files only matter once they’re loaded

My wrong mental model was that these files sat outside the context window, somewhere beside the harness, and were consulted without costing anything. That is not how it works.

A file on disk does nothing. It only changes the model's behavior once the harness reads it into the request. So the context layer is not outside the harness. It is a set of files the harness decides when to read into the window.

The real question is when each file loads. For Claude Code:

| File | When it loads |
|---|---|
| `CLAUDE.md` / `AGENTS.md` | At session start, in full |
| Auto memory | At session start, the first 200 lines or 25KB |
| Skill name and description | At session start, for every skill |
| Skill `SKILL.md` body | Only when that skill is used |
| Skill scripts and reference files | Only when the task needs them |

The goal of this layer is to load less, not to skip the window.

### Progressive disclosure: loading only what’s needed

That table is an example of progressive disclosure. Anthropic's post on Agent Skills explains it in levels. At startup the agent loads only the `name` and `description` of every installed skill into its system prompt. That is enough for the model to decide whether a skill is relevant. If it is, the agent reads the full `SKILL.md` into context. If that file points at more files, scripts or references, those are read only when the task needs them.

The same idea applies to tools. In Claude Code, MCP tool definitions are deferred by default: only the tool names sit in context, and the full schema loads when the model reaches for that tool. A session can know about a hundred tools without paying for a hundred schemas on every call.

<!-- diagram: what-loads-when -->

## How a tool call actually works

### What a `tool_use` request looks like

Here is what a real API reply looks like when the model wants a tool:

```json diagram=tool-call
{
  "content": [
    { "type": "text", "text": "Let me run the tests." },
    { "type": "tool_use", "id": "toolu_01", "name": "Bash",
      "input": { "command": "npm test" } }
  ],
  "stop_reason": "tool_use"
}
```

The harness sees `stop_reason: "tool_use"`, runs `npm test`, and sends the output back as a `tool_result` block that carries the same id. Then it calls again, with everything that came before plus these two new pieces. That is the whole handshake.

### Edits match text, not line numbers

I assumed the model edited files by line number, something like "replace lines 40 to 42 with this". It does not. Claude Code's `Edit` tool takes a `file_path`, an `old_string` and a `new_string`. The harness finds that exact text in the file and swaps it. If the text is not there, or appears more than once, the edit fails and the model has to try again with more surrounding context, or ask for `replace_all`.

The line numbers you see when a file is read are only for reading. They help the model talk about the code. They are not an address for editing.

## Walkthrough: “Fix the failing test,” step by step

Here is one full task, step by step. The token counts are rough, meant to show the window growing, not real measurements.

<!-- diagram: walkthrough -->

| Step | Who acts | What happens | Window |
|---|---|---|---|
| 1 | Harness | Session starts. Loads the system prompt, the tool list, `CLAUDE.md`, `AGENTS.md`, memory, and every skill's name and description | ~9,100 |
| 2 | You | Type "Fix the failing test". It is appended to the conversation. Nothing has gone to the model yet | ~9,100 |
| 3 | Harness | Sends the whole window to the API in one request | ~9,100 |
| 4 | Model | Replies with `tool_use: Bash { "npm test" }` | ~9,100 |
| 5 | Harness | Runs `npm test` on your machine and appends the output: 1 test failed, expected 5, got -1 | ~9,500 |
| 6 | Model | Sent everything again. Sees a skill description that fits and asks for `Skill { "run-tests" }` | ~9,500 |
| 7 | Context | Only now does the full `run-tests/SKILL.md` enter the window. The other skills stay as one-liners | ~10,400 |
| 8 | Model | Asks for `Read { "src/math.js" }`. The harness appends the file, with line numbers | ~11,300 |
| 9 | Model | Asks for `Edit { old_string: "return a - b", new_string: "return a + b" }`. The harness swaps the text | ~11,400 |
| 10 | Model | Asks for `Bash { "npm test" }` again. 12 passed | ~11,500 |
| 11 | Model | Replies with text only: "Fixed: add() was subtracting. All 12 tests pass." No tool request, so the loop stops | ~11,600 |

Two things to notice. The window never shrinks; everything the harness sent in step 3 is still there in step 11, plus all the tool results. And the model never did anything itself. It asked for five things, and the harness did all five.

## How the loop knows when to stop

I guessed the model wrote some JSON flag like `"turn": "end"` when it was done. Close, but no. The field is called `stop_reason`, and the model does not write it. The API adds it, based on how the model stopped.

```json from=model to=harness title="api reply" hl=3 note="no tool request, so the loop stops"
{
  "content": [{ "type": "text", "text": "Fixed. All 12 tests pass." }],
  "stop_reason": "end_turn"
}
```

What happens underneath: the model is trained to write a special stop token when it is finished, and to write a tool call in a trained format when it wants a tool. The API sees which one happened and sets the field. The harness then checks the value:

| `stop_reason` | Meaning | What the harness does |
|---|---|---|
| `tool_use` | The model asked for a tool | Runs it, calls again |
| `end_turn` | The model finished naturally | Shows the reply, waits for you |
| `max_tokens` | Hit the output limit mid-reply | Usually asks the model to continue |
| `refusal` | The model declined | Stops, shows the message |

One more detail that surprised me: the harness gets the final say, not the model. Claude Code has Stop hooks, scripts that run when the model tries to end the turn. A hook can block the stop and send the model back to work: "tests still failing, keep going." Codex's `/goal` mode is the same idea at a bigger scale. You give it one objective with a verifiable end state, and it keeps starting new turns until it is confident the condition is met.

## What’s inside the context window

Claude Code has a `/context` command that shows exactly what is in the window right now. Here is the breakdown from one of my sessions, a cloud session with no `CLAUDE.md` in it:

<!-- /context screenshot goes here -->

| Row | Tokens | Layer | What it is |
|---|---|---|---|
| System prompt | 3.9k | Harness | Claude Code's own instructions |
| System tools | 21.2k | Harness | Full schemas for the built-in tools |
| MCP tools | 18.5k | Harness | Full schemas for the connector tools |
| MCP server instructions | 0.8k | Harness | Short usage notes from the connectors |
| Skills | 5.8k | Context | Only the name and description of each skill |
| Messages | 38.8k | Conversation | My messages, Claude's replies, every tool call and result |
| Autocompact buffer | 33k | Reserved | Space kept free so the harness can summarize before the limit |
| Free space | 878.1k | Unused | The rest of the 1M window |

Three things it taught me.

Before I typed anything, the window already held about 50k tokens. Most of that is tool definitions, not context files. Every connector I add puts more tool schemas in, and they go out on every call.

The context layer here was just the Skills row, because this session had no `CLAUDE.md` or memory. When those exist they show up as their own row, labeled Memory files.

And below the table, `/context` listed another 37k tokens of deferred tools: tools whose names are in the window but whose schemas are not. That is progressive disclosure again, for tools instead of skills.

## Tokens and cost: why every call resends everything

### Starting overhead

Everything that goes out before the conversation starts is the starting overhead: the system prompt, the tool schemas, `CLAUDE.md`, the skill listing. Call it O. In the session above, O was about 50k tokens. A minimal harness you write yourself, with a short system prompt and four tools, could start around 5k.

Count tokens, not lines. The API bills tokens and the window is measured in tokens. As a rough guide, one line of prompt or JSON is 8 to 12 tokens.

### How the window grows

The overhead is not added once per message. It is the fixed front part of every request, and each call sends it plus the whole conversation so far:

```text diagram=resend-stack
Call 1 input = O + your message
Call 2 input = O + your message + reply 1 + result 1
Call 3 input = O + your message + reply 1 + result 1 + reply 2 + result 2
```

Replies are small. A reply that only asks for a tool is 50 to 200 tokens. What grows the window is tool results: file contents, test output, search results, often 500 to 5,000 tokens each.

A worked example. O is 5,000 tokens, my message is 10 tokens, and each loop adds about 1,000 tokens. After ten calls the window has only reached about 14,000 tokens, but I have been billed for about 95,000 input tokens, because every call resent everything before it.

### Prompt caching

This is why caching matters so much for agents. Most of each call is a repeat of the call before, and the API can cache that repeated front part instead of processing it again. A cache read is billed at a tenth of the normal input price, and on the newest models even less. Writing to the cache costs a bit more than a normal call. The catch is that the cache only matches an identical prefix, so anything that changes near the front of the request, like a tool list, throws it away.

In the ten-call example, caching takes the input cost from roughly 38 cents to around 10 cents on Opus 5.5, at the prices when I wrote this. The shape matters more than the exact number: the repeated part is most of the bill, and caching makes it cheap. Claude Code shows this in `/usage` as the share of input tokens that came from the cache.

## When the window fills up: compaction

The window is big but not infinite, and tool results pile up fast. When the conversation gets close to the limit, the harness compacts: it asks the model to summarize the conversation so far, then starts a fresh window with that summary at the top. Anthropic describes it as taking a conversation nearing the context limit, summarizing its contents, and starting a new window with the summary.

That is the Autocompact buffer row in the table above. Claude Code keeps that space reserved so it can run the summary before hitting the hard limit, rather than crashing into it.

It happened in the session this post came from. Everything Claude and I discussed before the compaction survives only as a ten-line summary at the top of the transcript, and the rest of the conversation starts from there.

Compaction is lossy. A summary can drop a file path or an error message, and the agent may have to re-read files to recover. That is why persistent rules belong in `CLAUDE.md`, which Claude Code reloads from disk after compaction, instead of in a message you typed an hour ago. You can also run `/compact` yourself with a focus, or `/clear` between unrelated tasks.

## Permissions vs sandboxes

I used the phrase "stateless sandboxed environment" to describe running Claude Code in YOLO mode. Both words were wrong.

Stateless applies to the model and the API only. The harness keeps state, and so does my machine. Every file edit is real.

And YOLO mode is not a sandbox. `--dangerously-skip-permissions` only turns off the "may I?" prompts. Claude Code still runs every command on my real machine with my real user account. It can delete files or reach the internet just like I can.

Permissions and isolation are two different controls. Permission modes decide whether a tool call runs and whether you are asked first. Isolation restricts what a command can reach once it runs. A sandbox is the second one: a container, a VM, or Claude Code's built-in sandbox, which uses OS features (Seatbelt on macOS, bubblewrap on Linux) to limit which folders and network hosts a command can touch.

The docs put it plainly: always run `--dangerously-skip-permissions` inside a container, a VM, or the sandbox runtime. That is how cloud sessions work. The agent runs in a throwaway VM, so the lack of prompts is safe. That setup is what makes "close your laptop and go to sleep" possible.

## Common beginner misconceptions

Every one of these is something I believed at some point in the conversation.

1. **The model runs the commands.** No. It asks. The harness runs them.
2. **Tool calls are text that looks like bash.** No. The model is trained for tool use, and the request is a structured block the API returns separately from the text.
3. **The model remembers the session.** No. The API is stateless. The harness resends the full conversation on every call.
4. **Edits happen by line number.** No. The `Edit` tool matches exact text, and it has to match exactly once.
5. **Context files sit outside the context window.** No. They are files on disk that only matter once the harness loads them into the window. The trick is loading less, not skipping the window.
6. **The harness polls the API on a timer.** No. It calls right after each tool finishes.
7. **The model decides when the turn ends by writing a flag.** Partly. The API sets `stop_reason` from how the model stopped, and the harness can override it with a Stop hook.
8. **The starting overhead is tiny.** It was about 50k tokens in my session, and most of it was tool schemas.
9. **YOLO mode is a sandbox.** No. It only removes the prompts. A sandbox is a separate thing.

## Recap: the three layers in one picture

- **Model:** reads the full conversation every call and returns either text or a tool request. Remembers nothing.
- **Harness:** runs those requests on a real machine, keeps the conversation, and decides what goes into the window.
- **Context:** files on disk that only matter once the harness loads them into that window.

```text diagram=layers
┌────────────────────────────────────────────────────────────┐
│ LAYER 3: CONTEXT (files on disk)                           │
│   CLAUDE.md / AGENTS.md   loaded in full at session start  │
│   memory                  loaded at session start          │
│   skills                  name + description at start,     │
│                           body only when the skill is used │
└────────────────────────────────────────────────────────────┘
                │  the harness reads these into the request
                ▼
┌────────────────────────────────────────────────────────────┐
│ LAYER 2: HARNESS (Claude Code, on your machine)            │
│   1. build the request: system prompt + tool list          │
│      + context files + the whole conversation so far       │
│   2. send it to the API                                    │
│   5. read the reply                                        │
│      no tool_use  -> show the text, wait for you           │
│      tool_use     -> check permission, run the tool,       │
│                      add the result, go back to 1          │
└────────────────────────────────────────────────────────────┘
                │  2. request              ▲  4. reply
                ▼                         │
┌────────────────────────────────────────────────────────────┐
│ LAYER 1: MODEL (lab servers, remembers nothing)            │
│   3. read every token it was sent                          │
│   4. reply with text and/or a tool_use block               │
└────────────────────────────────────────────────────────────┘
```

The model decides, the harness does, and the context files are what the harness feeds the model.

## Further reading

- [How Claude Code works](https://code.claude.com/docs/en/how-claude-code-works): Anthropic's own description of the agentic loop and what the word harness means.
- [Explore the context window](https://code.claude.com/docs/en/context-window): an interactive timeline of what loads when, and what survives compaction.
- [How Claude remembers your project](https://code.claude.com/docs/en/memory): the `CLAUDE.md`, `AGENTS.md` and auto memory loading rules.
- [Handling stop reasons](https://platform.claude.com/docs/en/api/handling-stop-reasons): every `stop_reason` value and the recommended tool loop.
- [Prompt caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching): how the cached prefix works and what it costs.
- [Choose a sandbox environment](https://code.claude.com/docs/en/sandbox-environments): permissions versus isolation, and the sandbox options.
- [Equipping agents for the real world with Agent Skills](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills): where progressive disclosure is explained.
- [Effective context engineering for AI agents](https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents): context as a finite budget, just-in-time retrieval, and compaction.
- [Building effective agents](https://www.anthropic.com/engineering/building-effective-agents): the post that defines an agent as a model directing its own tool use in a loop.
- [Harness design for long-running application development](https://www.anthropic.com/engineering/harness-design-long-running-apps): what a harness looks like for multi-hour tasks.
- [Harness engineering](https://openai.com/index/harness-engineering/): OpenAI's post that gave the discipline its name.
- [Introducing AGENTS.md](https://openai.com/index/introducing-agents-md/): the file that became the shared convention.
- [Follow goals with Codex](https://learn.chatgpt.com/use-cases/follow-goals): the `/goal` mode from the top of this post.
