# How we brief a coding agent to finish a pull request
Most bad agent output starts as a bad brief. "Add a fetch client" gives the agent a task but no finish line, so it stops when the code looks finished.
We are building Hafen, a data platform for AI agents. The roadmap runs to dozens of pull requests, so each one needs a brief that a coding agent can finish alone. This is the format we use.
The brief
Every PR gets one message with the whole task in it:
- Depends on
- PRs that must merge first
- Scope
- What to build
- Out of scope
- What not to build, so the run doesn't sprawl
- Done when
- The finish line, as checks someone can run
- Stop and ask if
- Decisions the agent must not make alone
- Test
- Only when the PR protects a named invariant
We write the brief just before the PR starts, so it reflects what has already merged. We link to the docs instead of pasting them, because the agent can read the repo.
"Done when" is the part that matters
If a reviewer can't run it or measure it, it isn't a finish line. "The crawler is polite" can't be checked. This can:
hafen-fetch bench urls.txt over a 10k-URL fixture list
reports success rate, p50/p95 latency and cost by tier.The important detail is where the evidence comes from. Our politeness check doesn't ask the fetch client how it behaved. It reads the access log of the host being fetched.
That check found a real bug. The rate limiter from the original plan spaced out when requests started. A first request slowed by cold DNS therefore arrived late, and the next one arrived close behind it. The fixture host logged 393 gaps of 0.963 s against a limit of 1 request per second. You only see that from the receiving side.
393
gaps under the limit
0.963 s
logged gap
1.000 s
closest gap after the fix
We replaced the limiter so the next request starts a full period after the previous one ends. After that, the log showed no two requests to a host closer than 1.000 s.
Name the stops
An agent that stops too often wastes your day. An agent that never stops can spend your money. So we list the stops and leave everything else to the agent:
- Money above a set budget
- Creating accounts, keys or production secrets
- Infrastructure outside the repo, such as DNS
- Licence or terms surprises
- Design choices the docs don't settle
- Anything destructive
- Publishing anything public
"Stop if you're unsure" doesn't work, because it gives the agent no way to tell a real decision from ordinary uncertainty.
End every run with three headings
Each run finishes with the same report:
- Blocked on me
- What needs us. We read this first
- Changed
- What the run changed
- Found
- Anything discovered outside its scope
The agent must also mark anything it couldn't confirm and say where it looked.
That last rule protects the reviewer. A confident summary of unverified work is worse than a short list of what still needs checking.
Don't test what you only imagined
Agents happily write tests that restate the code. Our testing policy says a test must name the failure it would catch, and a mock isn't evidence when it only returns what the test told it to return.
We also check the tests themselves. In the fetch PR, removing the loopback range or the per-host wait made the matching test fail. A test that still passes after you remove the feature protects nothing.
Review with a different model
The reviewer runs on a different model from the one that wrote the PR, and it gets one instruction: list only the findings that would block the merge, with the file, the line and how to show it fails. Without that limit, reviews fill up with style notes and the real problems get lost.
What this doesn't solve
We agreed the larger version of this on 27 October: one conductor thread runs up to five PR threads and checks them every two hours. It's still a new process, so treat that part as a design and not as a result.
The brief is the part we've actually been running. Writing a finish line someone else can check is also the same skill you need when an agent goes into production for a customer.