Sitemap

AI Agent Harness Engineering: Building Reliable Agents

14 min read2 days ago

--

Press enter or click to view image in full size

Most agent failures start in the harness around the model, not in the model itself.

When an agent gives a wrong answer, we blame the model. We swap in a bigger one, rewrite the prompt, add a few examples, and hope. Sometimes that works. Often the model was never the problem.

The harness is everything around the model that lets it act: the context it sees, the tools it can call, the state it carries from one step to the next, and the checks that decide whether the task is finished. A capable model inside a weak harness will fail in ways that look exactly like reasoning errors. From the outside, you cannot tell the difference without looking at what the model actually received.

That is the core idea of AI agent harness engineering. The model supplies capability. The harness determines how that capability meets a task. And a newer question is emerging from the research: can an agent improve its own harness without quietly making the system worse?

What a harness actually is

An AI agent harness is the software and configuration around a model that lets it operate in an environment. It assembles the context, exposes tools, tracks task state, controls execution, and verifies results.

That is broader than a prompt or a tool wrapper. A coding agent has to inspect a repository, run commands, remember what it was doing, recover from failed builds, and verify its own changes. A research agent needs different tools and different checks, but the principle holds: the system around the model decides how the model’s capability reaches the user.

A 2026 survey describes an agent as a model coupled to an execution harness, and splits that harness into observation, context, control, action, state, and verification. Guo et al., “From Question Answering to Task Completion” Those categories are useful for organizing a design. The question I find more useful in practice is simpler:

“What did the harness give the model at the moment it made its decision?”

If you cannot answer that for a given step, you cannot diagnose the failure. You can only guess.

I wrote about the control-flow side in Graph Engineering: Why the Loop Was Always Inside It. A harness decides when an action ends, what can run next, and which state carries forward. This piece is about the layers that sit beside that control flow: context, tools, memory, recovery, and evaluation.

A worked example: the refund that should have been approved

Consider a customer support agent that handles refund requests. The policy is clear: refunds within 30 days of purchase are approved automatically, except for annual plans that were already partially used. The model is good. It reads policies well and writes clear replies.

A customer writes in asking for a refund on an annual plan, eleven days after purchase. The agent denies it.

The team’s first instinct is to blame the model. They read the transcript and see a confident, polite refusal that cites the wrong rule. So they try a stronger model. It denies the refund too, with better prose.

When they finally pull the context the agent received at the decision step, the picture changes. The policy document in the context was from the previous fiscal year, when the partial-use exception did not exist. A retrieval step had pulled the old version because the new one was indexed under a different title. The model did not misread the rule. It read a rule that was no longer in force.

Nothing about the model needed fixing. The harness needed three things: versioned policies with effective dates, a retrieval step that filtered by effective date, and a trace that recorded which policy version each answer relied on. The model stayed the same. The correct answer was available to the system all along, just not to the model at that step.

This is the pattern behind most of what follows. The failure happened in the harness, and it looked like a model failure.

Three failure patterns that look like model problems

The first failure looks like weak reasoning. The agent gives a wrong answer, so the team reaches for a larger model or a better prompt. But the context may contain stale facts, the tool may have returned an unexpected format, or an important constraint may never have reached the model. The model answered the wrong question because the harness assembled the wrong one.

The second failure looks like a speed or cost problem. The team switches to a faster model, but the system still loads the full conversation history at every step. The model got faster. The work around it did not get smaller. A retry loop that repeats the same failing action will stay expensive no matter which model runs it.

The third failure looks like a confidence problem. The agent gives a wrong answer with a high confidence score, and the team adds another metric to explain it. A metric does not explain anything. A trace that records the context, tool calls, active constraints, and observed results can. The goal is not more numbers. It is finding the step where the system went wrong.

Each of these failures has the same shape. The visible symptom is in the model’s output. The cause is in what the harness did before the model ran.

Is context a budget or a bin?

Context determines what an agent can reason about, how long a task takes, and how much it costs. Yet many teams treat the context window as a bin: put everything in and let the model sort it out.

That approach mixes current instructions with old history, useful memory with noise, and facts with expired assumptions. A fluent answer does not prove the agent had the right context. It only proves the model produced plausible language from whatever it received.

I have dumped agent-run context and read it as if I were the model. The gap between the prompt I had designed and the context the agent actually received was larger than I expected. The designed prompt was clean. The real one had quietly accumulated details that looked relevant but were not. Nobody had decided to include them. They arrived through retrieval, through conversation history, and through tool outputs that nobody had trimmed.

Treating context as a budget changes the questions you ask. How many tokens does each source contribute? Which sources are current? Which ones would change the answer if they were removed? If a source cannot justify its place, it should not be there.

Caching adds another dependency. Reusing a stable prefix can cut repeated work, while changing that prefix can break reuse. Prompt ordering and context construction are therefore operational decisions, not formatting preferences. The actual savings depend on the provider, the model, and the workload, so a benchmark from one system should not be read as a universal cost estimate. Measure it on your own traffic.

How many tools is too many?

