Editor’s note
Two Fridays ago, our agent checked the weather. Last week, we gave it a plan and taught the harness to tap it on the shoulder when it wandered. This week, the same loop gets dropped into a codebase.
In the final part of Inside the Harness, we see how a change of tools creates a coding agent, then deal with what happens when its context and capabilities no longer fit. The loop stays put. The harness grows around it.
This also concludes our Friday scheduling experiment. Normal service resumes next issue. But before we dive in…
Leave the harness exactly where it is. Swap the tools, point it at a codebase, and the same loop becomes a coding agent.
Same harness, new tools
The experiment here is deliberately simple: keep the harness and its system prompt as they are, and change only the tools. If the harness skeleton is genuinely generic, that one swap should be enough to turn a research agent into a coding agent.
The system prompt, therefore, stays almost untouched:
<agent name=”Tara”>
You’re a helpful agent.
</agent>
<task>
Your task is to use the given tools to solve the user’s problem.
</task>
<notes>
- Parallel tool calling is supported.
</notes>
Only the toolset changes. Out go the research tools; in come three tools that let the agent work directly with a codebase, defined here in JSON:
[
{
“name”: “read_file”,
“description”: “Read a file from disk.”,
“input_schema”: {
“type”: “object”,
“properties”: {
“path”: { “type”: “string” }
},
“required”: [”path”]
}
},
{
“name”: “write_file”,
“description”: “Write content to a file. Set is_appending=true to append instead of overwrite.”,
“input_schema”: {
“type”: “object”,
“properties”: {
“path”: { “type”: “string” },
“content”: { “type”: “string” },
“is_appending”: { “type”: “boolean”, “default”: false }
},
“required”: [”path”, “content”]
}
},
{
“name”: “bash”,
“description”: “Execute a shell command.”,
“input_schema”: {
“type”: “object”,
“properties”: {
“command”: { “type”: “string” }
},
“required”: [”command”]
}
}
]
That’s it! read_file inspects the codebase, write_file changes it, and bash runs commands. Nothing else in the loop moves.
How this fixes a bug
Say the user asks the agent to fix a failing test. The run looks roughly like this:
user: add() in src/adder.py has a bug, pytest is failing, can you fix it?
assistant → bash(”pytest tests/test_adder.py”)
tool_result:
FAILED tests/test_adder.py::test_negative
assert add(-1, 1) == 0 # got -2
assistant → read_file(”src/adder.py”)
tool_result:
def add(a, b):
return a - b # ← obvious at a glance
assistant → write_file(
“src/adder.py”,
“def add(a, b):\n return a + b\n”
)
assistant → bash(”pytest tests/test_adder.py”)
tool_result:
2 passed in 0.03s
assistant: Fixed. add() was written as a - b; changed it back to a + b, all tests pass.
That’s a complete code-change loop: run the test, inspect the source, make the change, then verify it. Underneath, it is still ReAct—think → tool use → tool result → think. No new control flow required.
Drawn out:
Three tools and one loop are enough for a straightforward bug fix. For something larger, such as a feature spanning several files, add the Plan-then-Act layer from Part 2. The job changes; the underlying structure does not.
That is the point: the harness skeleton is generic, while the tools determine the agent’s shape. Replace web_search and web_fetch with read_file, write_file, and bash, and a research agent becomes a coding agent.
Speaking of context cost — some tools’ return values are naturally huge. A bash build can dump thousands of lines of stdout. A read_file on a real file is hundreds of lines easy. A web_fetch against a real article is tens of thousands of tokens in one shot. Stuff all of that into context as-is and ten turns later the window pops.
For less hype and more engineering, pull up a chair.
Why does write_file support appending?
It is less about convenience than context. Without an append option, adding one line to a 1,000-line file could mean sending the entire file through the tool call again—and leaving all 1,000 lines sitting in the context window. Incremental writes avoid paying that cost for a tiny change.
The problem gets worse with tool results. A build can produce thousands of lines of output, while reading a real file can consume hundreds more. Keep feeding all of that back into the loop, and even a generous context window begins to look rather less generous.
The harness now needs somewhere else to put it. That brings us to context offloading.
Offloading: When context won’t fit
Every tool result goes back into the message history. A repository-wide search or noisy build can add thousands of tokens in a single call—useful in the moment, dead weight a few turns later.
Even a 200k-token context window begins to look rather less generous during a serious coding task. As it fills, the agent can lose track of earlier details, contradict itself, or simply run out of room.
The harness layer’s first answer is context offloading.
How offloading works
The rule is simple: when a tool result exceeds a set size, the harness writes the raw content to disk and leaves a path in the context. If the agent needs the details later, it retrieves them with read_file.
Suppose a bash call produces 1,000 tokens. The tool result the agent receives might look like this:
{
“role”: “tool”,
“tool_call_id”: “call_042”,
“content”: “[OFFLOADED] saved to abc123.log (1024 tokens). Use read_file to retrieve.”
}
The 1,000 tokens never enter the agent’s context. The harness intercepts the output and replaces it with a roughly 30-token pointer.
From the agent’s perspective, nothing else changes. It still calls bash, receives a tool result, and decides whether it needs to read the file. No new tool. No change to the ReAct loop. Just a smaller payload.
Offloading is not compression
It is easy to confuse offloading with context compression. Both make room in the context window, but they do it differently:
They diverge on three axes:
Trigger timing: Offloading happens the moment the tool_result is born — the harness sees the return is over threshold and externalizes it on the spot. Compression happens the moment context is about to blow — only when occupancy is near the ceiling does the harness go back, scan history, and summarize.
Lossy vs. lossless: Offloading is lossless and recoverable — not a byte of the original is lost; only the storage location changed, and the agent gets it back verbatim with read_file. Compression is lossy — summarize crushes 10 turns of conversation into a 200-word summary, and the dropped info is gone for good.
Layer of abstraction: Offloading lives at the mechanism layer, handling hot/cold tiering of individual tool_results. Compression lives at the last-resort layer, packing whole stretches of history into archives. One is like the OS’s paging scheduler; the other is like tarring up old logs.
The framing I like is this: offloading is hot/cold tiered storage; Compression is archival compression. The former goes first because it’s lossless and cheap. The latter is the fallback because it’s lossy but life-saving.
That is offloading’s place in the harness: when tool results are too large, move them instead of shrinking them.
But fitting the agent’s working data solves only half the problem. Its capabilities may not fit either. That’s where Skill comes in.
Skill: Capability organization at the harness layer
So far, the harness has been helping one agent complete one task. Production introduces a different problem: the same agent may need dozens—or hundreds—of specialist capabilities. It might write Python one moment and diagnose a Kubernetes alert the next, each with a different set of instructions and pitfalls.
Loading all that guidance into the system prompt does not scale. If one Skill takes 3,000 tokens, fifty of them consume 150,000 tokens before the task has even begun. Even if they fit, the model must reason through a wall of mostly irrelevant instructions on every turn.
The harness’s answer to this capability explosion is Skill: make every capability available without loading all of them into context at once.
Load only the index at startup
Skill uses a simple form of progressive loading. Each Skill lives in a folder containing a SKILL.md file. At startup, the harness reads only the file’s frontmatter—a small metadata header containing its name and description—and adds that information to the system prompt’s <skills> index.
The full instructions remain on disk until the agent decides it needs them.
Here is the <skill-system> block from Claude Code:
<skill-system>
<instruction>
You have access to skills that provide optimized workflows for specific tasks. Each skill contains best practices, frameworks, and references to additional resources.
**Progressive Loading Pattern:**
1. When a user query matches a skill’s use case, immediately call `read_file` on the skill’s main file using the path attribute provided in the skill tag below
2. If an explicit requested skill is provided in the system context, load that skill first even if the user message is short
3. Read and understand the skill’s workflow and instructions
4. The skill file contains references to external resources under the same folder
5. Load referenced resources only when needed during execution
6. Follow the skill’s instructions precisely
</instruction>
<skills>
<skill name=”image-generation” path=”/Users/henry/.agents/skills/image-generation/SKILL.md”>
Use this skill when the user requests to generate, create, imagine, or visualize images ...
</skill>
<skill name=”skill-creator” path=”/Users/henry/.agents/skills/skill-creator/SKILL.md”>
Create new skills, modify and improve existing skills, and measure skill performance ...
</skill>
</skills>
</skill-system>
What the prompt leaves out
Two absences matter:
No new tool. The harness does not need an invoke_skill() or load_skill() function. When the model selects a Skill, it uses the coding agent’s existing read_file tool to load the relevant SKILL.md. Skill simply reuses the filesystem.
No Skill body. The index contains only a name, description, and path. The full instructions stay on disk until they are needed.
That is progressive disclosure at the harness layer: keep the map in context, then load the instructions on demand.
Why not retrieve the right Skill?
Why not treat Skill selection as a search problem? Use BM25 or embeddings to match the user’s request against every Skill description, load the top results, and move on.
That can work—but if retrieval happens only once, the candidate set is fixed before the agent fully understands the task.
“Debug the alert that just fired” might begin with checking logs. Those logs may point towards a recent deployment, and resolving the incident may eventually require a company-specific postmortem. Each Skill becomes relevant at a different point in the reasoning chain.
Keeping the compact <skills> index in the system prompt lets the model reconsider its options on every ReAct turn. Retrieval could also be rerun each time, so the real choice is not search versus reasoning. It is one-shot selection versus selection that evolves with the task.
The full sequence
Startup, selection, and loading look like this:
The one line in this sequence diagram worth observing is the read_file call in the middle. It uses the same generic tool the Coding Agent already had. No new abstraction. Harness support for Skill costs zero net new tool surface.
Skill on the harness map
Skill does not change the ReAct loop or add another tool. It simply separates what the agent should always see from what it should load only when needed.
That small act of deferral is what lets an agent carry a hundred capabilities without dragging a hundred playbooks through every turn. With that, the harness map is complete.
Closing the loop on Inside the Harness
Over the past three Fridays, we’ve taken the agent harness apart together, one mechanism at a time. If you’ve been with us since that first weather forecast, thank you for following the loop all the way through.
By now, a harness should look less like black-box middleware and more like a stack of small mechanisms:
ReAct makes the agent move. It supplies the basic rhythm: reason, act, observe, repeat.
Plan-then-Act gives it direction. The agent works from an explicit plan instead of chasing whatever catches its attention next.
Nudge keeps it on track. The harness brings the plan back into focus when it begins to fade.
Offloading protects the context window. Oversized results move elsewhere without being permanently discarded.
Skill makes capabilities manageable. The index stays visible; the full instructions appear only when needed.
Each layer answers a failure exposed by the one beneath it. Each does one job, and you can tune each without rebuilding the entire system.
That is the larger point of harness engineering. The ReAct loop makes an agent run. The harness is what gives it a chance of surviving contact with reality.
Keep up with Henry
If three weeks inside the harness have left you wanting to poke at one yourself, Henry (the author responsible for your past three Fridays inside the harness) is building LLM Space, an open-source desktop app for prototyping agent ideas, inspecting harness runs, and replaying the bits that went wrong. Rather on theme, really.
That’s it for this one. We’ll pick up the conversation next week.
Until then, keep building.
Tanya D’cruz
Editor-in-Chief





