Your review bottleneck is an abstraction problem
by Marvin Strangfeld
Everyone has the same complaint right now. Agents generate code faster than humans can review it, so review is the new bottleneck, so we need to make review faster.
Take a concrete change: give the payments service a staging environment. You get a PR with 480 new lines. A VPC, three subnets, route tables, a security group, an RDS instance, an IAM role, a policy document, a task definition, a handful of secrets. To review it, you need to think of and check many things: Is the database in the private subnet? Does the task role scope to payments/staging/* or did it end up with a *? Is deletion protection on, and should it be, for staging?
Half an hour later you’ve read one PR, you’re out of brain for the day, and coding agents have opened four other PRs in the meantime.
The standard solution: more AI
The popular fix is to buy a review agent: the same class of model that wrote the code, pointed back at the code. Sometimes people vary the vendor, generate with Claude and review with GPT, as if the disagreement between two labs were a source of assurance.
Ask where the human trust comes from. If you couldn’t hold those 480 lines in your head, a second model’s approval doesn’t hand you understanding. It hands you a verdict you also can’t check. That’s duct tape over duct tape.
To be fair to the category: agent review is genuinely good at a narrow job. Checking a diff against standards you’ve written down, catching the deprecated chart, the missing resource limit, the retry loop with no jitter. That’s real work and it scales. What it doesn’t do is produce a human who understands the change. If you need that (and for anything that can lose data or leak a credential, you do) the only lever is making the change small enough to understand.
A different solution: more abstractions
Take the same PR, but now assume it’s opened against a platform with good abstractions:
module "payments_staging" {
source = "../modules/environment"
name = "payments-staging"
tier = "staging"
database = "postgres-16"
ingress = "internal"
}
Five lines. The review is: right name, right tier, does this service need Postgres, should it be reachable from outside. Four questions, and you can answer all four from what you know about the payments service. You don’t need to know anything about AWS.
The second PR is not only shorter, it also requires answering simpler questions. Only the second one is a check a human can do reliably at eleven at night with four other PRs open.
Abstraction is harder than it sounds
Of course, you haven’t deleted the 480 lines. You moved them into modules/environment.
That’s a good trade when the module is reviewed carefully once and instantiated forty times. It’s a bad trade when it isn’t reviewed carefully, and the same 480 lines land with less scrutiny than they’d have got in the open. The economics only work if the attention follows the risk.
Then the abstraction leaks. Some service needs an IAM statement the module doesn’t expose, someone adds extra_policy_statements, and six months later half your call sites pass raw JSON through the escape hatch and you’re reviewing policy documents again with extra steps. Every long-lived abstraction collects these. It is difficult to say no to escape hatches while still shipping the thing the team needed on Thursday, and figuring out how to change the interface without breaking forty call sites. This is a real skill and most organisations do not have many people who have it.
And plenty of code doesn’t compress. Business logic is where the variety lives; that’s the point of it. Abstraction buys you the 70% underneath that’s plumbing, and the remaining 30% still has to be read by someone who cares.
We’ve done this for decades
An operating system is an abstraction over hardware. You don’t reason about which memory controller is in the machine. Nobody files that under developer productivity, but it’s the largest productivity intervention in the history of the field.
Take everything that isn’t business logic: deploys, secrets, networking, observability, data access. Define it once, define it well, and the code on top gets short enough to read.
If you want review to keep pace with generation, don’t try to read faster. Reduce what a change has to say.