Spec-driven development with coding agents: a practical guide
What spec-driven development is, when a change needs it, and how to take one from requirements and open questions to a plan, a handoff and evidence you can check.
- Author
- The ArchDocs team
- Published
- Reading
- 11 min read
You ask a coding agent to remove a field from an API response. It finds the consumers, opens ten pull requests and reports success. Three days later a nightly job fails, because it read that field through a path nobody listed. The agent did what it was asked. Nobody wrote down what had to stay true while it worked, so nobody could check.
Spec-driven development is the habit of writing that down first. You describe what the change must achieve and what it must not break, turn that into a plan, and then let a person or an agent implement the plan while keeping a record of where reality disagreed with it. When the work is done, you check the result against the spec, not against the agent's summary of what it did.
This guide covers when that is worth doing, how a change moves through it, and what each artifact proves. It uses ArchDocs in a few places to show a concrete step, but every part works with markdown files in a repository.
What a spec is, and what it is not
A spec states the problem, the goals, the constraints and the decisions for one change. It answers "what must be true when this is done?" in terms someone can check. GitHub's Spec Kit puts it as "Define what to build and why, then turn the specification into a plan, tasks, and working code." Kiro's specs split the same idea into a requirements file, a design file and a tasks file. The names differ. The order is the same: intent, then plan, then code.
A spec is the thing you compare the code against, and it can't prove the code is correct. A vague spec gives you nothing to compare against, and an agent will fill each gap with a reasonable guess that nobody reviewed.
When a change needs one
Write a spec when at least one of these is true:
- The change crosses a boundary, such as two services, a public API, a schema or a queue between teams.
- Someone else has to agree with the approach before work starts.
- The work will take more than one pull request or more than one agent session.
- It adds a dependency or a data store, or starts handling data the system didn't handle before.
Skip it for a change inside one component that changes no interface, a dependency bump, or a copy or config edit. For a bug fix where the intended behavior is already documented, a short plan with the failing case and the check that proves the fix is usually enough. The ceremony should match the cost of getting it wrong.
A worked change
The rest of this guide follows one fictional change. The Accounts API returns the customer on GET /accounts/{id} as a single string, customer_name. Billing and support need the id, display name and legal name separately, and some services split the string themselves. The change replaces the string with a customer object.
Start from requirements and open questions
The first draft of the spec names the problem and what has to be true at the end:
- Accounts API returns a
customerobject withid,displayNameandlegalName. - No consumer breaks during the migration.
customer_nameis gone when the migration ends.
Then it lists what isn't known yet, as questions with an owner instead of guesses buried in prose:
- When is it safe to remove
customer_name? Owner: the API's tech lead. - How do we know we found every consumer?
The second question is the one that comes back later. The first draft answers it with "30 days of gateway access logs for that endpoint", and records the risk next to it: a job that calls the API through another path, or less often than monthly, would be missing from the list. Writing the risk down costs one sentence. It is also the sentence an agent will need when the list turns out to be wrong.
A decision that will outlive this change belongs in an architecture decision record, not in the spec. "Remove a response field only after its consumers have moved" is a rule for every future migration, so it gets its own ADR and the spec links to it. The ADR guide covers when a decision deserves one.
Turn the spec into a plan
The implementation plan says how the work will happen and how each part will be checked:
- Return both fields from Accounts API.
- Move each of the ten consumers to
customer, one pull request each, each with a contract test that readscustomerfrom a response withoutcustomer_name. - Remove
customer_nameonce every consumer has evidence that it moved.
Each step should be small enough that an agent can finish it in one session and a reviewer can read it in one sitting. The plan also lists what it expects to touch, here the Accounts API and ten named services, so you can compare that list with what actually changed.
Acceptance criteria go in the plan too, written as checks rather than intentions. "Consumers are migrated" is an intention. "Each consumer's contract test passes against a response without customer_name" is a check. So is "no new production errors mention customer_name for 72 hours after removal", if you have an error tracker that can answer it.
The design doc example shows this spec and plan in full, along with a blank template you can copy.
Hand the plan to an agent
Give the agent the spec and the plan, not a one-line request. With the plan in front of it, the agent's job changes from "figure out what to do" to "do step 2 for Billing, and tell me if anything contradicts the plan."
That last instruction is the part people leave out. An agent that only knows the task will work around a surprise and keep going. An agent that knows the plan can say "this contradicts step 3" and stop.
When the agent finds a wrong assumption
Partway through, the agent working on the fifth consumer finds invoice-worker, a nightly job that reads customer_name in its export. It calls the Accounts API from a batch subnet that bypasses the gateway, so it was never in the log-based list. Removing the field would break the invoice export.
There are three wrong ways to handle this. The agent can patch invoice-worker on its own and keep going, so the plan no longer describes the work. It can stop with an error message in a chat that nobody else will read. Or it can finish its own step and leave the finding in its session history, where the next agent can't see it.
The useful version has three parts:
- The agent records a deviation on the plan: what it found, where (
jobs/export_invoices.py), and what it would break (the nightly export). - It raises a question with an owner: who moves
invoice-worker, and does the plan need a new step? - It does not remove anything. The removal step depends on a list that is now known to be incomplete.
A person then decides. Here the decision is to add invoice-worker to the plan with its own pull request and contract test, and to add an acceptance criterion that the nightly export completes using customer. The spec gets a new version that names the extra consumer and says why it was added. The original version stays, so anyone can see what was intended before the surprise and what changed.
Departing from the plan is normal. Departing without a record is what makes the final review meaningless, because the reviewer is comparing the code against a plan that no longer describes it.
Keeping the next session informed
Agent sessions end. The next one starts with no memory of the last, and so does the next engineer who picks up the work. Two habits keep them from repeating what was already learned.
The first is a work log on the change itself: progress, decisions, deviations and test results, recorded as they happen, each with who recorded it. The second is a handoff whenever someone stops partway: what is done, what is blocked, and what comes next, with references to the log entries behind each part.
In the example, the agent that found invoice-worker leaves a handoff that says the removal is blocked on the new question. A different agent picks up the next consumer the following day. It reads the handoff before it plans, sees that customer_name must stay, and moves invoice-worker to customer in the same session. The handoff example shows that log.
In ArchDocs, each design doc has a Work Log for this, and agents connected over MCP can ask for the task context of the files they are about to change. On Team and above, that returns the open questions, decisions and constraints recorded against those paths through code locations, including ones recorded on other design docs. A folder of markdown files and a convention to read them first gets you part of the way. What it doesn't give you, once files are mapped to the records about them, is a way for the agent to find what a different change learned about the same files.
Acceptance criteria, evidence and human review
When the work is done, the question is whether the change did what the spec said. Answer it criterion by criterion, and for each one say where the answer came from.
| Criterion | Evidence | Who or what produced it |
|---|---|---|
Orders reads customer | orders-customer-contract passed on the pull request | CI |
customer_name stayed in the response during the migration | Agent's report, reviewed by the tech lead | Agent, then a person |
Nightly export completes using customer | An export event from the fixed release with the new field | Product analytics |
No new errors mention customer_name for 72 hours | Zero matching error issues in the window | Error tracker |
Then a person reviews the evidence and decides whether the change shipped. That review is short when the evidence is specific, and impossible when the only evidence is "the agent said it's done".
An AI review of the design is useful earlier, before the plan runs, as a second reader that checks the spec against the code. ArchDocs' AI review, on Team and above, reads the design doc, the organization's shared docs and the code in linked repositories, and records findings such as a premise the code contradicts. Treat its findings the way you would treat a colleague's review comments: as input a person weighs. It never approves the doc.
What each artifact establishes
- A written spec establishes what the team intended. It doesn't establish that the intent was right or that the code follows it.
- An agent's report establishes what the agent says it did. An agent can report work it didn't finish or a test it didn't run, so treat a report as a claim to check.
- A merged pull request establishes that someone approved the diff and that it reached the branch. It doesn't establish that the diff covers the whole change or that it was deployed.
- A passing check establishes that the check passed. On GitHub, a required status check also lets a merge through when it reports "skipped" or "neutral", so read what the check actually ran. A test that doesn't exercise the risky path passes every time.
None of these replaces the others, and none replaces a person deciding the change is done. Spec-driven development makes that decision cheaper by giving it something specific to compare against.
Where ArchDocs fits
ArchDocs is not a coding agent. You keep using Claude Code, Codex or whichever agent you use, and you keep your Git workflow and CI. ArchDocs holds the record those tools work from and write back to: the design doc with its spec and plan, the questions and decisions, the Work Log, the handoffs, the acceptance criteria and the evidence. Agents read and write it over MCP. People review it in the app.
You can also do this with markdown files in the repository. Spec Kit and Kiro both write their specs as files, and ArchDocs can import a Spec Kit plan.md or a Kiro tasks.md as a design doc's implementation plan when you want the rest of the record around it. The parts that are hard to do with files alone are the ones that span changes: seeing what last month's migration learned when you touch the same code, comparing what a plan expected to touch with what was recorded, and, on Business and Enterprise, checking criteria against GitHub and connected sources such as PostHog.
If you want to start with the document, How to write a software design document walks through each section with examples, and the design doc template is free to copy. For a real change recorded this way, including what went wrong, read how we use design docs to build ArchDocs with coding agents.
More in Coding agents, context and verification
Start