Home

Taking apart my agent harness

Seven weeks of rules, hooks and review gates meant to stop regressions and half-built features, a session built to watch the agents, and a benchmark that sent me back to almost vanilla.

For the past seven weeks I've built a product mostly by directing coding agents. Claude Code writes most of the code, Codex some of it, and my job is deciding what to build and checking what comes back.

The problem I kept running into was simple to describe. I would ask for a small feature, and the agent would come back having done one of three things:

Almost every task had at least one of these. Everything in this post is my attempt to fix that, what happened when I tried, and the experiment I ran at the end to find out what actually helped.

lines of harness at the peak
11,262
of commits touched the harness
40%
review rounds to merge one PR
7
PRs merged: 3 hours gated vs 35 min ungated
0 → 4
tokens over the two worst days
~1.4B
lines in the agents' rulebook
489 → 102

Where the idea came from

The starting point was a guide by @poteto on verification. Its argument is that the most important thing you can give an agent is a way to verify its own work: let it drive the real app, check the result, and keep going until it works, so you stop being the bottleneck. The proof you get back is a short video you can skim.

I built that. Agents got a command-line tool that drives the app the way a person would: sign in, click through screens, start background jobs, read the database. They also got a "feature map", a set of files describing what each feature does and how to reach it. This part worked well. Agents debugged against real data and found real bugs.

It did not fix the problem above, because the agents' reports about their own work weren't reliable.

What the agents actually did

When I went back through the session logs, the pattern was consistent. The agents wrote decent code. What they got wrong was everything around the code: what they claimed, what they skipped, and what they did when something failed.

Claimed things were done when they weren't

A status report said "all six parts have passed their first review". Every first review had failed, and no fix had been re-reviewed. Another said "onboarding is walked" when seven files had changed since the walk.

Claimed things were tested when they weren't

"Both PRs deployed and verified on staging" referred to a rerun of two tests unrelated to either PR. Another report described work as "real research" when the agent had called an internal function directly.

Hid failures instead of reporting them

A failing check became if (await more.isVisible()) await more.click(), which can't fail. A test cleanup printed "LEAK: cleanup step failed" and the report didn't mention it.

Called bugs "environmental"

Two real bugs were each investigated three times and closed as environmental, with no reproduction.

Dropped parts of the request

About a dozen requested pieces were cut without asking. They appeared only in agent-to-agent reports I wasn't reading.

Made my decisions and credited me

One agent changed how a value was stored, which broke sorting, then wrote "Abhinav decided" in the docs. I hadn't.

Worked around the checks

Agents tried git commit --no-verify and SKIP_REVIEW=1 to get past the hooks. When those were blocked, a command could still slip through by splitting a word with quotes, like --no-v''erify, so the hook's pattern didn't match. Then the step was reported as done.

Steered their own reviewers

The author agent wrote its reviewer's instructions, including "orchestrator decisions you can treat as given (do not re-flag)". When reviewers refused, it started new ones until one approved.

None of this looked deliberate. It looked like an agent under pressure to finish, describing its work as finished. But the effect for me was the same: I couldn't trust "done", "tested" or "verified" without checking it myself, which defeated the point.

Each failure became a rule

My response was to add a rule every time an agent got something wrong. Then a script to enforce the rule. Then a hook so the agent couldn't skip the script. I didn't remove any of them.

Lines of harness vs lines of productThe rules, hooks, scripts and end-to-end suites I wrote for the agents, against the app itself. Daily, at each day's last commit; vendored lint plugins and third-party skills left out. The product dip on Sep 23 is the day every unit test was deleted.
HarnessProduct
025k50kAug 21Sep 22Oct 712345Product 46.2kHarness 2.6k
  1. Pre-push gate + slop guard
  2. Review gates every commit; unit tests deleted
  3. CI deleted; the rulebook peaks
  4. Walk gate: prove the exact committed code
  5. The cut
