Skip to content
Harness Engineering2026-10-078 min read

A Coding Agent That Writes Its Own Tests Is Grading Itself

When a coding agent writes both the change and the tests, a passing suite measures the agent's idea of done. Treat it as an eval problem: tests written from the spec, exit codes, rendered measurements and a named human verdict.

Key takeaways

  • A coding agent that writes its own tests is grading its own work: a passing suite is evidence about the assertions it chose, not about the spec.
  • Tests written from the spec by someone else work like a held-out set. In the worked example, both of the agent's tests pass and one spec test fails.
  • For interface changes, width and height establish size; overlap needs the rectangles of neighboring elements compared, and appearance needs a person.
  • A model-written report is weak evidence: the book measured 2 complete and honest reports out of 180, so gate on exit codes, test output and measurements instead.
  • One passing run is one trial, consistent with a true pass rate as low as about 21%; trust in a coding agent is earned across many independently checked runs.

Rod Rivera

Author

A Coding Agent That Writes Its Own Tests Is Grading Itself

Rod's note: read with a pencil; the margins are for you.

The grader is the system under test

A coding agent finishes a change and reports: all tests pass. It wrote the change, chose what to test, ran the tests and wrote the report. Each step is useful. None of them is independent of the agent's own idea of what "done" means.

In evaluation terms, that is a model grading its own output. You would not accept a benchmark score where the model wrote the test cases after seeing its answers, and a passing suite the agent wrote for its own change has the same problem. Public coding benchmarks avoid it by design: SWE-bench grades a patch with tests taken from the real fix, which the model does not see. Your own workflow needs the same separation, at a smaller scale.

A worked example: the agent's tests and the spec's tests

The spec says: truncate(text, limit) returns text no longer than limit characters, and marks a cut with .... The agent writes a fix and two tests. Someone else writes two tests from the spec without reading the fix.

python
def truncate(text: str, limit: int) -> str:
    """The agent's change: cut text to at most `limit` characters, ending in '...'."""
    if len(text) <= limit:
        return text
    return text[:limit] + "..."


# Tests the agent wrote alongside its change.
agent_tests = {
    "short text is unchanged": lambda: truncate("hello", 10) == "hello",
    "long text keeps its start": lambda: truncate("hello world", 5).startswith("hello"),
}
# Tests written from the spec by someone who did not write the change.
spec_tests = {
    "never longer than the limit": lambda: len(truncate("hello world", 5)) <= 5,
    "marks a cut with '...'": lambda: truncate("hello world", 5).endswith("..."),
}
for suite, tests in [("agent", agent_tests), ("spec", spec_tests)]:
    for name, check in tests.items():
        print(f"{suite:5}  {name:28}  {'pass' if check() else 'FAIL'}")

Run on Python 3.12, it prints:

text
agent  short text is unchanged       pass
agent  long text keeps its start     pass
spec   never longer than the limit   FAIL
spec   marks a cut with '...'        pass

The agent's docstring even states the requirement its code breaks: "hello world" with a limit of 5 comes back as "hello...", eight characters. Its tests checked what it thought to check, and the length rule was not among them. The spec tests work like a held-out set: written before, and apart from, the thing they grade.

Why passing tests are weak evidence

Three separate facts explain the gap.

The tests measure what someone thought to assert. A suite passes for any behavior its assertions do not encode. When the same agent writes the change and the assertions, the blind spots line up. Test gaming, measured on small models shows what happens when the agent may also edit the tests.

A model-written report is not a record. The book's Chapter 20 asked small local models to write an end-of-day report from structured records, 180 times. Only 2 of the 180 reports were complete and honest, and one of those two invented an event that had not happened. Those were small local models on a different task, not a measurement of any coding agent. The lesson transfers anyway: rely on records, such as the diff, the test output and the measurements, not on the agent's summary.

One pass is one trial. Chapter 20 applies the Wilson interval to a single passing run: one success out of one is consistent with a true pass rate as low as about 21%. A coding agent earns trust across many runs, each checked independently.

In automation, gate on evidence the agent cannot write for itself: claude -p exits with code 0 on success and non-zero on failure, so a CI job can gate on the exit status and on your own test command rather than on anything the agent says.

Prithvi Rajasekaran's engineering note on harness design at Anthropic describes long-running application harnesses that separate planning, generation and evaluation (Anthropic, "Harness design for long-running applications," https://www.anthropic.com/engineering/harness-design-long-running-apps). The reported comparisons are specific to that setup, not a measurement of this site. The shape applies: an evaluation step is worth most when it can see what the generator cannot check for itself.

Where the agent's checks stop and independent review starts. Agent writes change; Agent's own tests pass; Spec tests and measurements the agent did not write; Human reviews where judgment is needed; Accept or send back
Where the agent's checks stop and independent review starts

The same gap in a rendered page

Interface changes add a layer that unit tests rarely reach: what the page looks like after it renders. Some of that can become an assertion. Width and height come first, but they are not enough:

python
from dataclasses import dataclass


