DEV Community

Stop Writing Specs for Your Agents

Spec-driven development is having a moment. Write a detailed spec, hand it to the agent, get working code. It's a clean story and it maps neatly onto how we already think about requirements.

I think it's the wrong shape, and the failure mode isn't obvious until you're six months in.

Specs rot at four different speeds

Here's the actual problem with a spec: it isn't one document. It's four documents wearing a trench coat.

A typical spec contains, all interleaved:

  1. Why we're building this — the product requirement.
  2. How the system is structured — the architectural decision.
  3. The order of operations — the implementation plan.
  4. What "done" looks like — the contract.

Each of these has a completely different half-life.

The product requirement changes when a customer complains. The architectural decision holds for years — and when it doesn't, the record of it still matters, because someone will ask why the old thing looked like that. The implementation plan is dead the moment the PR merges. The contract only matters if it's executable, because anything else is a lie waiting to happen.

Put them in one file and the file decays at the speed of its fastest-rotting component. Within a couple of sprints, that document is 30% accurate. It's the worst possible state — confidently wrong, and now indistinguishable from the parts that were right.

That was bad enough when only humans read it. Now you have agents ingesting it as ground truth. A stale spec doesn't just confuse a new hire who'll ask a follow-up question in Slack. It actively steers an agent into building against a system that no longer exists, and the agent won't push back, because it has no way to know.

Documentation should be layered by lifetime, not by topic.

Here's what I actually run.

Layer 1: Product requirements → the ticket system

GitHub Issues, Linear, Jira. Inside larger orgs, PRDs and RFCs.

These are supposed to churn. That's not a defect, it's the job. Tickets have closed states, owners, and timestamps, which makes their staleness legible — a ticket from eight months ago is visibly a ticket from eight months ago.

Critically: this layer does not belong in the repo. The moment you mirror product requirements into markdown next to your code, you've created two sources of truth that will diverge, and the one in git will lose — because nobody closes a markdown file.

Layer 2: Architectural decisions → ADRs in git

This is the layer that actually earns its place in version control.

I use MADR. Each decision gets a numbered file: context, options considered, decision, consequences. Decisions are never edited to reflect new reality — they're superseded by a new record that references the old one.

Two things fall out of this that matter enormously for agents:

It's append-only, so it can't rot. An ADR describes a decision made at a point in time. It's accurate forever, by construction. "We chose Postgres over DynamoDB in March 2024 for these reasons" doesn't stop being true when you migrate — you just add ADR-0042 marking 0017 as superseded.

It captures the why, which is the thing agents cannot infer. An agent can read your code and reconstruct what it does. It cannot reconstruct why you rejected the obvious alternative. That's the highest-value context you can possibly hand it, and it's exactly what specs bury under implementation detail.

When an agent asks "should I add a queue here?", the ADR directory is the answer.

Layer 3: Implementation plans → ephemeral, never committed

This is where I differ most sharply from the spec-driven crowd.

The implementation plan is scaffolding. It exists to get from "we agreed on the approach" to "the code works." Then it should die.

Modern agents handle this natively now — Claude and Codex both maintain working plans internally and iterate against them. Before that, I'd write a todo.md, work through it, and delete it. Same thing, worse ergonomics.

The important part is that it stays out of git. A committed implementation plan is a fossil that looks like a map. Six months later someone — or some agent — reads it as a description of the system, when it was only ever a description of an afternoon.

Layer 4: Contracts → black-box integration tests

Prose cannot define a contract. Prose describes a contract, and the description drifts.

What actually defines my code at the contract level is a suite of integration tests exercising it from the outside in — no internal mocks, no reaching into private state. Call the public surface, assert on observable behavior.

This is interface-first design. It gives you three things at once:

  • A specification that cannot go stale, because CI fails when it does.
  • A refactoring boundary — internals are free to change as long as the black box holds.
  • A verification loop an agent can run itself, without a human in the middle.

That last one is underrated. An agent with executable contracts can self-correct. An agent with a prose spec can only guess whether it complied, and will report success either way.

This is not TDD. TDD means writing tests and defining correctness up front, before the implementation. Sometimes that's right. Often you don't know the correct interface until you've built enough to feel where it's wrong, and committing to a test suite prematurely just means you refactor tests instead of code. What I'm claiming is narrower: by the time it's merged, the contract is expressed as executable black-box tests. How you got there is your business.

What this looks like in practice

Layer Lives in Lifetime Who reads it
Product requirements Ticket system Churns constantly Humans, mostly
Architectural decisions Git (MADR) Permanent, append-only Humans + agents
Implementation plan Agent context / scratch file Dies with the PR Agent, briefly
Contract Integration test suite Enforced by CI CI, agents, humans

Four artifacts. Four locations. Four decay rates, each matched to a medium that handles it.

Nothing here is novel on its own — ADRs are a decade old, black-box testing is older than that. The claim is about the separation itself: that the layering is the methodology, and that collapsing it into a single spec document is the thing quietly poisoning your agent's context window.

The uncomfortable part

The pitch for spec-driven development is that you write one document and the agent does the rest. What I'm describing is more work up front. You maintain a ticket system, you write ADRs, you build real integration tests.

But you were going to do all three anyway. Spec-driven development doesn't remove that work — it duplicates it into a fifth artifact that has no owner and no expiry date, and then feeds that artifact to a machine that can't tell it's out of date.

Write less documentation. Put each piece where it can't lie to you.

Top comments (1)

Collapse
 
vinimabreu profile image
Vinicius Pereira

"Layered by lifetime, not by topic" is the line that earns the post. The layer I would extend is contracts as executable tests, because it breaks in an interesting way when the thing under contract is a model.

A black-box test pins deterministic behavior. When the component is an LLM call, the contract is a distribution rather than a value, so a single assert either flakes or gets loosened until it asserts nothing. What works is keeping the layer and changing the instrument: a labeled eval set with thresholds, committed and run in CI. Same properties you want from the test layer, executable, fails loudly, cannot drift in silence, except it measures rates such as unsupported-claim and correct-abstention instead of one equality. Prose cannot define that contract either.