The 70% problem in AI-assisted coding
Why agents get a feature 70% done in minutes and the last 30% takes longer than writing it yourself — and what to do about it.
The shape is familiar enough that most people using agents daily have stopped remarking on it. You describe a feature. Ten minutes later there is a working implementation — routes wired, types defined, tests passing, error handling that looks reasonable. It runs. You are, briefly, delighted.
Then you start actually using it. The pagination breaks on the last page. The error path returns a 500 where it should return a 409. The optimistic update flickers. Nobody handled the case where the user's session expires mid-request. Each fix is small. Each fix takes a while, because you did not write this code. Four hours later you are still not done, and you have the uncomfortable suspicion that writing it yourself would have taken three.
The delight was real and the frustration is real, and they are the same phenomenon viewed from two sides. Understanding why they are the same thing is what turns this from a complaint into a workflow.
The two halves are different kinds of work
The instinct is to describe this as a quality problem — the agent is 70% good. That framing is wrong and it leads to the wrong response, which is waiting for a better model.
The first 70% and the last 30% are not the same task at different difficulty levels. They are categorically different work, and the second kind is one that language models are structurally bad at for reasons that have little to do with capability.
The first 70% is convergent. Given "REST endpoint with auth, validation, and a database write in a TypeScript codebase using this ORM," there is essentially one right answer, it appears thousands of times in training data, and your codebase's existing files show the local dialect. The agent is doing high-quality pattern completion over an enormous, consistent corpus. It should be fast at this, and it is. This is the part where an agent genuinely outperforms you — not because it is smarter, but because it types at 200 words per second and never forgets to add the import.
The last 30% is divergent. It is where the requirements you did not state turn out to matter. What happens when two users edit the same record? Should a failed webhook retry, and how many times, and does it go to a dead-letter queue? Is "recently active" 24 hours or 7 days? When the upstream API returns a 429, do we back off, queue, or surface the error?
These have no correct answer derivable from the code. They are business decisions. The information required to answer them was never in the prompt, never in the repo, and quite often was never in your head either — you had not thought about it until the code forced the question.
That is the crux. The last 30% is not hard because it is technically difficult. It is hard because it is where under-specification surfaces, and under-specification is not a coding problem.
Agents resolve ambiguity by guessing, and never tell you
Here is what makes this worse than it sounds.
When a competent engineer hits an underspecified requirement, they do one of
three things: ask, flag it in the PR, or leave a TODO naming the assumption.
All three surface the ambiguity to a human who can resolve it.
An agent almost never does any of these. It picks the most statistically likely interpretation and implements it, in the same confident tone it uses for the parts it knows cold. The output contains no marker distinguishing "this is the standard, correct pattern" from "I made this up because you didn't say."
Consider a spec line like "users should be able to export their data." Every one of these questions gets silently answered:
- Format? (It will pick CSV. Or JSON. You will find out later.)
- All data, or the current filtered view?
- Synchronous download, or a job with an emailed link?
- What is the row limit before it becomes a job?
- Does it include soft-deleted records?
- Are other users' names in the shared workspace included? That is a privacy decision, made in passing, by a language model.
You get an implementation that answers all six. It looks finished. Nothing in the diff says "I guessed here." The wrongness is only discoverable by using the feature, or by reading the code with the specific suspicion that a decision was made — which is exactly the reading nobody does on code that appears to work.
This is why the last 30% is expensive. You are not fixing bugs, in the sense of code that fails to do what it says. You are discovering decisions, one at a time, from their downstream symptoms. That is the slowest possible way to find out what your requirements are.
The debugging asymmetry
The second compounding factor: you are debugging code you did not write.
When you write a function, you build a model of it as you go. You know where the state lives, which branch is the hot path, which assumptions are load-bearing. When something breaks, you often have a hypothesis before you finish reading the stack trace, because you remember making the mistake.
Agent-generated code gives you none of that. It reads as plausible on every line — it was generated by a system optimizing for plausibility. There is no code smell to catch your eye, no awkward variable name signaling where the author got confused. It is uniformly reasonable-looking, including the wrong parts.
So debugging degrades from "recall plus verify" to "read and comprehend from scratch." That is a real slowdown, maybe 2-3x on the reading alone, and it is worse when the codebase is unfamiliar or the generated code is spread across several files.
There is a second-order effect that costs more. Because reading it fully is expensive, you tend not to. You patch the symptom in the one function you did read, which works, and you move on — without ever building the model. Repeat that five times and you own a subsystem you have never actually understood, which is where the real cost lands, three weeks later, in a bug that spans two of your patches.
The related failure — where the agent has forgotten its own reasoning and now neither of you has the model — is covered in why AI coding agents lose context.
Why more capable models help less than expected
Model improvements move the boundary. Work that was in the last 30% — subtle concurrency, tricky types, gnarly async — migrates into the first 70% as capability improves. That is real, and it has been happening steadily.
But the residue is different in kind. No model can infer a decision that was never made. Whether your export includes soft-deleted records is not a fact about the world that a sufficiently capable model can deduce. It is a choice somebody at your company has to make. A better model produces a more plausible guess, faster, with better test coverage around the guess.
In some ways that is worse. A crude guess in ugly code invites scrutiny. A sophisticated guess, wrapped in clean abstractions with a passing test suite, sails through review. The failure mode gets quieter as models get better, not louder.
Improving models compress the 70% — that part gets faster and faster. They do not compress the 30%, because the bottleneck there is information that lives in a human's head, or in nobody's head yet.
What to actually do
Everything below is a variation on one move: resolve the ambiguity before generation, not after. The 30% is expensive because you are discovering requirements from symptoms. Discover them from questions instead.
Front-load the ambiguity hunt
Before any code is generated, spend ten minutes finding the decisions. The most efficient way is to make the agent do it — models are much better at identifying ambiguity than at resolving it:
Here's the feature. Before writing any code, list every decision you'd have to
make that isn't determined by what I've told you. For each, give the options
and what you'd pick by default. Don't write code yet.
You typically get 8-15 items. Two or three will be things you had already decided and forgot to say. Most will be genuinely open. One or two will be questions you would not have thought of until production.
Answering those ten questions takes about ten minutes. Discovering the same ten answers through bug reports takes days, and each one arrives as a surprise mid-sprint.
Write acceptance criteria before you generate
If a requirement cannot be stated as a checkable assertion, it is not specified enough to hand to an agent. "Export should work" is not a requirement. "Exporting a filtered view produces a CSV containing only the visible rows, excluding soft-deleted records, with the same column order as the table" is — and it doubles as the thing that catches the agent's guess before you do.
This is worth its own treatment; see acceptance criteria that actually work.
Prefer vertical slices to horizontal ones
The 70/30 split applies per unit of work, so make the units small and complete.
A vertical slice — one user-visible capability through every layer — has a property horizontal work lacks: you can tell whether it is done. You use it. It works or it does not. The last 30% surfaces immediately, on a small surface area, while the whole thing is still in your head.
Horizontal work ("all the endpoints," then "all the UI") defers integration to the end, which means every unresolved decision from every layer surfaces simultaneously, in a pile, after you have generated far too much code to carefully review. The 30% compounds instead of arriving in installments.
Review for decisions, not for correctness
Standard code review asks "is this right?" For agent output, the higher-yield question is "what did this decide?"
Read the diff looking specifically for places where a choice was made: a default
value, a hard-coded threshold, a catch that swallows, a boundary condition, a
sort order, a timeout. Each of those is a decision. For each, ask whether you
made it or the model did. The ones the model made are where your last 30% lives,
and finding them by reading takes minutes rather than days.
Defaults deserve particular suspicion. A default is an unstated decision wearing a disguise, and it is the single most common place for a plausible guess to hide.
Know when to take over
The clearest signal you should stop delegating: you have explained the same constraint three times and the code still does not reflect it.
That is not the agent being stubborn. It is a sign the constraint is subtle enough that conveying it in prose costs more than expressing it in code. If you can describe precisely what is wrong with the output, you already have the mental model. Writing it yourself is now the fast path.
Other reliable take-over signals:
- The bug is in code you have not read, and reading it will take longer than rewriting it.
- The problem is genuinely novel — no established pattern to draw on.
- You are on your fourth round of "no, not like that."
- Correctness depends on something you know but cannot easily articulate.
Taking over is not defeat. The 70% you got for free was real value, and the handoff point is a judgment call, not a failure. The mistake is not taking over too early; it is spending two more hours in the loop because stopping feels like admitting the tool did not work.
The reframe
The productive way to hold this: an agent is very fast at the part of the work that has a known answer, and no faster than you at the part that requires deciding what you want. That is not a limitation to route around. It is a description of where the work actually is.
Which means the leverage is not in better prompting or better models. It is in doing the deciding earlier — before generation, in a form that persists, rather than after, one bug at a time. The 70% was never the hard part. It only felt like the hard part because it was the part that took all the typing.
Keep reading
- How to write specs for coding agentsA practical guide to specs an LLM can execute — non-goals, verifiable criteria, named interfaces, and decisions with rationale that survive review.
- ZeNorm vs. AWS KiroKiro closes the loop inside its own IDE with click-through approvals. ZeNorm gates on a score and hands off to your agent. A mechanism comparison, honestly.
- ZeNorm vs. GitHub Spec KitSpec Kit is free MIT-licensed prompt templates run by your own agent. ZeNorm runs a server that refuses. A mechanism comparison, and when Spec Kit wins.