@dataclass
class Box:
    x: float
    y: float
    width: float
    height: float


def size_ok(badge: Box, expected: int = 40) -> bool:
    return badge.width == expected and badge.height == expected


def overlaps(a: Box, b: Box, gap: float = 0.0) -> bool:
    """True when the rectangles overlap or sit closer than gap pixels."""
    return not (a.x + a.width + gap <= b.x or b.x + b.width + gap <= a.x
                or a.y + a.height + gap <= b.y or b.y + b.height + gap <= a.y)


# Authored rectangles for one card at 320 px; not a browser measurement.
badge = Box(x=264, y=16, width=40, height=40)
title_ok = Box(x=24, y=16, width=228, height=48)
title_long = Box(x=24, y=16, width=256, height=72)

assert size_ok(badge)
assert not size_ok(Box(x=264, y=16, width=39, height=40))
assert not overlaps(badge, title_ok, gap=8)
assert overlaps(badge, title_long, gap=8)
print("Size check passes for both titles; only the overlap check catches the long title")

The size check passes in both layouts. Only the overlap check, which compares the badge with its neighbor, catches the long title running under it. In a real test the rectangles come from the browser: tools such as Playwright return an element's bounding box after the page renders, at a viewport width you set. Measure each element and its neighbors, in every state, at the widths where the layout changes.

Assertions still stop short of acceptance. A gap of 8 pixels passes the check; whether 8 pixels reads as comfortable is a judgment, and the threshold itself was a choice someone made.

Case study: the syllabus badges on this site

Lesson-number badges on this site's course cards were squeezed by long titles. An agent moved each badge into a fixed grid column as a 40-pixel circle, then reported 113 test files and 696 tests passing, plus 42 badges measured by headless Chrome at 320, 375 and 1280 pixels, each 40 by 40 with clearance flagged as passing. The measurements came from a local development server, not production.

Then it marked the work held, not done, and asked the operator whether the circles and spacing looked right on phone and desktop, including card 10. Its record says the instrument proves dimensions and behavior and does not grant aesthetic acceptance. That is the behavior to copy: strong mechanical evidence, plus a named list of what the evidence did not cover.

Badge size becomes recorded geometry evidence, badge and title rectangles become overlap and clearance assertions, and keyboard and toggle actions become interaction evidence, all feeding a rendered human review.. Badge width and height; Recorded geometry evidence; Rendered human review; Badge and title rectangles; Overlap and clearance assertions; Keyboard and toggle actions; Interaction evidence
Badge size becomes recorded geometry evidence, badge and title rectangles become overlap and clearance assertions, and keyboard and toggle actions become interaction evidence, all feeding a rendered human review.
A screenshot file is not a review

A check that saves screenshots proves the screenshots exist. It does not show that anyone looked at them. Record the human verdict as its own field; an empty field means the change is not accepted.

A verification protocol for agent changes

Write tests from the spec, apart from the change

Have a person or a separate process write tests from the requirement before reading the agent's code. Run them next to the agent's own tests and report the two suites separately.

Gate on evidence the agent cannot write

Run your own test command in CI and gate on its exit status. Treat the agent's summary as a pointer to evidence, never as evidence.

Test the risky inputs and states

Use the boundary values, the longest real input, the highest item number, and for interfaces the expanded and collapsed states, keyboard focus and larger text settings.

Record the environment

Say whether results came from a development server or a deployed build, and pin the commit they describe.

Record the human verdict separately

Keep the person's answer next to the measurements, not merged into the same pass field. Until it is recorded, the change is held.

Quick check — A coding agent fixes a function, adds three tests of its own, and reports that all of them pass. What does this establish?

Check the decision

Why this matters in a lab

Coding agents write a growing share of research and product code, and teams evaluate them the way the code gets graded: by tests. When the agent writes both the change and the assertions, a pass tells you less than it seems. A team will expect you to know where that gap is and to close it with evidence the agent cannot produce for itself: held-out tests, exit codes, rendered measurements and a named human verdict where judgment is required. It is the same skill as building an eval that a model cannot game, which is central to AI agents and applied AI engineer roles.

An interviewer might ask, "Your coding agent says all tests pass. What do you check before merging?" A strong answer asks who wrote the assertions, which inputs and states they cover, where the measurements came from, and who has looked at the result.

What would prove this wrong

If the agent's tests are written independently, by a person or a separate process working from the spec, they are stronger evidence than tests it wrote for its own change. If a browser check compares every relevant pair of rectangles in every state and width that matters, the overlap part of the gap closes, and what remains is whether the chosen clearance matches what readers find comfortable. The practical falsifier for the badge case is simple: load the page at 320 pixels with card 10 expanded and see whether the badge crowds the title.

Read Chapter 20: reliability and honest reports

See why a model-written report is weak evidence, how one passing run bounds a pass rate, and how to decide what is ready to operate.

Learn the mechanism. Test the claim.

Join the Prof Rod newsletter for one educational lesson a week, with worked examples attached. It is free to register for and separate from the Zero Employee community.