Tools turn a model’s suggestions into actions, but each additional tool adds a choice the model has to make. Overlapping descriptions, unclear schemas, and irrelevant tools present in every request all make selection harder and inflate the context.

The useful principle is not a magic tool-count limit. Expose the capabilities the current task needs, give each tool a clear contract, and tell the agent what to do when a call fails. For a large tool library, route or load tools by task instead of assuming every agent needs every option at every step.

Each tool should define its inputs, its outputs, its side effects, and its failure modes. Those four items are the difference between a tool the agent can use confidently and one it will misuse. A side effect that is not documented is a surprise waiting to happen.

The harness should also know which actions are safe to repeat. Reading a record can be retried freely. Sending an email cannot. Issuing a refund needs an idempotency key, or a retry can pay the customer twice. Some actions should require a person to approve them before they run. Those rules belong in the harness, not in the model’s hopes.

Why do memories and constraints expire?

An agent can retrieve the right memory for the wrong situation. A correction that was valid on Tuesday can mislead the agent on Friday if the conditions that made it valid were never stored with it. The memory was accurate. Its scope was not recorded.

I explored the memory side in AI Agent Memory That Actually Works: Signal Over Storage. The short version is that storage is cheap and signal is not. Remembering everything guarantees that the wrong thing will eventually get retrieved.

Constraints have the same propagation problem, and they are worse because they are easy to assume. Saving a rule to a file does nothing if the context builder never includes it. A constraint has to be retrieved when it applies, and it needs enough provenance to be understood: where it came from, when it took effect, and what it overrides.

State goes stale too. Some information belongs only to the current step. Some should survive the session. Some deserves long-term storage. Treating all of it as equally durable fills the context with details that were useful once and are now noise. The harness needs an explicit policy for retaining, refreshing, archiving, or dropping state. Without one, the state quietly becomes a second, unreviewed source of truth.

What a harness has to do when things break

A production agent should expect tools to fail, actions to time out, and results to come back incomplete. Recovery is part of the harness. The model cannot supply it on its own, because the model does not know whether the database write actually committed.

The runtime decides whether to retry, replan, ask for help, or stop. It can verify that an action had the expected effect before telling the user it succeeded. A refund tool that returns a 200 status is not the same as a refund that appears on the customer’s statement, and the harness should know the difference.

Separating planning, execution, and evaluation helps for the same reason. When these are tangled together, a failed action turns the whole run into a mystery. When they are separate, you can ask a precise question: did the plan make sense, did the execution do what the plan said, and did the evaluation check the right thing?

The basic audit still pays off most. Capture what the model saw at each step, then compare that with what the task required. Was the current constraint present? Was the retrieved memory still valid? Did the tool return the result the agent believed it returned? Those questions lead to fixes. A confidence score alone does not.

What does a trace look like in code?

Tracing is the cheapest way to make a harness diagnosable, and it does not need to be elaborate. The minimum useful record is what the model saw, what it called, and what came back.

Get Michiel Horstman’s stories in your inbox

Join Medium for free to get updates from this writer.

Here is a small recorder for an agent loop. It writes each step as a JSON line, so the trace can be replayed, diffed, and searched later:

import json
import time
from dataclasses import asdict, dataclass, field
from termcolor import colored
@dataclass
class StepRecord:
step: int
context_sources: list[str]
context_tokens: int
tool: str | None
tool_input: dict
tool_output: str
error: str | None
timestamp: float = field(default_factory=time.time)
class HarnessTrace:
def __init__(self, path: str):
self.path = path
self.records: list[StepRecord] = []
def record(self, rec: StepRecord) -> None:
self.records.append(rec)
status = colored("FAIL", "red") if rec.error else colored("OK", "green")
print(f"[{rec.step:03d}] {status} tool={rec.tool} sources={len(rec.context_sources)}")
with open(self.path, "a") as f:
f.write(json.dumps(asdict(rec)) + "\n")
def first_failure(self) -> StepRecord | None:
return next((r for r in self.records if r.error), None)
def missing_constraints(context: str, required: list[str]) -> list[str]:
return [c for c in required if c not in context]

The missing_constraints function is deliberately crude. A substring check will miss paraphrases and can produce false alarms. In a production system, constraints should carry stable identifiers and the harness should check for those identifiers, not for sentences. But even the crude version catches the most common failure: a rule that was supposed to be in context and was not.

Notice what the trace records and what it leaves out. It records sources, not full documents, so the trace stays small enough to keep. It records the tool input and output, because those are the facts you will need when a result looks wrong. It does not record the model’s reasoning as ground truth. The reasoning is useful to read, but the evidence is what the harness did and saw.

Can a harness improve itself?

A self-evolving harness uses experience from agent runs to propose persistent changes to its prompts, memory, workflows, tools, or runtime code. The model weights stay fixed. The system changes around them. A growing body of research is testing this idea.

The terms matter, and they are easy to blur. Self-evolution describes a process that changes the harness. Self-improvement means the change produced a measured gain. A new prompt or tool is a revision. It is not proof that the agent got better. Most of the difficulty in this area lies in the distance between those two words.

