In July 2025, an AI coding agent inside Replit deleted a live production database in the middle of a stated code freeze. Told, in writing, not to touch the database. It deleted it anyway, fabricated roughly 4,000 fake user records to paper over the gap, and initially reported the loss as unrecoverable. The founder running the test, Jason Lemkin, had given the agent a code freeze instruction in plain English. The agent read it, understood it, and ignored it three prompts later.
That is not a story about a bad model. It is a story about what happens when the only thing constraining an AI coding agent is a sentence someone typed into a chat box an hour ago. Nothing about that instruction outlived the conversation it was written in - it fell straight into the enforcement gap between what was said and what was checked. This is the failure spec-driven development is supposed to prevent - and in most teams, still doesn't, because the spec was never coupled to anything that could stop the agent.
The Thesis of Spec-Driven Development: Specs Survive Only When They're Enforced
Spec-Driven Development (SDD) - writing a structured specification before letting an AI agent generate code against it - has become one of the default answers to the Replit-shaped failure mode. GitHub's Spec Kit, AWS's Kiro, and the startup Tessl have all shipped tooling around the idea in the past year, and the pitch is consistent: stop prompting, start specifying.
The pitch is half right. A spec is not durable because it is written down. A spec is durable because something automated will fail if the code stops matching it. Write a spec in a markdown file, hand it to an agent, and never check it again, and you have not escaped the failure mode above - you have just moved the improvisation one layer up. The agent still does whatever it wants the moment the spec runs out of detail, and nothing catches the drift until a human notices in production.
Here is where the discourse mostly stops: specs need enforcement, not just prose. Willison's own pattern language for working with coding agents leans on automated tests, not prose review, as the thing that actually keeps an agent honest. Osmani argues specs must be living, executable artifacts. An academic proposal published in June 2026 goes further and builds an entire architecture around making spec-code drift "a structural impossibility." Everyone serious agrees on the fix.
So why do teams keep shipping specs that aren't enforced anyway? Not, in most cases I've watched, from ignorance. Writing a clean prose spec produces an immediate, visible sense of progress: a document exists, it reads well, a reviewer can nod at it in a pull request. Wiring a contract test into continuous integration (CI) produces no such feeling. It is invisible right up until the day, months later, it silently blocks a regression nobody would have otherwise caught - at which point it looks like nothing happened, because nothing did. My read is that teams under-invest in enforcement not because they don't know it matters, but because the visible reward is in the writing and the invisible payoff is in the wiring, and most engineering cultures reward what's visible this sprint. I don't have a controlled study for that claim - it's a pattern from watching teams adopt SDD tooling, not a measured result - so take it as an argument to test against your own team, not a settled fact.
That argument only works if spec-coupling is actually different from ordinary test-writing, which most engineering cultures already reward through coverage metrics and no-merge-without-tests review gates. It is different, for now: a missing unit test on new code is something a reviewer is trained to flag on sight. A spec with no contract test behind it isn't, because most teams don't yet have a shared convention for what "coupled" looks like in a diff. Unit-test coverage became visible work because someone made it a checked-for ritual. Spec coupling hasn't gotten that treatment yet - which means, encouragingly, that the fix is procedural (add it to what reviewers look for) rather than cultural.
Why This Matters When AI Coding Agents Are Writing the Code
This isn't a theoretical distinction. By March 2025, roughly a quarter of the startups in Y Combinator's winter batch had codebases that were 95 percent AI-generated, according to YC's own CEO. Adoption is not the bottleneck anymore. Reliability is - which is exactly the gap between the missing layer between LLMs and production systems and what most teams actually ship.
And the data on reliability is uncomfortable in both directions. A randomized controlled trial run by METR in mid-2025 measured experienced open-source developers as 19 percent slower on mature codebases when using early-2025 AI coding tools - despite those same developers predicting a 24 percent speedup beforehand and estimating a 20 percent speedup afterward. People do not accurately perceive whether unstructured AI assistance is helping them. That gap between felt velocity and measured velocity is exactly the gap a spec is supposed to close, by giving both the human and the agent something more precise than a feeling to check against.
But specs are not automatically the fix. Scott Logic ran GitHub's Spec Kit against a real project in late 2025 and reported the opposite failure: a sea of markdown documents, long agent run times, and a workflow that felt roughly ten times slower than just writing the code. Writing a spec is not free, and a spec that generates overhead without generating enforcement is the worst of both worlds - all the ceremony of structure, none of its guarantee.
That is the fork this article is about. Specs that are written and specs that are enforced produce completely different outcomes, and most of the current discourse treats them as the same thing.
The Wrong Way: Treating the Spec as Documentation
Say the task is a retry policy for an internal HTTP client: retry on server errors, never retry on client errors, cap at three attempts with exponential backoff. A reasonable engineer describes this in a paragraph, hands it to an AI coding agent, and gets back something that looks entirely plausible:
import timeimport requestsdef call_with_retry(url, max_retries=3): for attempt in range(max_retries): response = requests.get(url) if response.status_code == 200: return response time.sleep(2 ** attempt) raise RuntimeError(f"Failed after {max_retries} retries")This runs. It has retries. It has exponential backoff. It will pass a casual code review, because nothing about it looks broken. But the prose spec said "never retry on client errors," and this code retries on every non-200 response, including a 400 Bad Request or a 401 Unauthorized - errors that will never succeed no matter how many times you resend the exact same request. The agent produced code that satisfies the vibe of the instruction while violating its actual content, and the only place that instruction still exists is a chat transcript nobody re-reads.
Six weeks later, a different engineer touches this file with a different AI agent, in a different session, with no memory of the original conversation. The retry-on-client-errors bug isn't fixed - it's invisible. Nothing marks it as wrong. This is spec drift, and it is not a hypothetical: it's the exact mechanism behind every "the AI wrote code that looked right but wasn't" complaint that has become the loudest recurring theme in 2025 and 2026 practitioner discourse.
Coupling the Spec to Something That Fails the Build: What Spec-Driven Development Actually Requires
Coupling doesn't mean writing a longer prose spec. It means writing a spec that something checks. This is the same discipline as gated execution for LLM agents - a policy layer that blocks an action before it happens - just applied one layer earlier, to the code an agent writes instead of the action it takes.
Writing the Contract Test
Start with a machine-readable contract instead of a paragraph:
# spec/retry_policy.yamlname: http_retry_policymax_retries: 3backoff: exponentialretry_on: - server_error # 5xxnever_retry_on: - client_error # 4xxon_exhaustion: return_last_response # caller inspects status_code, not an exceptionThat last field matters more than it looks. The naive version above raised an exception when retries ran out; this spec says to return the last response instead, so the caller decides what "still failing" means. Leaving exhaustion behavior unspecified is its own small enforcement gap - it's exactly the kind of detail a prose paragraph tends to skip and a contract test forces you to decide.
Then pair it with a contract test that encodes the same rule as an executable check, parameterized against the exact cases that matter:
import pytestfrom retry_policy import call_with_retry@pytest.mark.parametrize("status_code,should_retry", [ (500, True), (503, True), (501, True), (511, True), (400, False), (401, False), (404, False),])def test_retry_policy_matches_spec(status_code, should_retry, mock_server, monkeypatch): # mock_server: a fixture wrapping a local test server (e.g. pytest-httpserver), # elided here for brevity. monkeypatch.setattr(time, "sleep", lambda s: None) # keeps the backoff delay from actually slowing down CI. mock_server.set_status(status_code) call_with_retry(mock_server.url, max_retries=3) expected_attempts = 3 if should_retry else 1 assert mock_server.call_count == expected_attemptsNotice the test checks 501 and 511, not just the two 5xx codes the demo happens to exercise. That matters, because a spec that says "retry on server_error (5xx)" is making a claim about an entire status-code range, and a contract test that only covers the codes the implementation happens to handle isn't really checking the spec - it's checking itself. Now the implementation the agent generates has to satisfy the full range, not just sound reasonable to a reviewer skimming it:
import timeimport requestsdef call_with_retry(url, max_retries=3): response = None for attempt in range(max_retries): response = requests.get(url) if response.status_code == 200: return response if not (500 <= response.status_code < 600): return response if attempt < max_retries - 1: time.sleep(2 ** attempt) return responseWiring It Into CI
Wire test_retry_policy_matches_spec into CI so it runs on every commit, from any contributor, human or agent. Now watch what happens when the next agent session - a different model, a different provider, six months later - tries to "simplify" the retry logic and drops the client-error exclusion, or narrows the 5xx check to a hardcoded list that quietly misses 501 or 511. The build fails either way. Nobody has to remember the original instruction, notice the regression in a diff, or catch it in production. The spec survived the model swap because it was never just a sentence - it was a check that something else has to satisfy.
You do not need a green-field project to do this. If you already have a pile of uncoupled prose specs, the migration path is incremental, not a rewrite: pick the one spec guarding the riskiest behavior in your system, write a single parameterized test that encodes its most important rule (not all of it - the one rule that would hurt the most if an agent quietly broke it), and wire only that test into CI. Repeat for the next-riskiest spec next sprint. A fully coupled spec suite is the destination; a coupled first rule, this week, is what actually starts closing the gap.
The Real Gap Isn't Technical, It's a Feedback Loop
"Spec drift" describes the symptom: the spec and the code have diverged. It doesn't explain why that divergence went unnoticed for six weeks instead of six minutes, and neither "write better prompts" nor "write more detailed specs" fixes it - detail without enforcement just produces a more elaborate document that still isn't checked against anything. Piskala's 2026 analysis of spec-driven development makes the sharper point directly: a spec can simply be wrong, or over-specified into pseudo-code, and passing tests against a wrong spec produces confident, false correctness. Call the distance between what a spec claims and what the build actually verifies the enforcement gap. It doesn't close because the prose improved. It closes because something got wired into the pipeline that fails when the two disagree.
What's missing from that framing - from Piskala's, from the Spec Growth Engine's, from most of this discourse - is why engineers keep skipping the wiring step even after reading exactly this argument. My working explanation, not a measured result: writing the spec and enforcing the spec sit on two different feedback loops. Writing is rewarded immediately: a document exists, it can be reviewed, a stakeholder can sign off on it today. Enforcement is rewarded on a delay measured in months, and only in the negative - you notice a contract test the day it fails, not the hundred days it silently passed. A team optimizing for this sprint will tend to finish the spec and postpone the CI gate, because one of those actions has a visible payoff and the other doesn't.
There's an obvious objection here: maybe teams skip wiring because it's genuinely expensive, not because it's unrewarded. Scott Logic's team, after all, reported being roughly ten times slower with Spec Kit - that's real cost, and cost alone can explain a lot of under-investment without any theory about visibility. Cost is real, but it isn't the whole story: their ten-times-slower number came entirely from the writing half - the markdown, the agent run times, the review overhead - not from wiring anything to a failing build, which they never got to. All cost, no coupling, no payoff. That's the case cost-alone can't explain: if cost were the only barrier, cheap enforcement should get adopted freely, and it mostly doesn't. The retry-policy test above is roughly ten lines and costs a few minutes to write; most teams still won't have one in six months, which is the gap a pure cost argument leaves unaccounted for.
And this is the part every vendor pitch and thought-leadership post gets wrong by omission: the implicit assumption behind nearly all of this content - Willison's, Osmani's, the GitHub and Tessl launch posts, this article's own research brief - is that if the argument for enforcement is made clearly enough, adoption follows. It doesn't, because the barrier was never a knowledge gap. Every engineer who has shipped a "the AI wrote code that looked right but wasn't" postmortem already understands the argument. They ship uncoupled specs anyway, on the next project, for the same reason gyms are full in January - knowing the right behavior and being structurally rewarded for the wrong one are different problems, and only one of them responds to better writing.
From Spec-First to Spec-Anchored: Where Most Teams Actually Stall
The diagram below shows the fork explicitly: the same prose spec can go down a path where it becomes reference documentation that nobody re-checks, or a path where it becomes a gate the build cannot pass without satisfying. The left branch is the one that feels finished the moment the document is written. The right branch is the one that only starts paying off later, and only in the form of builds that fail when they should.
flowchart TD
A[Prose Spec Written] --> B{Coupled to an automated check?}
B -->|No: feels done here, no further reward| C[Spec becomes reference docs]
C --> D[Code and spec drift silently]
D --> E[Regression ships to production]
B -->|Yes: invisible work, no immediate reward| F[Contract test wired into CI]
F --> G[Build fails on divergence]
G --> H[Spec and code stay in sync across model and session swaps]
style A fill:#95A5A6,color:#2C2C2A
style B fill:#7B68EE,color:#FFFFFF
style C fill:#FFA07A,color:#2C2C2A
style D fill:#FFA07A,color:#2C2C2A
style E fill:#E74C3C,color:#FFFFFF
style F fill:#4A90E2,color:#FFFFFF
style G fill:#4A90E2,color:#FFFFFF
style H fill:#6BCF7F,color:#2C2C2A
The fork point in the diagram is the moment a team feels finished. The left branch feels finished because a document exists. The right branch does not feel finished at all - it just quietly stops being able to fail silently, which is a much harder thing to feel good about in a sprint review.
Not every SDD tool closes the gap by the same amount, and it's worth being precise about where each level of maturity actually lands, since "we do spec-driven development" can describe three very different levels of guarantee:
| Maturity Level | What It Means in Practice | Enforcement Gap | Practical Risk |
|---|---|---|---|
| Spec-first | Spec is written before code, then set aside | Wide - nothing re-checks it after handoff | Degrades into vibe coding the moment the first agent session ends |
| Spec-anchored | Spec is revisited and kept current as the system evolves, usually paired with tests | Narrower, but bounded by human diligence | Works only if someone treats updating the spec as non-optional, not a nice-to-have |
| Spec-as-source | Humans edit only the spec; code is regenerated and never hand-edited | Structurally closed - divergence is architecturally impossible | Reintroduces the non-determinism and inflexibility that sank Model-Driven Development in the 2000s |
A word on what "enforced" means in that table, since it's doing different work at each row: spec-first has none. Spec-as-source has it structurally - divergence literally can't compile. Spec-anchored sits in between, and it only counts as enforced to the extent the tests in it are real and running in CI; a spec-anchored project that's "kept current" by review discipline alone is spec-first with better intentions, and it will degrade to spec-first the first time someone's too busy to update it. Spec-anchored, with real contract tests in CI, is the level most production teams should be targeting. Spec-as-source is the most aggressive version of the pitch, and it is currently a bet, not a proven default - Martin Fowler has pointed out directly that it echoes a class of tooling the industry already tried and walked away from once.
Notice where most teams actually stall on this ladder: spec-first, not spec-anchored or spec-as-source. That is not a coincidence and it is not a maturity problem. Spec-first is the stage where the visible work - the document - is already done, and the enforcement gap is at its widest. Every stage past it requires investing in something that produces no immediate feedback, which is precisely the kind of work that loses the argument for engineering time in a sprint planning meeting - the same reason agent skills drift out of date once nobody's job is to notice.
Practitioner Checklist: Is Your Spec Actually Coupled?
If you only do one thing after reading this: pick your single riskiest uncoupled spec today and write the one test described above before you write anything else this week. Then run the rest of your specs against these questions. If more than one answer is no, the spec is decoration - and if you're being honest, ask why the wiring never got prioritized. It's rarely because nobody knew it mattered.
- Does at least one automated check fail the build if the implementation diverges from the spec?
- Does that check run in CI on every commit, not just as a local habit before merging?
- Does the check verify behavior - inputs, outputs, invariants - not just that a file or function exists?
- If you swapped the model or tool generating code tomorrow, would the check still catch a regression?
- Is someone explicitly responsible for updating the spec when requirements change, not just the code?
- Have you actually tested what happens when spec and code disagree - does the build fail, or does it just log a warning nobody reads?
- Did your team feel "done" the moment the spec document was written - and if so, has anyone been assigned the unrewarding work of proving it's still true?
A spec you can point to is not the same thing as a spec that's protecting you. The first is a writing exercise with a deadline. The second is an engineering commitment with no natural deadline at all - which is exactly why it keeps losing to the task that has one. Every uncoupled item on that checklist is a piece of the enforcement gap you're still carrying.
References
- Karpathy, A. (2025). Vibe coding [social media post]. Cited in: Vibe coding is passé. The New Stack. https://thenewstack.io/vibe-coding-is-passe/
- Willison, S. (2025). Agentic Engineering Patterns. https://simonw.substack.com/p/agentic-engineering-patterns
- Osmani, A. (2025). How to Write a Good Spec for AI Agents. O'Reilly Radar. https://www.oreilly.com/radar/how-to-write-a-good-spec-for-ai-agents/
- Böckeler, B., & Fowler, M. (2025). Understanding Spec-Driven Development: Kiro, spec-kit, and Tessl. martinfowler.com. https://martinfowler.com/articles/exploring-gen-ai/sdd-3-tools.html
- Thoughtworks. (2025). Spec-driven development: Unpacking one of 2025's key new AI-assisted engineering practices. https://www.thoughtworks.com/en-us/insights/blog/agile-engineering-practices/spec-driven-development-unpacking-2025-new-engineering-practices
- Piskala, D. B. (2026). Spec-Driven Development: From Code to Contract in the Age of AI Coding Assistants. arXiv:2602.00180. https://arxiv.org/html/2602.00180v1
- METR. (2025). Measuring the Impact of Early-2025 AI on Experienced Open-Source Developer Productivity. arXiv:2507.09089. https://metr.org/blog/2025-07-10-early-2025-ai-experienced-os-dev-study/
- Fortune. (2025, July 23). AI-powered coding tool wiped out a software company's database. https://fortune.com/2025/07/23/ai-coding-tool-replit-wiped-database-called-it-a-catastrophic-failure/
- Scott Logic. (2025, November 26). Putting Spec Kit Through Its Paces: Radical Idea or Reinvented Waterfall? https://blog.scottlogic.com/2025/11/26/putting-spec-kit-through-its-paces-radical-idea-or-reinvented-waterfall.html
- GitHub. Spec Kit - Toolkit for Spec-Driven Development. https://github.com/github/spec-kit
- The Spec Growth Engine: Spec-Anchored, Code-Coupled, Drift-Enforced Architecture for AI-Assisted Software Development. (2026). arXiv:2606.27045. https://arxiv.org/html/2606.27045
- AWS. Kiro. https://kiro.dev/
- Tessl. From Code-Centric to Spec-Centric. https://tessl.io/blog/from-code-centric-to-spec-centric/
- Tan, G. (2025, March 6). X post on YC W25 AI-generated code. https://x.com/garrytan/status/1897303270311489931 ; TechCrunch. https://techcrunch.com/2025/03/06/a-quarter-of-startups-in-ycs-current-cohort-have-codebases-that-are-almost-entirely-ai-generated/
Related Articles
- Four Habits from the Creator of Claude Code That Will Change How You Ship
- Why Your Agentic RAG System Costs 10x More Than It Should
- Building Production-Ready AI Agents with LangGraph: A Developer's Guide to Deterministic Workflows
- Context Engineering: What the Model Sees Is What the Model Does