Back to blog

Writing SDD Artifacts an Agent Can Follow

Sep 23, 2026
Series · Day 7
Spec-Driven Development
View all lessons →
Writing SDD Artifacts an Agent Can Follow

Day 7 — Writing SDD artifacts an agent can actually follow

Your four artifacts are done. Constitution, spec, plan, tasks — all filled in, all reading fine. You hand the bundle to the agent and get back a wrecked PR anyway. Every field was there; the code still came out wrong. This isn't a question of whether to write the artifacts — you did. It's one failure mode sitting underneath almost all of it: the right decision written on the wrong floor.

The 40-file diff after doing everything right

Here's the moment it clicked for me. Four files, all clean. Constitution reads like a style guide. Spec has real requirements, acceptance criteria, the works. Plan names the stack. Tasks are numbered. I hand the whole thing over, go make coffee, come back to a 40-file diff for what should've been a 4-file change. Auth got refactored. A logging util got 'improved.' Two tests I never mentioned got rewritten.

First instinct: the docs are incomplete, I missed a field. Wrong — every field was filled. The bug wasn't absence, it was leakage. A decision that belonged on the spec floor had slid down into tasks. A tech choice that belonged in the plan had crept up into the spec. Each artifact looked complete on its own, and the seams between them were exactly where the agent fell through.

Altitude: the one question that audits any field

Picture the four artifacts as four floors of a building. Each floor owns decisions at exactly one altitude:

  • Constitution — the always-on rules. Rides in every prompt, every artifact, every task. Less a floor than the building code the whole structure has to obey.
  • Spec — what and why. The behavior you want and the reason for it. No how.
  • Plan — how. Tech choices, design, structure. No re-arguing what.
  • Tasks — sequence. Ordered, PR-sized steps, each with the test that proves it done.

Every field in every template is a fence marking where one altitude ends and the next begins. So the audit question for any line you write is blunt: is this decision on the wrong floor? A database name in the spec is a plan-floor decision that leaked up. Acceptance criteria buried inside a task is a spec-floor decision that leaked down. Requirements re-argued in the plan is the spec floor being re-litigated one story too high. Nearly every useless template is one of those three.

Who you're actually writing for: an agent, not a PM

These four templates are borrowed furniture. Constitution ≈ eng standards doc, spec ≈ PRD, plan ≈ design doc, tasks ≈ Jira breakdown. All of them were built for humans, and humans read a particular way: they skim prose, infer intent, silently skip the acceptance-criteria table, patch the gaps with judgment. A 'good' PRD leans on every bit of that.

An agent-executor reads the opposite way. It doesn't skim — it takes every sentence as instruction, your throat-clearing narrative included. It doesn't infer the exit condition — hand it no falsifiable done-check and it invents 'looks done.' It doesn't know your file layout unless you write the paths. And it drowns in story: the paragraph of context that helps a human orient is pure noise-per-token to an agent, and worse, it reads your 'we should probably also clean up...' aside as a task. There's your 40-file diff. That's why a 'complete' human doc still fails — complete-for-a-human and complete-for-an-agent are two different specs.

  • Human doc optimizes for: context, motivation, narrative flow, room for judgment.
  • Agent doc optimizes for: explicit exit tests, literal file paths, dependency order, zero narrative it could mistake for scope.

Constitution: the always-on file

The constitution is the only artifact injected into every single prompt, which is why its signal-to-junk ratio compounds harder than anything else. A dead line in one task pollutes one task. A dead line in the constitution pollutes every task, forever, and burns context budget on every call. A field earns its place here only if it's a trigger plus a testable constraint — a condition the agent can detect, and a rule it can check itself against. Not a value. Not a vibe.

markdown
# Good — trigger + testable constraint
- When creating an API route: it MUST export `const runtime = 'nodejs'`.
- When importing an LLM: import from `app/lib/llm.js`; never a provider SDK directly.
- Language is JavaScript. Do NOT introduce TypeScript, .ts files, or type annotations.

# Dead weight — no trigger, not testable, pure vibe
- Write clean code.
- Follow best practices.
- Keep things maintainable.

'Write clean code' can't be triggered — when does it apply? always, which means never — and can't be checked — clean by whose test? The agent can't act on it, so it's tokens spent to no effect on every call. The good lines each say when they fire and what makes them pass. The test: could a linter or a second agent verify this line? If not, it's decoration.

Grow the constitution from post-mortems, not from an upfront checklist. Every time an agent drifts, ask one thing: what standing rule, sitting in the prompt, would have stopped this? Add that line and only that line. A constitution written from real failures stays small, and every line has a scar behind it. One brainstormed upfront balloons to forty aspirational lines the agent quietly learns to ignore.

Spec: what and why — and the field that carries it

The spec owns what and why. The field that decides whether a spec is useful or decorative is non-goals / out-of-scope — and that's the counterintuitive part, because it's the field humans treat as optional boilerplate. It's load-bearing for agents for one reason: agents fail by doing MORE than asked, not less. The requirements tell the agent where to go. The non-goals are the only thing telling it where to stop.

Same requirements, two specs. Guess which one hands you the 40-file PR:

markdown
# Spec A
## Requirements
- Add rate limiting to POST /api/ask (10 req/min per IP).
## Acceptance
- 11th request in a minute returns 429.

# Spec B — identical requirements, one added fence
## Requirements
- Add rate limiting to POST /api/ask (10 req/min per IP).
## Acceptance
- 11th request in a minute returns 429.
## Non-goals / out-of-scope
- Do NOT add rate limiting to any other route.
- Do NOT refactor the existing route handler beyond what's needed.
- Do NOT touch auth, logging, or tests unrelated to rate limiting.
- 'Done' = this behavior works AND no other file changed.