The October 2026 preprint Self-Evolving Agent Harness formalizes the feedback cycle: collect runs, propose revisions, evaluate candidates, and retain accepted changes for later use. It discusses theoretical conditions for generalization and regression control. It does not report a new benchmark of its own, so it is best read as a framework rather than as evidence.

Other preprints report experiments, though they use different systems and evaluation setups, so their numbers are not a shared leaderboard. One multi-task study uses the same frozen model as both solver and proposer, then reports gains on held-out and out-of-distribution tasks. Xu DarwinX selects among a population of harness variants rather than accepting a single edit, which guards against keeping local wins that damage other tasks. Zhang et al. HarnessFix takes a narrower route: it uses execution traces to locate a specific flaw, proposes a scoped repair, and tests for regressions. Its authors report improvements over the initial harnesses on four benchmarks. Chen et al.

Many of these are arXiv preprints and have not been peer reviewed. That does not make them wrong, but it does mean you should read the methods before you read the numbers. Still, the papers together show why the old debate between model and harness is no longer enough. We now also need to ask how harnesses change, what evidence justifies a change, and what happens when an update fails.

How do you update a harness without making it brittle?

Treat a harness update like a software patch. Give it a traceable reason, limit its scope, run it through checks the patch author did not control, and keep a way back.

Press enter or click to view image in full size
  1. Capture a representative run. Record the context, tool inputs and outputs, state changes, errors, and completion evidence. The final answer rarely shows where the run went wrong.
  2. Diagnose before editing. Point to an observable step: missing context, an incorrect tool result, lost state, or a completion signal that arrived too early. “The model got confused” is not yet a diagnosis. It is a description of the symptom.
  3. Limit the edit. Change the instruction, tool behavior, memory policy, or workflow tied to the failure. Do not rewrite the whole prompt because one case went wrong. Keep the old version and write down the intended effect before you run anything.
  4. Test on separate tasks. Use fresh validation examples that were not used to propose the patch. Replay tasks the old harness already handled. A candidate that fixes one case but breaks several old successes is not a clear improvement, and a candidate that only looks good on the examples that inspired it has not been tested at all.
  5. Keep the evaluator independent. The candidate must not be able to weaken the criteria that judge it. If the same system writes the change and writes the rubric, the rubric will drift toward whatever the change does. Use deterministic checks where they exist, and explicit criteria with independent review where judgment is required.
  6. Stage and roll back. Version the change, save the test results, and release it gradually. If behavior regresses, restore the previous harness rather than asking the changed system to certify itself.

The reason these steps matter is that a self-editing harness can fail quietly. Each edit looks reasonable on its own. The damage shows up as a slow drop in success on tasks nobody was watching.

What should you measure?

Task success is necessary, but it is not the whole evaluation. A harness can raise its success rate on one benchmark while making tool errors more frequent, slowing recovery, or spending twice as many tokens per task. A complete picture includes regressions on tasks the previous harness passed, transfer to held-out tasks, tool error rates, recovery behavior, resource use, and whether verification checks the actual result.

The right measures depend on the work. For a coding agent, check whether the tests pass and whether the agent changed files outside the scope of the task. For a research agent, check whether the cited evidence actually supports the answer. For a support agent, check whether the refund was issued correctly, not whether the reply sounded polite.

Measure the work the agent is supposed to do, not how convincing its output sounds.

Convincing output is the trap. A fluent, well-structured answer feels correct, and that feeling is exactly what a weak verification step fails to challenge. Good verification is often boring: it compares the result to a source of truth, checks an invariant, or confirms that an external system changed state.

The October paper makes a distinction every builder should internalize: a harness can evolve without improving. I would not ship a self-editing agent until its updates can be inspected, tested against the old behavior, and rolled back.

Where should a team start?

A team that is just beginning does not need a self-evolving harness. It needs the boring parts first.

Start with tracing. Record the context, tool calls, and results for every run, even before you know what you are looking for. Traces are cheap to keep and expensive to reconstruct after the fact.

Next, version the things the harness depends on: prompts, policies, tool schemas, and retrieval indexes. The refund example above was a versioning failure disguised as a model failure. When you can say which version of each input produced an output, you can answer most “why did it do that” questions in minutes.

Then write down the constraints the agent must never violate, and build a check that confirms those constraints reached the model. Start with the crude version. A check that exists and sometimes misfires is more useful than a perfect design that never ships.

Only after those are in place does it make sense to let the system propose changes, and even then, a human should review the first several accepted edits. You will learn more from the rejected ones.

My take

The model is the visible part of an agent, so it gets most of the attention. The harness decides whether a run survives stale context, bad tools, retries, and failure, and most of those failures are invisible unless you trace them.

I treat an agent’s self-edit like any other software change. It has to beat the old version on fresh tasks, preserve known successes, and be reversible. If an update cannot meet those conditions, it does not belong in production, no matter how good the prompt looks.

The question I keep returning to is not whether the model is smart enough. It is whether the system around it is honest enough about what the model knew when it decided.

Sources

--

--

See more from me