Wednesday night, four draft PRs were sitting on Freebo’s main. Another agent had opened them earlier in the week. Big diffs. I scrolled through and got that specific bad feeling you get when an agent has been writing “just in case” code — new tables, new services, dormant scaffolding for features that didn’t exist yet. I didn’t trust the diff, but I also didn’t want to spend the next four hours reading 10,000 lines of TypeScript by hand.
So I opened a fresh session and gave it a boring prompt:
Review draft PRs 629–632. They address big problems and improvements but I saw the agent adding a lot of code I’m not sure how much they removed. Give me your full report.
Then I told it to spin up one reviewer per PR, in isolated worktrees, and go.
The verdicts
They came back forty minutes later, one at a time, each with a teammate-message and a full report saved to a scratchpad. The tone was not what I expected. These were not four helpful summaries. They were four grades, and two of them were failing.
review-631 on the “minimum-outing domain” PR:
This PR contains exactly one working user-facing change: the flex-duration availability fix. Everything else is a dormant parallel “outings” domain — 6 tables, 16 SQL functions, 6 routes, a sidebar page, a payment-review dialog — with zero producers: nothing in the API ever creates an outing, a membership, or an obligation, so the operator page is permanently empty and the confirm/operation endpoints are hard-wired to 503. Recommendation: SPLIT.
review-630 on the financial-integrity rewrite:
DO NOT MERGE. The design direction is right and about a third of the diff is salvageable. But as committed it is a kill switch for every existing tenant.
— review-630, on PR #630
A Claude agent refused to rubber-stamp another Claude agent’s PR. It used the phrase “kill switch.” It listed the two live production tenants by which balance reads would fail, which cancels would fail, which refund paths would fail. It called out that thirty failing unit tests were not “stale mocks” — fourteen of them were real behavior regressions the new code intentionally violated.
review-629 on the SMS-and-notifications PR was more nuanced: yes the durable queue is real, yes the worker exists, but the only path that consumes it is gated behind NOTIFICATION_DISPATCH_ENABLED=true, and with the flag off trigger() upserts a pending row and returns — meaning every confirmation, cancellation, refund, and reminder email silently drops on the floor.
review-632 on the checkout-flow refactor was the only clean-ish one. Real work, real tests, a couple of items to tighten.
That’s a much more useful surface than “LGTM.”
What actually changed in my head
I had assumed the value of a reviewer agent was speed — “read the diff faster than I can.” That is not the interesting part. The interesting part is that a reviewer agent has no ego in the code it is reading. It did not write the SQL migration. It has no sunk cost. It has one job — find the ways this is wrong — and it will happily tell you the emperor is naked.
An agent that just wrote 10,000 lines of code will always find a way to declare victory. It has to. It’s the same reason a junior engineer’s self-review reads like a press release.
Two agents in adversarial roles beat one agent in an optimistic one. Every time.
Then I sent four more agents in to fix them
Once I had the reports, I turned around and did the thing the reports made possible: a /goal with a session-scoped Stop hook that would not let me stop until the drafts were finished. The hook body was short:
spin up subagents to finish these draft prs to main up
(just make sure current versions of each pr are committed
so we can always go back).
Four finish-629, finish-630, finish-631, finish-632 agents fanned out into worktrees under ~/Documents/freebo-worktrees/ and started closing the gaps the reviewers had opened. Each had its own scratchpad, its own set of “must-fix” items lifted directly from its review report.
They also needed a shared resource: one local Supabase database. Four agents cannot run supabase db reset at the same time on the same port. So they coordinated through flock.
The pitfall the agents found on their own
I did not tell them to use flock. They figured out they needed a lock, wrote it in, and then ran into a subtle bug that is on my list of favorite things an agent has ever discovered without being told to look:

finish-629 sent a note to the other three agents through the teammate channel, unprompted:
flock ... -ckeeps the lock as long as ANY child holds the fd — servers started for parity snapshots inherited it, so my first session held the lock ~10 min after its script ended (20:45→20:55) until I killed the servers. Fix isflock -o(close fd before running the command) andpkill -fon exact patterns (kill -- -$!on the npx wrapper does not reach the re-parented node child).
That’s a diagnosis I would have taken an hour to reach. It got there in ten minutes, told its three teammates about it, and saved a memory note (reference_flock_shared_db_lock_pitfall) so no future session in the same repo will trip on it again.
The timeline that made it work
- T+0Four review agents dispatchedOne per PR, isolated worktrees, static review only.
- T+40mVerdicts landTwo SPLIT/DO-NOT-MERGE. One flag-gated dead path. One clean-ish.
- T+45m/goal Stop hook armedSession can't stop until the drafts are finished and committed.
- T+50mFour finisher agents dispatchedEach pulls its must-fix list straight out of its review report.
- T+2hflock fd-inheritance bug found and shared across teammatesOne agent hits it, the other three get the writeup and switch to flock -o.
- T+4hAll four PRs green886 unit tests pass, integration suite green, merge preview clean.
What I’ll keep doing
The two-agent adversarial pattern is the whole trick. Everything else — worktree isolation, /goal hooks, teammate messages, the DB lock protocol — is scaffolding. The pattern is: one agent writes, another agent grades, and the grader has no incentive to be nice.
If your review agent never says “do not merge,” it isn’t a reviewer. It’s a cheerleader with a diff viewer.
Ship the reviewer first. Then let the writer catch up.