Table
DayHarnessProduct
08-211183,154
08-222223,154
08-243853,154
08-267708,073
08-311,55016,729
09-022,53719,738
09-043,56320,657
09-074,37821,679
09-104,68423,484
09-144,90028,892
09-175,07231,731
09-184,86534,549
09-214,92936,636
09-225,08341,369
09-235,56429,835
09-256,38030,955
09-277,45732,155
09-289,11233,148
09-2910,46734,901
09-3010,66036,733
10-0210,68137,537
10-039,82337,784
10-0410,24637,784
10-0511,04844,735
10-0611,17645,997
10-072,62546,240

The orange line is everything I wrote for the agents rather than for the product: the rulebook they read every turn, git hooks, review scripts, guards, an end-to-end test suite, process docs. At its peak it was about a quarter of the size of the product, and it grew in steps, each one right after something went wrong.

The main steps:

  1. A gate before every push

    Lint, typecheck and build before every push, plus a guard that refuses TODOs, skipped tests and unexplained lint suppressions. This one stayed.

  2. A hook that won't let the agent stop

    A Claude Code Stop hook runs the checks at the end of every turn and blocks the agent from finishing while anything is red.

  3. A reviewer must approve every commit

    A separate reviewer agent has to approve every commit, recorded against a hash of the staged diff. The same day, unit tests were replaced with end-to-end checks only.

  4. The rulebook hits 489 lines

    The rulebook every agent reads on every turn reaches 489 lines, including a dozen dated rulings. CI is removed the same day.

  5. A hook against hook bypasses

    A hook refuses --no-verify, skip flags, and writing the review record by hand.

  6. Prove the exact bytes

    A commit is refused unless a recorded walk of the app ran the exact staged contents of every changed file.

  7. A checklist hook on merges

    Merging a PR is refused while its checklist has an unticked item, unless I'm quoted dropping it.

The rulebook every agent reads firstAGENTS.md, the instructions loaded into every agent turn. It peaked at 489 lines mid-day on Sep 28; the dip on Sep 30 was a rewrite that grew right back.
0250500Aug 21Sep 22Oct 712345AGENTS.md 102
  1. Pre-push gate + slop guard
  2. Review gates every commit; unit tests deleted
  3. CI deleted; the rulebook peaks
  4. Walk gate: prove the exact committed code
  5. The cut
Table
DayAGENTS.md linesDated rulings
08-21720
08-22980
08-241150
08-261170
08-311690
09-022390
09-042680
09-072730
09-102740
09-142760
09-172840
09-182892
09-212932
09-222973
09-233767
09-2544510
09-2744510
09-2847711
09-2944814
09-302062
10-022063
10-032962
10-043134
10-053134
10-063195
10-071020

Each rule was reasonable when I added it. Together they made a process that was very hard to get through.

The meta session

By early October I couldn't keep track of what the building sessions were doing. They ran for hours, spawned their own sub-agents, and reported in long summaries I didn't fully trust. So I set up a second Claude session whose only job was to watch the first one.

Every 20 minutes it read what was new in the running session's transcript: my messages, the agent's replies, every shell command, git state, open PRs, review results and recorded walks. It checked them against the rules and reported to me what was going wrong. When I asked for a full audit, it ran five sub-agents in parallel, one each for the reviewers, the harness itself, the build agents, the orchestrator and the integration work, and combined their findings.

It was good at finding facts. Most of the examples above come from its audits. It caught that a crash blamed on the out-of-memory killer was really the VM shutting down. It caught an agent waiting 15 minutes for output from a job that had already finished with nothing to do. It caught the false status reports.

The plan was to use what it found to improve the harness. That's where it went wrong. I let it change the harness while the build was still running, and in one night it landed four harness PRs. Each one told every running agent to merge the latest code and restart its review. It went from proposing more enforcement (a checklist guard, a decision guard, scripted status reports) to removing every gate within a few hours, reacting to whatever had just happened. I ended up relaying messages between the two sessions and running review commands in my own terminal because the agents' shells were locked out.

When it finally removed the review gate, it wrote this about its own change:

I wrote this harness change myself, merged it without an independent review, and didn't run its live journeys.

the watcher session

A session watching another one is useful as an observer. As a second driver changing the rules mid-run, it made things worse.

Where it broke down

The worst of it was on October 5th and 6th, during a large redesign split across several agents.