Spec A gives you the 40-file PR. Not because it's wrong — because it's silent, and an agent reads silence as permission. Spec B fences the blast radius. The rule to internalize: for an agent, 'done' has to include 'and touched nothing else.' The leak to watch for on this floor is naming a database or a library. 'Store limits in Redis' is a plan decision. The second it lands in the spec, you've made a how-choice at the what-altitude — and made it without any of the design thinking that belongs one floor down.

Plan: how — where the cascade starts or stops

The plan owns tech choices and design — the how. Two leaks show up here. The obvious one is re-stating the what, copy-pasting requirements back in, which wastes tokens and invites the agent to re-interpret settled behavior. The dangerous one runs the other way: a tech choice that leaked UP into the spec now forces the plan into a corner.

Watch the cascade. Say the spec smuggled in 'store limits in Redis' as an offhand half-decision. Now the plan has two bad moves. Obey it, and you've committed to Redis with zero design analysis — maybe the app has no Redis and an in-memory map or the existing DB was the right call. Or contradict it, and now spec and plan disagree, so the agent has to guess which floor wins. It usually picks the more specific-sounding one, guesses wrong, and that wrong guess cascades down into tasks, which now sequence work against a datastore you never wanted. One leaked word upstairs, three floors of wrong.

Keep the choice on its own floor. Spec says 'requests over the limit are rejected.' Plan says 'enforce with an in-memory sliding-window counter keyed by IP; no new infra.' Now the plan actually made the call, in the one place equipped to weigh it.

Tasks: sequence — granularity is a context problem

Task granularity isn't a story-points question. It's a context-window and verification question. Too big — 'implement auth' — and the agent has to hold too much at once, thrashes, and hands back a sprawling diff you can't verify in one read. Too vague on the done-check and you can't tell if it actually worked. A task is agent-followable when it's three things at the same time:

  • One PR-sized diff — small enough that the agent holds the whole change in context and you review it in one sitting.
  • A single falsifiable done-check — one test or command that passes or fails, no 'looks done.'
  • A back-reference to the spec requirement it serves — so the chain from requirement to code never breaks.
markdown
# Thrash — too big, no exit test, no back-ref
- [ ] Implement authentication

# One-shottable
- [ ] Task 4: Add POST /api/login route returning a session cookie.
      Done when: `npm test tests/auth.test.js` passes.
      Depends on: Task 3 (session store).
      Serves: Spec req R2 (users can authenticate).

The second one an agent can pick up cold and finish in a single pass. It knows exactly where to start (after task 3), what to write (the login route), when it's done (the test goes green), and why (R2). No judgment call left dangling for it to fill with drift.

The chain-integrity check: grep for orphans

Requirements vanish silently during decomposition. You write eight spec requirements, the tasks cover six, and nobody notices the two that fell off until they're missing in production. Two greppable checks catch it:

  • Orphan requirement — a requirement in the spec with no task that back-refs it. That behavior simply won't get built.
  • Orphan task — a task with no spec back-ref. Either the spec is missing a requirement, or the task is unrequested scope the agent will happily expand.
bash
# Every spec requirement should appear as a 'Serves: R#' back-ref in tasks
grep -oE 'R[0-9]+' spec.md | sort -u > /tmp/reqs
grep -oE 'Serves: R[0-9]+' tasks.md | grep -oE 'R[0-9]+' | sort -u > /tmp/covered
comm -23 /tmp/reqs /tmp/covered   # orphan requirements — in spec, no task

# Any task with no 'Serves:' line is an orphan task
grep -E '^- \[ \] Task' tasks.md   # eyeball each for a Serves: back-ref

Cheap enough to run every single time, and it's the highest-leverage check in the whole workflow. It's the difference between 'the agent built something' and 'the agent built what the spec asked, and only that.'

The 10-minute audit you run tonight

Grab one real bundle you already have. Walk every field and ask it two questions: is this decision on the wrong floor? and is this human-shaped narrative an agent will read as scope? Then act:

  • Wrong floor → move it. DB name in the spec goes to the plan. Acceptance criteria in a task goes to the spec. Requirements re-argued in the plan get deleted — they already live upstairs.
  • Narrative → delete it. Any 'we should also...', 'it might be nice to...', context paragraph — cut it, or turn it into an explicit requirement or non-goal. Ambiguity is scope to an agent.
  • Constitution → strip every line that isn't trigger + testable constraint.
  • Spec → confirm the non-goals exist and 'done = touched nothing else' is written down.
  • Tasks → confirm each has one diff, one done-check, one back-ref. Grep for orphans.

Tomorrow builds straight on this. Once the artifacts are clean at every altitude, the next question is how you keep them clean while the agent edits the codebase underneath them — the docs drift out of sync with the code, and that's a failure mode of its own.

Flashcards
Check yourself

Extend your knowledge

  • Run the 10-minute audit on a real bundle tonight: walk every field, ask 'wrong floor? or human narrative?', move or delete. Note which artifact leaked most — that's your weak spot.
  • Read GitHub's Spec Kit templates (github/spec-kit) and re-read each field as a fence: what altitude does this field own, and what would leaking into it look like?
  • Study your own agent post-mortems: for the last three times an agent drifted, write the one trigger+testable constitution line that would have stopped each. That's how the constitution should grow.
  • Build the orphan-grep into a pre-commit or CI check so requirement/task chain breaks fail loudly instead of vanishing silently.
Test yourself on this lesson

Discussion

Chat with Chi Cong (AI) about this article. Your conversation is private to you — you can publish a summary for others when you're done.

Ask me anything about “Writing SDD Artifacts an Agent Can Follow” — trade-offs, decisions, or the story behind it.