Spec-driven development gives an AI coding agent a reviewed behavior contract before implementation and links each requirement to acceptance evidence. The specification should describe outcomes, constraints and failure cases. It should not merely repeat the implementation the agent happens to generate.
Key takeaways
- Use stable requirement identifiers and observable outcomes.
- Separate business behavior from implementation preferences.
- Keep unresolved decisions visible rather than letting the agent invent them.
- Trace requirements to tests, code changes and review decisions.
- A complete traceability table does not prove correct behavior; the tests still need review and execution.
Specify the outcome and its boundaries
A useful specification answers who can do what, on which resource, under which conditions and with what result. Include invalid and conflicting cases. “Build a secure endpoint” leaves important decisions unstated; “a user from another tenant cannot retrieve this record” is an observable requirement.
Distinguish three kinds of information: required behavior, technical constraints and open questions. For example, preserving a public API is a constraint; deciding whether a missing resource returns one business error or another may be an unresolved product decision.
| Artifact | Useful content | Weak substitute |
|---|---|---|
| Behavior requirement | Observable actor/action/outcome | “Make it work well” |
| Constraint | Compatibility, data and execution boundaries | Unstated assumptions |
| Acceptance scenario | Initial state, action and expected result | “Tests should pass” |
| Decision log | Owner, options and resolved choice | Agent guesses hidden policy |
| Traceability record | Requirement to test and review evidence | A checkbox with no supporting result |
Keep the specification concise enough to review. A large generated document full of repeated claims can obscure the actual contract. Prefer a small authoritative record with links to relevant architecture and examples.
A worked support-request specification
Consider a fictional tenant-scoped support-request API. The task is to allow a user to read a request they are permitted to access. The examples below use fixture identities and records, not production data.
Purpose: retrieve a support request through the existing API
R1: An authenticated user may read an authorized request in their tenant.
R2: A user must not retrieve another tenant's request by guessing its ID.
R3: An unauthenticated caller receives the defined authentication failure.
R4: A missing request produces the approved not-found behavior.
R5: The response excludes internal-only diagnostic fields.
Constraints:
preserve the current public response contract except approved additions
use existing identity and repository abstractions
do not add external services or production credentials
Open decision:
confirm the approved disclosure policy for forbidden versus missing IDsR2 and R4 need a coordinated decision about information disclosure. The agent should not silently choose a policy because one HTTP status is easier to implement. The product and security owners should settle the intended external behavior.
R5 needs an explicit response-field allowlist or equivalent reviewed contract. “Do not leak sensitive fields” is too vague if nobody has named which fields belong in the response.
This is a teaching example, not a complete authentication design. Real applications may have role, ownership, collaboration and resource-specific rules beyond tenant membership.
Map requirements to independent acceptance cases
| Requirement | Given / when | Expected evidence |
|---|---|---|
| R1 | Authorized user retrieves an allowed fixture request | Approved response fields and successful result |
| R2 | User requests another tenant’s fixture identifier | Approved denied/disclosure behavior; no record content |
| R3 | No valid caller authentication | Defined authentication failure |
| R4 | Allowed user requests a nonexistent identifier | Approved not-found behavior |
| R5 | Internal-only fields exist on the stored fixture | Fields absent from the response contract |
Keep the acceptance cases owned by the evaluator. The assistant may propose additional cases, but should not rewrite expected outcomes merely to make its implementation pass.
Map each requirement to the relevant test identifier and result, then to the code/review artifact. One test can support several requirements, and a requirement can need several tests. The map should describe evidence rather than force an artificial one-to-one relationship.
Include regression cases for bugs discovered during review. If R2 fails through an alternate route, add that route to the acceptance set and correct the implementation. Do not simply update the specification to bless the defect.
A controlled spec-to-code workflow
Draft requirements + constraints + open decisions
↓ owner review
Approved behavior and acceptance scenarios
↓
Agent plan → focused implementation → reported checks
↓ independent verification
Requirement-to-test-to-change evidence
↓
Review decision + controlled release
↓
Observed defect → revised decision/test where justified- Review the specification: resolve material ambiguities before implementation.
- Constrain the task: provide repository context, allowed tools and compatibility boundaries.
- Ask for a plan: identify the expected modules, tests and remaining assumptions.
- Implement in small changes: inspect the diff and avoid unrelated rewrites.
- Run independent checks: preserve expected behavior and record results truthfully.
- Review the trace: identify uncovered requirements and unsupported completion claims.
- Release normally: keep approvals, recovery and operational ownership.
The AI-assisted SDLC playbook places this workflow in the larger delivery process. The NIST SSDF provides broader secure-development context for requirements, verification and vulnerability response.
A prompt can describe allowed scope, but execution controls should reinforce it. Keep secrets, external systems and production access constrained independently of the specification text.
A small traceability-artifact validator
We ran the following Python fixture against five cases: a valid map, duplicate requirement identifiers, missing coverage, unknown identifiers and an empty expected outcome. All five cases behaved as expected. The fixture checks document consistency only; it does not execute the application or prove that a test meaningfully verifies a requirement.
def validate(requirements,tests):
ids=[r['id'] for r in requirements]
if len(ids)!=len(set(ids)):raise ValueError('duplicate requirement')
referenced={r for t in tests for r in t['requirements']}
if referenced-set(ids):raise ValueError('unknown requirement')
if set(ids)-referenced:raise ValueError('uncovered requirement')
if any(not t['expected'].strip() for t in tests):raise ValueError('empty expected outcome')
return True
For example, a requirement can be referenced by a test whose assertion is wrong. This validator would not detect that. Similarly, an “expected” string can be nonempty yet meaningless. Keep human review and actual test execution as separate gates.
In a larger implementation, store stable test identifiers, result links and the repository commit. Version the specification and test map together where practical. Do not make the traceability tool another source of silent policy decisions.
Common spec-driven development failure modes
The specification describes code rather than behavior
If the spec only says which function to call, it may not explain what users should observe. Add actor, condition and outcome. Keep implementation constraints when needed, but do not confuse them with acceptance.
The agent rewrites tests to match the bug
Review changed assertions and expected values. Keep independent cases. Require a justified requirement change before accepting a new outcome.
Important negative cases are absent
Add denied access, invalid input and dependency failure scenarios appropriate to the task. Happy-path coverage alone leaves the boundary undefined.
The traceability table looks complete but has no evidence
Record which checks actually ran and the result. Distinguish a planned test from an executed test. A link to a test file is not proof of a passing run.
The specification becomes stale
Update it through the same reviewed change process as the implementation. When behavior changes, revise the requirement, scenarios and operational documentation deliberately.
For security review of generated changes, see the AI-generated code security checklist.
Common questions
How does spec-driven development improve AI-generated code quality?
It makes intended behavior and boundaries explicit, then connects implementation to independent acceptance evidence. It reduces ambiguity but does not guarantee correctness.
Should a specification contain implementation details?
Include necessary technical constraints, but separate them from observable business behavior and unresolved decisions.
Can an AI agent write the specification?
It can draft and critique it. Accountable owners must resolve policy decisions and approve the intended behavior.
Does requirement coverage prove the software works?
No. A referenced test can be weak or wrong. Review test quality and execute meaningful independent cases.
How should requirement changes be handled?
Version and review the requirement, acceptance cases and implementation together, recording the reason and decision owner.
Give coding agents a contract you can verify
Bring the feature, current constraints and examples of ambiguous requirements. Discuss AI-assisted engineering adoption with a practical specification and traceability workflow.
