For about the first month I told myself I was reviewing the code. What I was actually doing was asking the agent that wrote it to read it again, in the same conversation, with every assumption it started with still loaded. It would find a typo. It would never find the bug it had already convinced itself was not there. I was wrong, and the way I was wrong is the interesting part, because it is the same mistake most people make when they let AI write the bulk of their code.
Self-review by the author is not review. It is the author nodding at their own reasoning. When the author is a model, the nod is just faster.
So the portal grew a second gate, and the design choice that matters most is what the reviewer is not allowed to know. It runs as its own subagent with no access to the conversation that produced the change. It sees the diff and the repository, nothing else. The amnesia is the feature. A reviewer that did not hear me argue for an approach has no reason to assume the approach was sound, which is exactly the skepticism the author can no longer supply.
The reviewer does not get to freelance on taste, either. Every finding it raises carries exactly one of six tags: correctness, security, observability, testability, api-compatibility, performance. That list is closed. If a problem does not fit one of the six, it is a note, not a finding. Style and naming and duplication are explicitly out of scope, because I do not want a reviewer relocating a comment when the change touches an authorization path on a system that moves money. The categories aim its attention where a real defect lives.
It is also told how to think, not just what to flag. Find the definition of a symbol before reasoning about it. Trace behavior across files, not just inside the hunk. And, the instruction I lean on hardest: zero findings is a normal, valid result. A reviewer that feels obligated to find something will manufacture noise, and noise is how the real finding gets buried.
Here is the move that turned a skeptical reader into something I trust. Every review ends with a fenced record block, written for a machine: a count of findings per category, one risk score from one to five, the size of the diff. It is not prose for me to skim. A separate command, /retro, reads those blocks back out of the commit history and builds a ledger. Risk scores over time. Which categories generate findings and which never do.
That ledger is the difference between hoping the gate works and watching it work. When a whole category stops producing findings, there are two explanations. Either my code is flawless in that dimension, which I do not believe, or the reviewer has gone blind there and I need to fix its prompt. The data tells me which, instead of me assuming the flattering one.
The gate is expensive, so it does not run on everything. A documentation edit pays a lint-only toll. A change that touches authentication, a data write, or core routing pays the full bill, and only then does the independent reviewer spin up, scoped to those files. That is the same judgment a human team applies without saying it out loud, except here it is consistent instead of dependent on whether I happened to feel cautious that day.
What I was really wrong about was thinking review is a step you perform. On a one-person team it has to be a property of the system, independent of the thing it judges and honest enough to show you its own blind spots. The agent writes more code than I could ever read line by line. The gate is what lets me ship it into a contractor's production systems anyway and sleep.
This is the unglamorous half of putting AI to work in a real business: not the generation, the verification around it. I build that verification into the loop as a forward-deployed engineer embedded on a client's team. If you are shipping AI-written code into production and the only reviewer is the model that wrote it, that is the conversation to have at /services.