DEV Community

Cover image for What Is Harness Engineering? How to Build AI Agents That Catch Their Own Mistakes
Harry Floyd
Harry Floyd

Posted on Originally published at harryfloyd.substack.com

What Is Harness Engineering? How to Build AI Agents That Catch Their Own Mistakes

The model does the reasoning. The harness is much of what makes it reliable, and there is a test for every part of it.

A dark glass sphere labelled model, held in a gold gimbal cage, with four tags on the cage: AGENTS.md, contract check, deny rule and held-out test.

Harness engineering is designing everything around an AI model, such as its instructions, context, tools, checks and sign-off points, so the agent gets work right first time and catches its own mistakes. In short, Agent = Model + Harness: the model does the reasoning, and the harness is much of what makes it reliable.

One failure runs through this guide. You ask a coding agent to tidy up a data model. It renames a field, the code compiles, the checks in front of it pass, and it reports success. Two other services that read that field quietly break. Thoughtworks engineers, who use this example, call it "locally correct, but systemically destructive," and they say the gap between model intelligence and reliable delivery "is not primarily a model-quality issue." What the agent lacked, in their words, was "system-level visibility, architectural guardrails and automated feedback mechanisms."

This guide applies what OpenAI, Anthropic, Google, Thoughtworks and independent engineers have learned to that one failure, a layer at a time, and shows how to test each fix. If you have ten minutes, read section 3 and do the box in step 2 of section 4.

1. What is harness engineering in simple terms?

The short version is a formula: Agent = Model + Harness. Birgitta Böckeler, writing on Martin Fowler's site, splits the harness in two. Part of it comes built into the tool, such as the system prompt and the way it searches your files. The rest is an outer harness that you build for your own work and your own codebase.

She also gives the most useful working model. Guides steer the agent before it acts: instructions, skills, examples. Sensors check its work afterwards: tests, linters, type checks, review. Guides alone give you an agent "that encodes rules but never finds out whether they worked"; sensors alone, one "that keeps repeating the same mistakes." The renamed field got past both. Nothing told the agent the field was shared, and nothing checked the services that read it.

Guides before, sensors after: the AGENTS.md line steers the agent, the contract check catches the rename, and a repeat updates the guide or adds a check.

Mitchell Hashimoto, who started calling it harness engineering in a February post, states the aim: "anytime you find an agent makes a mistake, you take the time to engineer a solution such that the agent never makes that mistake again." Read "never" as the aim. Agents are not deterministic, so some failures come back by chance, and some fixes cause new problems. That is what section 3 is about.

2. Harness engineering vs context engineering

The short answer: context engineering decides what the model sees; harness engineering is the wider system that also prevents, checks and fixes. Anthropic calls context engineering "the natural progression of prompt engineering." HumanLayer draws the line the other way, calling harness engineering "the subset of context engineering" done through the agent's own configuration points.

Either way, the renamed field failed on both counts: the services that read the field were never in the agent's context, and no check ran against them. Treat SmartScope's comparison as a first question to ask: a single wrong answer suggests context, and quality that slips with repeated use suggests the harness, though one wrong answer can also come from the model, a tool or a missing check.

3. Every addition is a hypothesis

The reflex after a failure is to add something: another rule, another tool, another check. The best evidence so far says to test that reflex.

Two controlled studies have tested the instruction files at the centre of most harnesses. ETH Zurich researchers, working on Python repositories, found that context files, whether written by a model or by the repo's own developers, did "not generally improve task success rates," raised cost by over 20%, and did no better when shorter. The authors conclude the files are useful for "specifying non-standard coding practices."

