- AI
- Workflow
- Engineering
Spec-Driven Development: Write the Spec Before the Prompt
AI coding agents are fast but drift without direction. Writing a short spec first turns them from guessers into implementers — here's the workflow and a template.
AI coding agents have changed how fast code gets written. They haven't changed how fast the right code gets written. Ask an agent to "add password reset" and you'll get something that compiles, looks reasonable, and quietly makes a dozen decisions you never made: how long the link lasts, whether old links still work, what happens when the email doesn't exist.
Spec-driven development fixes that by moving the decisions to the front. Before any code — yours or an agent's — you write a short spec that says what the feature must do, how you'll know it's done, and what's out of scope. Then the spec, not a one-line prompt, drives the implementation.
Why prompts aren't enough
A prompt is a request. A spec is an agreement. The difference shows up in three ways:
- Ambiguity gets resolved by guessing. Anything the prompt doesn't say, the model fills in with the most common pattern it has seen. Sometimes that's right. Often it's subtly wrong for your system.
- Context disappears. A chat thread loses its early decisions as it grows. A spec file stays in the repo and gets re-read every time.
- There's nothing to review against. Without written acceptance criteria, "does it work?" becomes "does it look like it works?"
None of this is new — it's the same reason teams wrote design docs before AI. What's changed is the cost of skipping it. An agent can produce a thousand lines in a minute, which means it can produce a thousand lines in the wrong direction in a minute.
The workflow
Spec-driven development is four steps, each producing a file you can read and review.
1. Specify: what and why
Describe the feature from the outside: who uses it, what they can do, and the rules it must follow. No implementation details yet. The most useful part is the acceptance criteria — concrete, testable statements.
2. Plan: how
Now decide the technical approach: data model changes, endpoints, libraries, and how it fits the existing architecture. This is where you point at real files and existing patterns so the implementation matches the codebase instead of inventing a new style.
3. Break it into tasks
Split the plan into small steps that can each be built and verified on their own: "add the password_resets collection and index", "add POST /auth/forgot-password", "add the email template". Small tasks keep an agent's changes reviewable, and they give you natural checkpoints.
4. Implement and verify
Hand the agent one task at a time, with the spec and plan as context. After each task, check the result against the acceptance criteria — ideally with tests written straight from them.
A spec template
Here's the shape I use. It fits on one screen, which is the point: a spec nobody reads is no better than no spec.
# Password reset
## Goal
Users who forget their password can set a new one by email,
without contacting support.
## Users and flow
1. User submits their email on /forgot-password.
2. They receive a link valid for 30 minutes.
3. The link opens /reset-password, where they set a new password.
4. All their existing sessions are signed out.
## Acceptance criteria
- The response to step 1 is identical whether or not the email exists.
- A reset link works once; reusing it shows "This link has expired."
- Requesting a new link invalidates any earlier unused link.
- Links older than 30 minutes are rejected.
- After a reset, existing refresh tokens for the user are revoked.
- Requests are limited to 5 per email per hour.
## Out of scope
- SMS or authenticator-app recovery.
- Changing the email address.
## Constraints
- Store only a hash of the reset token.
- Reuse the existing mailer in services/email.
- Follow the error format used by the other /auth endpoints.Notice what the acceptance criteria do: every one of them is a decision an agent would otherwise make on its own, and every one of them maps directly to a test.
Turning criteria into tests
Because the criteria are concrete, they translate almost line by line into tests — and you can ask the agent to write those tests first, before the implementation:
async def test_forgot_password_does_not_reveal_unknown_email(client):
known = await client.post("/auth/forgot-password", json={"email": "ana@example.com"})
unknown = await client.post("/auth/forgot-password", json={"email": "nobody@example.com"})
assert known.status_code == unknown.status_code == 202
assert known.json() == unknown.json()
async def test_reset_link_works_only_once(client, reset_token):
first = await client.post("/auth/reset-password", json={"token": reset_token, "password": "n3w-Passw0rd!"})
second = await client.post("/auth/reset-password", json={"token": reset_token, "password": "another-0ne!"})
assert first.status_code == 200
assert second.status_code == 400Now "done" has a definition the agent can check by itself: the tests pass. That changes the loop from generate, eyeball, re-prompt to generate, run tests, fix.
Keep the spec in the repo
Put specs next to the code — a specs/ folder works — and commit them with the feature. That gives you:
- Context for future changes. The next person (or agent) touching password reset can read why it works the way it does.
- Better reviews. A pull request that links its spec can be reviewed against intent, not just style.
- A living document. When requirements change, update the spec first, then the code. If they disagree, the spec is the bug report.
Tools are starting to build this workflow in. GitHub's Spec Kit and AWS's Kiro both structure agent work around spec, plan and task files. But you don't need a tool to start — a Markdown file and the discipline to write it first is enough.
When it's overkill
Not everything needs a spec. A typo fix, a CSS tweak, or a one-line bug fix with an obvious cause can go straight to code. A reasonable rule: if you'd have to explain the change to a teammate before they could build it, write the explanation down first. That explanation is the spec.
Takeaways
- AI agents make code cheap; deciding what to build is still the expensive part.
- Write the spec before the prompt: goal, flow, acceptance criteria, out of scope, constraints.
- Plan the approach against the real codebase, then split it into small, verifiable tasks.
- Turn acceptance criteria into tests so "done" is something the agent can check.
- Keep specs in the repo and update them before the code.
The agent writes the code. The spec makes sure it's the code you meant.