How I use sub-agents: separation of powers for writing Go
One agent that writes the code, writes the tests, and reviews its own work has a problem you already know from humans: it grades its own homework. When a test it wrote fails against code it wrote, the cheapest way out is to edit the test, and a keen agent will take it. So I do not run one agent. I keep a handful of narrow ones, and I draw hard lines between what each of them is allowed to touch.
The roster
Each agent does one job and is told, in its own definition, what it may not touch.
- go-writer is the only agent allowed to edit production code. It writes the domain interfaces, the service logic, the SQL, the handlers, and the wiring. It is forbidden from editing any
*_test.gofile or the integration suite, from hand-editing generated code, and from touching infrastructure. - go-test-writer owns the unit tests. It writes them and, when it finds an uncovered branch or a suspected bug, it hands that back to go-writer as a defect to fix. It never edits production code.
- go-bdd-writer owns the integration suite, the Gherkin feature files and the steps behind them.
- go-reviewer reads and reports. It reviews a change for correctness, for whether it is idiomatic Go, and for whether it fits the codebase, and it writes its findings down. It cannot edit a single line.
- architect decides structure and infrastructure. New dependency, new service, a change to how the layers fit: that is an architect call, and it decides without writing the Go itself.
- tech-writer owns the READMEs and keeps them short.
The one I actually talk to
I do not call those agents by hand. I talk to one more, a tech-lead, and it runs the rest. I give it the job in plain English, it works out which specialists are needed and in what order, then runs them and passes each one’s handoff to the next without editing a word of it. It reports back with what changed and the commands it ran to prove it. A typo gets one agent and none of the ceremony; a feature walks the whole chain.
The tech-lead lives under the same restriction as everyone else: it does not edit files either. It reads whatever it needs to make a plan, but every change goes through the agent that owns those files. A coordinator that starts fixing things itself is just another writer, an unreviewed one, and the boundaries it is meant to enforce quietly fall over. So it delegates the work and routes the handoffs untouched, and it stops and waits for me on two kinds of change: anything that touches infrastructure, and anything about to become a document other people read. I describe the work to one lead, and the team runs behind it.
Why the boundaries matter
The rule that matters most is the one on go-writer: it cannot edit tests. A test is the one independent check on the writer’s work, and the moment the same agent can change both the code and the test that judges it, the check is gone. It will make red go green by moving the goalposts, and it will sound confident while it does it.
So when go-writer’s change breaks a test, it has two honest options. Either the code is wrong and it fixes the code, or the test is encoding a real behaviour change, in which case it stops and reports it as a handoff for the test owner to look at. What it cannot do is quietly retag the failing scenario and move on. The separation is what makes the green tick mean something.
Same logic for the reviewer being read-only. A reviewer that can apply its own suggestions is just another writer, and you lose the second pair of eyes. Findings go in a report, I read them, and go-writer fixes the ones I agree with.
Laziness baked in
Left alone, a coding agent builds too much. It adds the interface with one implementation, or the config option for a value that never changes. I got tired of typing “keep it simple”, so I stopped typing it. Ponytail
is a plugin whose hooks carry the ladder into every subagent automatically, so go-writer, architect and go-reviewer all run it without me wiring it into each one. I keep it on ultra, its strictest setting, because an agent needs the restraint dialled higher than I do. Does this need to exist, is it already in the codebase, does the standard library do it, can it be one line. The writer works down that ladder, and the reviewer checks it did. It is a rail on the whole path rather than a note I have to remember to add.
Keeping the agents in sync
The last piece is stopping the agents from drifting apart as the codebase grows. Conventions I have decided (the error taxonomy, the naming rules, the layout) are recorded with mex , so every agent reads the same conventions rather than inventing its own on the day. When I change a rule, I change it in one place and every agent picks it up. Without that, three agents write three subtly different versions of the same function, each passing its own test, which is exactly the three-is-a-pattern problem at machine speed.
And I still read every diff
None of this is hands-off. The agents are fast, and the guardrails stop them lowering the bar while they go quick, but the last check is me reading the change before it lands. The setup does not remove the review. It makes sure that by the time a change reaches my review, it has already been written by one agent, tested by another, and read by a third, none of whom could cover for the others.
If you want to set this up on your own codebase, or just get more out of sub-agents than a single do-everything assistant gives you, give me a shout for a quick chat.