# Build Your Own Agent Harness

Source: https://tpiros.dev/blog/build-your-own-agent-harness

Most developers I talk to use an agent every day and have never seen the loop that drives it. They've watched a coding assistant read files, run commands and edit code for twenty minutes unattended, and they assume the machinery underneath must be enormous. It isn't. The core of it fits on one screen.

That core is the harness: everything in the system that isn't the model. The loop, the tool definitions and their descriptions, the system instruction, the logic that decides what context the model sees on each step, the checks that catch it misbehaving. A one-liner doing the rounds compresses it nicely: agent = model + harness, and if you're not the model, you're the harness.

The model is the part you don't build. You rent it, and it gets better every few months whether you do anything or not. The harness is the part that only improves if you improve it, and on any agent that has to work more than once, it is where most of the effort ends up. When an agent fails in production it is rarely because the model was too weak for the task. It is because the harness around it was too thin: no retry, so a blip ended the run; no gate, so it ran the wrong command; no limit on the history, so it walked off the end of its context window. This post is about what that part is, how you build it, and why it is worth your time. I won't show a single line of code; code dates fast and hides the idea behind syntax. We'll watch a sketch of a harness run instead, and break it until the shape is clear.

## The loop is the smallest harness

A harness is a loop, and the loop is short enough to say in four lines. Send the conversation so far to the model, along with the list of tools it is allowed to ask for. Read the reply. If the reply asks for tools, run each one, append the result to the conversation, and send the whole thing back. If the reply is plain text, that is the answer: hand it back and stop.

That is already a complete agent. Here is a bare version of it running a small task, where each arrow is the loop reporting what it just did:

Three trips around the loop: the model asked for a tool, saw the result, asked for another, then answered. Notice that the model never did anything itself. It only produced text saying what it wanted, and every action in that transcript was the harness obeying. This is the smallest harness there is, and it is where everyone starts. It also works perfectly on small tasks, which is exactly the trap. Point it at real work and it breaks, and how it breaks is the rest of this post.

## What building one actually means

Building a harness is not piling features onto that loop. The loop only lets you stand in five places, and building a harness is deciding what goes in each of them. You do not get to choose the five. The shape of the loop hands them to you: before the run, before each model call, around the model call, around each tool call, and after the run. Every mature harness lands on the same five, down to the names. Claude Code and the Claude Agent SDK expose them as hooks called `SessionStart` and `SessionEnd`, `UserPromptSubmit`, `PreToolUse` and `PostToolUse`, and `PreCompact`. Nobody copied that layout from anyone; the loop has five seams, so everyone who instruments it ends up at the same five.

Here is each one, and the failure that teaches you why it has to exist.

### Run start

Before the first message reaches the model, the harness sets the scene. It validates the task, loads whatever the agent is meant to remember (notes from past sessions, user preferences, project facts), and assembles the system instruction. It is easy to overlook, because in the bare loop it is just the step that builds the opening message. But everything the model knows on turn one, it knows because this step put it there.

### Before each model call

Give the agent a long job and the conversation only grows, because every tool result is appended and nothing is ever removed. Sooner or later the history is larger than the context window and the request is rejected outright, and the bare loop never sees it coming. This is the most important of the five, because the history you pass in is the model's entire world, so this is where you decide what stays and what gets summarised away.

The crude version drops the oldest turns. A good one spends a cheap model call to summarise them first, so the agent keeps the gist of what it already did. Either way the rule holds: keep the history inside the window, and never cut it so that a tool result is left without the call that asked for it.

> If you are interested in learning more about memory, read my other piece [Agent memory, end to end](https://tpiros.dev/blog/agent-memory-end-to-end).

### Around the model call

The model call is the one step that leaves your machine, so it is the one that fails for reasons that are nothing to do with you: a rate limit, a momentary overload, a network blip. In the bare loop a single failed request throws and an hour of work dies with it. A few retries with backoff turn most of those into a pause nobody notices.

This is also where a cache lives, and where you route an easy turn to a cheap model and a hard one to an expensive one. The loop around it never changes; only the thing in the middle does.

### Around each tool call

When the model asks for a tool, the bare loop runs it, no questions asked. That is fine for reading a file and alarming for deleting one. The fix is a gate between the request and the action: cheap, reversible tools pass straight through, and anything destructive stops for a policy check or a human.

A denied call is not an error; it goes back to the model as a result, and the model adapts. (That stray space in `rm -rf / tmp/build` is the famous typo that wipes the whole disk instead of one folder, and the gate is what stands between the model's slip and your filesystem.) This is also where every tool call gets logged, which is the difference between an agent you can audit and one that did twenty things you cannot reconstruct.

### Run end

A run stops for good reasons and bad ones, and the bare loop cannot tell them apart: any reply without a tool call counts as finished, even one the model cut off mid-sentence because it ran out of room. Run end is where you check why you stopped, retry a truncated answer with more headroom, save what is worth keeping, and enforce the limits that stop a confused agent looping forever or quietly spending a fortune.

## Why bother

Look back at those five places and notice that every fix started as a failure. That is the whole discipline, and it has a name. Harness engineering is making the agent better by improving the part you own instead of waiting for a better model. You treat every misbehaviour as a defect in the harness, find the place it happened, and add a rule there so it cannot happen again.

The agent read a secrets file it had no business touching? A deny rule goes into the gate, and that file is off the table for good. A run died because the history was cut in the wrong place? The fix goes into the context step, and that crash is finished. Each rule lands at exactly one of the five places and traces back to the failure that caused it, and that traceability is also the test for keeping it: instructions compete for the model's attention, so a rule you cannot tie to a real incident is a rule worth deleting. The good rules stay quiet on success and speak up only on failure, handing the error back as the tool result so the model corrects itself on the next turn, rather than sitting in the system prompt as advice it can talk itself out of on a bad day.

Here is the reason it is worth the effort, and not just a tax you pay to keep the thing alive. Do this for a few months and the rules stop being a pile of patches and become the thing of value. They are your team's accumulated experience with this task, written down and enforced. A newer model makes the loop smarter, but it shows up knowing none of it, and so does a competitor renting the same model you are. The model is the part everyone can buy. The harness is the part only you have, and it compounds: every failure you survive makes the agent a little harder to break the same way again. That is the asset, and it is why this is work worth doing rather than waiting for the next model to do it for you.

None of which means you must write all five yourself. A framework is a set of ready-made answers to these five questions, a compaction strategy and a retry policy and a permission model, chosen by someone who never met your task. Adopt one when its answers fit and you have checked which of the five it actually lets you reach, because you will need to reach in eventually. Build your own when the fit is the product, like an approval flow that mirrors how your organisation really signs things off, or when you simply want to understand the thing you are running. What goes in each place depends entirely on the task, and that is where the judgement lives: a support agent guards customer records and hands a failed run to a human, while an hour-long coding agent lets sandboxed edits run ungated because the diff gets reviewed after. Same five places, opposite rules.

## Wrapping up

The loop is small, and it stays small. Send the conversation, run the tools, append the results, repeat. Everything that makes an agent worth trusting lives in the five places around that loop, and harness engineering is the habit of turning each failure into a permanent rule at one of them.

Build a harness like this and break it yourself. Afterwards every agent you use reads differently, because you will know which of the five is speaking when it asks for approval, summarises its own history, or quietly retries. When you outgrow a single loop, I've written about [a local managed agent in four planes](/blog/building-a-local-managed-agent-harness) for the architecture-scale version of the same idea.
