The spec, not the prompt, decides what an agent builds
The bottleneck in building software with agents moved, and a lot of people are still optimising the old one. It used to be "can the agent write code that works." That's mostly solved. The question now is "is the spec good enough that the agent builds the right thing, well," and that's a different skill, closer to writing than to coding.
The numbers are stark enough to take seriously. GitHub and AWS both report 3 to 10 times higher first-pass success when an agent works from a formal spec versus ad-hoc prompting. That gap is the difference between an agent that lands the change and one that builds a plausible wrong thing you then have to notice and unwind. The prompt tells the agent what to do this turn. The spec tells it what "done" and "correct" mean, and without that second thing a capable agent will confidently satisfy the letter of your request while missing the point.
What actually goes in a good spec is more specific than "describe what you want." The pattern that's converged is event-driven and testable: "when this trigger happens, the system shall do this response." Written that way, the spec language and the test language become the same language, so a requirement and its check are the same sentence. Alongside that, a three-tier constraint model that's saved me repeatedly: things the agent may always do, things it must ask before doing, and things it must never do. Spelling those out up front is what stops an agent one-shotting a change into territory you'd have wanted a say in.
Brownfield is where specs get skipped and shouldn't. Writing a change into an existing codebase without an exploration pass first, mapping the components, the existing patterns, the integration points, the debt, is how agents produce code that works in isolation and fights the codebase it lands in. The good spec formats make preservation explicit, marking what's being added versus modified versus removed. The failure mode in existing code is rarely the new behaviour. It's usually the old behaviour the change quietly broke.
One counterintuitive finding reshaped what I put in my own context files. A study out of ETH Zurich found that effective context files contain tooling commands and non-inferable constraints, and that architecture overviews increase cost without improving task success. The agent can read the architecture from the code. What it can't infer is the command to run your tests, or the constraint that this one function is load-bearing for reasons nothing in the file explains. So I stopped writing prose descriptions of how the system is shaped, and kept the context to the things a capable reader genuinely couldn't derive.
The guardrail I lean on hardest is spec-as-test, and it directly answers a problem I kept hitting: agents write too many tests, and the tests they write are weak. This is measured, not a hunch. In a study of over a million commits, agents added mocks in 36 percent of commits against 26 percent for humans, and test quantity turned out not to predict whether the underlying problem was actually resolved. AI-written tests hit roughly 20 percent mutation scores, meaning 80 percent of deliberately injected bugs sail straight through code the tests supposedly cover. A green suite the agent wrote itself is close to worthless as evidence, because the agent optimises for the suite passing, and writing a test that passes is trivially easier than writing one that would catch a real regression.
So I write the tests. Not all of them, but the 3 to 10 integration tests that encode what the feature actually has to do, and I write them to fail first. Then the agent's job is to make my failing tests pass without modifying them. That one constraint, it cannot touch the tests, flips the incentive. The agent can no longer make the check green by weakening the check. The spec is executable, it's adversarial to the implementation rather than complicit with it, and the thing verifying the work is something I wrote to express intent rather than something the agent wrote to clear a bar. Pair that with diff-scoped coverage gates rather than a global percentage, so you're measuring the code that actually changed, and you have a loop where you can ship without reading every line, because the part doing the verifying is the part you kept your hands on.
The whole shift is that the leverage moved upstream. The valuable, hard-to-delegate work in agentic coding is no longer typing the implementation. It's writing the spec precisely enough that "the right thing" is unambiguous, and writing the tests honestly enough that "correct" can't be faked. Get those two right and the agent does the rest. Get them wrong and no amount of prompting rescues it.