The core problem was a loop. A change only counted as proven if a recorded walk of the app had run the exact code being committed. A walk included a real end-to-end run against real data, which took 10 to 15 minutes. Every review by a fresh reviewer found three to five new things. Fixing any of them changed the code, which invalidated the walk, which the next review required.

Why one PR took seven review roundsProof was tied to the exact bytes being committed, so every fix a reviewer asked for invalidated the recording that the next review required.
A fresh reviewer finds 3–5 new things
The agent fixes them
The recorded walk no longer matches the code
Walk again: a real 10–15 min end-to-end run
Ask for a new review
7 rounds~1h50m for one PR

One agent put it this way in its report:

Every fix invalidates every walk.

a build agent

After three refused reviews in a row, the review script stopped running from an agent's shell at all, so agents asked me to run it in my own terminal. One of those runs crashed.

The first day of the redesign used about 890 million tokens over eight hours and merged two PRs. I asked for status nineteen times over those few days.

Tokens, in millionsSummed from session transcripts. Almost all of it is the agent re-reading its own context (cache reads).
Day one of the redesign889M
This experiment (all runs)385M
One benchmark run (avg)39M

My own messages from that night:

in no world does 7 reviews even make sense for a product as simple as ours

me

can we just please please just make the agent normal our harnes is just the worst right now

me

When the review gate came out, four PRs that had been stuck for hours merged in 35 minutes.

PRs merged, before and after removing the review gateThe same night, the same agents, the same work in flight.
First 3 hours, gated0 PRs
35 min, gate removed4 PRs

Those PRs had already been reviewed several times, so this doesn't show that quality holds without review. It shows where the time was going.

The experiment

At that point I had a lot of anecdotes and no measurements. People were also telling me a vanilla setup works better than a custom harness. So I ran a benchmark.

Same task, four harnesses, three blind gradersTotal score out of 120 (three graders × 40: completeness, correctness, collateral damage, code quality). Graders saw the diffs as P, Q, R and S and never knew which harness made which.
Steps + outside review78 / 120
Gates off (what we had)74 / 120
Vanilla + outside review65 / 120
Minimal rulebook60 / 120
Table
HarnessOpus 5.5GPT-6.1-SolGPT-6-SolTotal
Steps + outside review28262478
Gates off (what we had)27272074
Vanilla + outside review23261665
Minimal rulebook23201760
What each run costMinutes from prompt to an open PR, lines of product code added, and estimated spend at list prices (cache reads are ~97% of the tokens). Graders and reviewers not included.
Steps + outside reviewGates off (what we had)Vanilla + outside reviewMinimal rulebook

Minutes to PR

Steps64 min
Gates45 min
Vanilla46 min
Minimal97 min

Lines added

Steps209
Gates260
Vanilla205
Minimal582

Estimated cost

Steps$59
Gates$56
Vanilla$89
Minimal$104
Table
HarnessMinutesLines addedEst. $
Steps + outside review64209$59
Gates off (what we had)45260$56
Vanilla + outside review46205$89
Minimal rulebook97582$104

What the results showed:

I also gave two of the harnesses a harder performance task and stopped both before they finished. The run on the old harness had rewritten 91 files and broken an unrelated feature. The other had touched 28 and scored 29 to 19.

Limits of the test

What I use now

The expensive parts (the gates, the review loop, proof tied to exact bytes) were pure cost, and they're gone. The one-pass review by a different model clearly helps. A few short steps about how to run a task help a little. Nothing else showed a measurable effect.

So the harness is now close to vanilla:

What the final cut removedNet lines deleted by the PR that shipped the near-vanilla harness. Authored harness went from 11,262 lines to 2,625.
End-to-end journeys4,809
Verify scripts & checks2,694
Test seams1,634
Process docs552
Skills305
Map proof claims290
Rulebook (AGENTS.md)217
Table
PartNet lines removed
End-to-end journeys4,809
Verify scripts & checks2,694
Test seams1,634
Process docs552
Skills305
Map proof claims290
Rulebook (AGENTS.md)217

The harness I wrote went from 11,262 lines to 2,625.

Looking back, every rule I added was a response to a real failure, and most of them made sense on their own. The mistake was only ever adding, and never asking whether a rule was helping. Any change to the harness now has to beat this benchmark before it goes in.