The harness is most of the agent
When an agent misbehaves, the instinct is to rewrite the prompt or upgrade the model. Usually the fault sits in the scaffolding that neither of those touches.
An agent demos beautifully. It ships. Within a week it is looping on a task it finished twenty minutes ago, confidently reporting success on a step that failed, and forgetting a constraint the user stated at the start of the session.
The team’s first move is almost always the same: rewrite the system prompt. When that yields a few percent, they upgrade the model. That yields a few percent more, and the same three failures keep happening.
The failures were never in the prompt or the model. They were in the harness.
What the harness actually is
The harness is everything between a user’s request and the model’s tokens, and everything that happens after those tokens come back. The control loop. The tool surface. The policy for what stays in context and what gets dropped. How errors re-enter the conversation. What the agent is allowed to do without asking. What you can reconstruct afterwards when something goes wrong.
None of that is prompt engineering, and none of it improves when a better model ships. It is ordinary software engineering applied to an unusual runtime — one where the component in the middle is probabilistic, reads everything you hand it as potentially meaningful, and cannot resolve an ambiguity once a tool call is already underway.
The loop is a state machine, not a while-true
Most agent loops start as while (not done) { call model; run tools } and stay
that way well past the point where they should have grown up.
The questions a real loop has to answer are specific. What counts as done — the model saying so, or a verifiable condition? What happens when a tool errors: does the agent see the error, a summary of it, or nothing? If a tool times out, is that a failure or a retry? When the same tool is called with the same arguments three times in a row, is that legitimate polling or a stuck agent?
Agents that “give up early” and agents that “spin forever” are usually the same bug seen from two sides: the loop has no opinion about progress. Track it explicitly. A turn that produces no state change is not the same as a turn that edited a file, and the loop should be able to tell them apart.
The smallest useful question after every tool call is not did the model sound confident? It is what changed? If the answer is nothing, the harness should know what to do with that fact: retry with a bound, ask a person, or stop with an honest explanation.
The practical version is less glamorous than a prompt review. Put the agent in front of a real catalogue migration: it reads a product record, calls a pricing service, writes a result, and checks the write. A healthy run turns calls into visible state changes. A stuck run can produce a busy trace — retries, fresh summaries, increasingly confident prose — while the catalogue has not moved by one record. That gap is the signal worth watching.
Request → context policy → model proposal → tool gateway → observed state. The harness owns the transitions between each step.
The harness loop: request → model → tool → observe → decide.
Tool design is API design for a reader who cannot ask questions
Your tool names, descriptions, and schemas are not metadata. They are the documentation, and the model reads them the way a new engineer reads an unfamiliar codebase at 2am — quickly, under pressure, with no one to ask.
This changes what good looks like. A description should say when not to use the tool, not just what it does. Two tools with overlapping purposes are worse than one tool with a clear parameter, because now every call site is a judgement call. A parameter that is “usually correct to omit” should say so in its own description, because the model sees the parameter before it sees your intent.
Errors deserve the most attention and get the least. See Tool errors are instructions — most of the retry failures I have debugged were an error string written for a human reading a stack trace.
Context is a budget with a policy, not a bucket
Long-running agents fail in a characteristic way: they violate a constraint the user stated forty turns ago. The constraint did not become less true. It fell out of the window, or it was summarized into something vague enough to ignore.
Treat context as a budget with an explicit policy. Some things are pinned and never evicted — the task, hard constraints, the user’s stated preferences. Some things are summarized on a schedule, and the summary should preserve decisions and their reasons, not just topics. Some things are cheaper to re-fetch than to carry, and file contents usually fall here.
The useful discipline is to write the policy down. If you cannot describe what your agent drops first when it runs low, it is dropping something important at random.
A context policy keeps constraints, summarizes decisions, and fetches old material when needed.
Make failure boring
Assume every tool call can fail halfway. The harness question is not how to prevent that, it is what the world looks like afterwards.
Prefer operations that can be run twice without harm. Where you cannot, make the agent able to observe what already happened before it acts — a check before a write beats an apology after one. Retries should change something: same call, same arguments, same failure is not a retry, it is a loop with extra latency.
Partial progress should survive. An agent that completes eight of ten steps and crashes should be resumable at step nine, not restarted at step one. This is unglamorous plumbing and it is most of the difference between an agent that is pleasant to run and one people quietly stop using.
You cannot debug what you cannot replay
When an agent does something strange, the question is always “what did it actually see?” If your logs contain a tidy summary of the conversation rather than the literal tool calls and literal returns, you cannot answer it.
Log the real payloads. Log the arguments the model chose, not the arguments you expected. Being able to replay a session against a modified harness — same inputs, new tool descriptions — turns agent debugging from archaeology into something closer to a test suite.
The test that tells you where you are
Swap the model. A different family, or the same family a tier down.
If quality holds roughly steady, your harness is doing the work and the model is a component you can upgrade. If quality collapses, your harness was leaning on one model’s particular habits — its tolerance for a vague tool description, its willingness to re-read a file, its instinct to check before writing. That is not a system. That is a demo that happens to run.
The harness is the part you own, the part that compounds, and the part that still works in a year. Most teams spend their effort on the part that does not.
Track state changes alongside tool calls. Activity alone is not progress.