A smaller study, by Lulla and colleagues, ran agents with and without an AGENTS.md on 124 pull requests and found the file cut median runtime by 28.6% and output tokens by 16.6%, with completion about the same. The two teams measured cost differently (ETH counted inference cost; Lulla's counted runtime and output tokens), so their efficiency results are not directly comparable.

Neither found a reliable gain in task success: developer-written files did slightly better in the ETH runs (2.4 percentage points on average, not statistically significant), and clearly better than generated ones.

Anthropic's harness-design team gives the reason this matters beyond instruction files. Every part of a harness "encodes an assumption about what the model can't do on its own," and those assumptions "can quickly go stale as models improve." Their working rule comes from an earlier post: "find the simplest solution possible, and only increase complexity when needed." Thoughtworks puts it in three words: "Earn every rule."

Treat every addition to your harness as a hypothesis. It costs tokens, time and attention, and it assumes something about the model that may stop being true. Make the smallest change that would have caught the failure, run the same kind of task with and without it, and keep it only if the difference pays for the cost. Section 7 shows how.

4. Fixing the renamed field, one layer at a time

Here is the failure again, fixed in layers: see what happened, make it less likely, catch it when it happens, and contain the damage. For a public contract, the check is the layer that has to hold; the instruction only makes the failure rarer.

Step 1: See what the agent saw

Before adding anything, look at what entered the agent's context on the failed run. Sourcegraph's test is to log it, "then check whether the answer was in there." In the rename it was not: the services that read the field live elsewhere, and the agent never opened them. OpenAI's line covers it: "anything it can't access in-context while running effectively doesn't exist." If the agent never encountered the dependency, find out why. It may need a clearer instruction to search for it, access to the right repository, or a check that catches what it missed.

Step 2: Make it less likely with one guide

The cheapest change is a line in the agent's standing instructions file. AGENTS.md is the shared format, read by many coding agents; Claude Code reads CLAUDE.md first and, from v2.1.277, falls back to AGENTS.md when a project has no CLAUDE.md. For the rename, the line might read: Field names in orders/schema are a public contract; before renaming one, search the other services for it and list what reads it. That is the kind of non-standard rule the ETH study found files are good for.

Keep the rest of the file lean. OpenAI's team tried "one big AGENTS.md" and it failed; for one thing, a giant file "crowds out the task." Theirs is now "roughly 100 lines," a "table of contents" pointing to a docs/ folder. HumanLayer keeps theirs "under 60 lines." In a large repo, put a nested AGENTS.md in each project; agents read the nearest file, "so the closest one takes precedence," as agents.md puts it.

Software Mansion's guide cites Anthropic's Claude Code docs on what goes in and what stays out; here is a shortened version:

Keep what the agent can't find for itself (commands, your non-default rules, how to test, decisions and gotchas, pointers to docs) and cut what it can.

Do this first (10 minutes): Open your instructions file. Delete anything the agent could work out for itself, move long sections into separate docs with one-line pointers, and add the one rule your agent most recently broke.

Step 3: Catch it with one sensor

An instruction can be ignored; a check runs every time. For the rename, that is a contract check: each consuming service lists the fields it reads, in a file the agent cannot edit, and the check fails when the schema no longer has one of them. A test that searches the code for the schema's own field names looks similar and misses this case, because it never looks for the field the agent just deleted. A first version can be this small (TypeScript, with Vitest and a Zod schema):

// contract.test.ts: runs in CI.
// This file, contracts/ and the CI job sit outside the agent's write access.
import { test, expect } from "vitest";
import { OrderSchema } from "../orders/schema";
import consumers from "../contracts/consumers.json";
// { "billing": ["customer_id", "total"], "shipping": ["customer_id", "address"] }

test("every field a consumer reads still exists", () => {
  const fields = Object.keys(OrderSchema.shape);
  const broken = Object.entries(consumers).flatMap(([service, needed]) =>
    needed.filter((f) => !fields.includes(f)).map((f) => `${service} reads ${f}`));
  expect(broken, `${broken.join("; ")}: keep the old name or update the consumer`).toEqual([]);
});
Enter fullscreen mode Exit fullscreen mode

Run against the renamed schema (Vitest 5 with Zod), it fails like this, and it passes again once the old name is back:

FAIL  tests/contract.test.ts > every field a consumer reads still exists
AssertionError: billing reads customer_id; shipping reads customer_id: keep the old name or update the consumer: expected [ 'billing reads customer_id', …(1) ] to deeply equal []
Enter fullscreen mode Exit fullscreen mode

It catches a removed top-level field and nothing subtler; a production check should also compare types, required fields and the consumers' real interfaces. And it protects you only while the agent being checked cannot change it, so keep the test, the consumer list and the CI step that runs them out of its reach.

Run it automatically after every edit, and follow four rules that make sensors work.

  1. Silent success, verbose failure. That is Thoughtworks' name for it. HumanLayer learned why the hard way: when they ran the full suite after every change, "4,000 lines of passing tests would flood the context window," and the agent lost track of its task.
  2. Write the error for the agent. OpenAI writes custom lint messages that "inject remediation instructions into agent context"; Böckeler calls this "a positive kind of prompt injection." For the rename, the check above says: billing reads customer_id; shipping reads customer_id: keep the old name or update the consumer.
  3. Promote repeated rules into checks. A rule you keep re-adding to your instructions "should be promoted into a mechanical sensor," says Thoughtworks. Software Mansion's split: "Written instructions should cover the cases that require judgment. Hooks should cover the cases that do not."
  4. Protect the checks that decide "done". Agents need to write and change tests as part of the work. What they must not be able to edit is the check that says the work is finished, and an instruction alone will not hold that line. For a lock that does, see Lock the Tests, Then Test the Lock.

The kit: find out whether your sensor works

A sensor can look wired and still do nothing. In our recorded runs, a hook that exited 1 never got its message to the agent, a matcher written as edit|write never fired on an Edit, and a typo in the script path exited 2, the same code a real failure uses.

The kit runs Claude Code on throwaway copies, with your hooks and without them, and shows whether each sensor and stop hook in your project's settings fires, whether a failure reaches the agent, and whether the work ends in a state your check accepts, with the files you protect unchanged. It also ships a starting sensor, stop rule and deny rules.

On 3 November, with Claude Code 2.1.292, that starting set held in all 8 Sonnet 5.5 runs given a breaking change, 4 in the demo and 4 in one of our own repositories, and all 4 matching runs without hooks were left broken. Opus 5.5 also got the sensor's warning and was sent back by the stop rule, but in our repository it kept the rename it had been asked for in both break runs, saying that undoing it would go against the instruction, and left the tests failing. No run ended with the protected files changed. On a later version, 2.1.316, one of two Sonnet runs ended broken after the stop rule sent it back once, which is why the kit records the version of every run.

Download the kit. Unzip it and run python3 kit/selftest.py first; kit/README.md says what each file does, what the runs showed and what they cannot tell you.

Step 4: Contain it where the stakes are high

Some changes should not go through on the agent's say-so. Thoughtworks keeps human confirmation for "irreversible or high-blast-radius choices," such as changes that span several repositories, schema migrations or public API modifications; everything else proceeds with "sensible defaults." A renamed field in a public contract is exactly that kind of change.

There are four ways to stop an action, and they differ in strength. An instruction asks. A hook runs your own script around a tool call and can block it: HumanLayer's denies any command that tries to run database migrations and tells the agent to ask a human. A deny rule blocks matching tool calls; in Claude Code, "Deny rules prevent Claude Code from using the specified tool," and they are checked first, though another tool or command may still reach the same thing. A sandbox limits what any command can reach, such as which folders it can write to.

Hooks carry their own risk: Claude Code's docs warn that they "execute shell commands with your full user permissions." Use the strongest layer the risk calls for, and give each agent the fewest permissions it needs; a review agent with read-only tools, as Thoughtworks notes, simply can't write.

Step 5: Test the fix

Each of those layers is a hypothesis about this failure. Run the rename task, and a few like it, with and without the new line, the check and the stop, and see which one changed the outcome. Section 7 sets out how.

5. Keep the context clean

The rename needed one fact, the list of services that read the field, and none of the rest of the repository. That is the general aim. As tokens increase, Anthropic explains, "the model's ability to accurately recall information from that context decreases," so the goal is "the smallest possible set of high-signal tokens." Its cookbook compares three ways to free up space.

Three ways to free up context: clear old tool output, compact the conversation, or keep notes outside the window.

If you build on the API, start with clearing, which Anthropic calls "one of the safest lightest touch forms of compaction"; the agent can always fetch the result again. When you compact, tell the summary what to keep. Sourcegraph's compaction prompt keeps the task, the files already inspected, decisions and why, and "approaches already tried that failed, and why." Leave that last item out "and the fresh window will cheerfully retry them."

Tools take space too. Anthropic's tool search loads tool definitions only when the agent needs them, about 85% fewer tokens in its example, and Claude Code now defers MCP tool definitions "until Claude needs them."

Send noisy research to a subagent and have it return the answer with its evidence and any open questions. Anthropic says a subagent may use "tens of thousands of tokens" and return "often 1,000-2,000 tokens," and Sourcegraph warns that "a sub-agent that retrieved badly still returns a confident summary."

Every connector and skill also puts someone else's text in the agent's context. Software Mansion describes a published attack that hid instructions inside a skill in invisible Unicode characters. Read every third-party skill or connector before installing it. That lowers the risk without removing it: hidden text can get past a read, and a connector's list of tools can change after you have read it.

Memory needs curating. On Databricks' internal benchmark of enterprise tasks, their own memory system scored 22.3% with no memory, 35.8% with searchable raw session traces, and 66.1% with lessons drawn from those traces (68.4% with both). Their caution: "more history is not necessarily better memory." Give memories an expiry too. Sourcegraph's example is a memory about a system the team changed weeks ago, which "is worse than no memory: the agent holds a confident, wrong prior it will defend."

6. Work that lasts longer than one session

When a job spans several sessions, Anthropic's long-running agents experiment is the most concrete guide. Each new session "begins with no memory of what came before," and a later agent would "look around, see that progress had been made, and declare the job done."

Their fix: a first session writes a feature list of everything "done" means, every item marked failing, in JSON because the model is "less likely to inappropriately change or overwrite JSON files compared to Markdown." Every later session updates a progress file, ends with a commit so bad changes can be rolled back, and starts by reading both and checking the basics still work.

7. How to test a harness change

Use this on any change to your harness, including the contract check from section 4. It needs no special tooling, but it does take a fixed set of tasks and a few dozen agent runs. Treat it as a pilot: it will show a large effect, and it cannot prove a small one.

The four-setup test: your original harness, plus the instruction, plus the check, plus both, all graded by the same acceptance tests.

  1. Pick the task class your agent failed on, and collect ten or so real examples. Keep a few back that you never look at while building the fix; those held-out tasks show whether the fix generalises or only fits the examples.
  2. Grade every setup the same way. Decide what "done" means before you start, with held-out acceptance tests the agent never sees, and use them for all four setups. If the contract check were also the grader, setup C would win by definition.
  3. Run each setup on the same tasks, several times. Google's team recommends running evals in batches, because single runs "can be noisy due to nondeterminism of AI models."
  4. Record six numbers: tasks passed, tasks verified done by a check you trust, human corrections needed, tokens, time and cost. Count cost honestly; in data from Anthropic's research system, agents used about 4 times the tokens of chat, and multi-agent systems about 15 times.
  5. Keep the cheapest setup that holds up. If C does as well as D, the instruction line is not earning its place. If none beats A, the failure may lie elsewhere, or the effect may be too small for a test this size; add tasks before you decide.

That comparison measures outcomes. Google's team separates outcome benchmarks from behavioural evals: checks of one action at a time, such as whether the agent searched the other services before renaming, which show why a score moved where a big benchmark only shows it moving "by a few percentage points." Make each check strict for simple tasks, or use an AI judge where several paths are fine. When you change model, run the whole test again; the assumptions in your harness may have gone stale.

8. Which fix for which failure

Most agent mistakes fall into a few kinds, and each has a cheapest fix worth trying first. This table is our synthesis of the guides above; treat each row as a starting hypothesis.

Which fix to try first for each kind of failure, and what to escalate to if it recurs.

The rename starts in the first and third rows. Once the line is in AGENTS.md, a repeat moves it to the second: the agent knew the rule and broke it, so the next fix is the contract check, and the stop on public API changes is the escalation.

The checklist

This week

☐ Your agent's last mistake has one check that would have caught it, silent when it passes, with the fix in its error.

☐ Your instructions file holds only what the agent can't find for itself, plus the rule it last broke.

☐ Actions you can't undo (migrations, deletes, pushes) are blocked by a deny rule, a sandbox or a hook.

☐ The checks that decide "done" are out of the agent's reach.

Next

☐ Your latest fix has been run against the setup without it, on held-out tasks.

☐ Noisy research goes to a subagent, which returns the answer with its evidence.

☐ Only the tools and connectors you use are switched on, and every third-party one has been read.

☐ Long tasks have a done-list and a progress note.

Once it works

☐ Each failure you've fixed has a small behavioural test.

☐ On a failed run, you check whether the answer was ever in the context.

☐ Memories have an expiry or a review date.

☐ After a model change, every part of the harness is re-tested, and what no longer helps goes.

The whole job

When the agent makes a mistake, find the smallest fix that would have caught it, test it against the setup without it, and keep it only if it earns its place. This week, start with the last mistake your agent made.

Further reading: if you read three of the originals in full, make them Böckeler on guides and sensors, Anthropic on agents that work across sessions and Thoughtworks on the renamed field. Every other source is linked where it is used. They date from June 2025 to October 2026, and every link and quotation was checked on 22 October 2026.

The next guide in this series, AI Agent Permissions, follows one deleted branch through five guards and shows which of them hold.

I'm Harry, and this is The Durability Curve, where I work out where value goes as AI gets cheaper. The aim each time is to leave you with something you can take to your own work: a test to run, or a way of seeing it you did not have before. If you'd like the next one to find you, subscribe.

This guide is also on the site: durabilitycurve.com/blog/what-is-harness-engineering.

Top comments (0)