A spec (or lightweight PRD) is the bridge between "I want a thing" and "the agent built the right thing." AgentsAgent systemsAI systems that take actions, use tools, and complete multi-step tasks by reasoning through a sequence of decisions. are literal and fast: a vague ask produces a confident, wrong answer, while a scoped spec produces reviewable, correct work. Writing one takes a few minutes and saves hours.
Ambiguous ask vs buildable spec
Compare:
"Add search to the app."
against:
"Add a search box to the top of
/practice. It filters the visible problem list by title and tag as the user types (debounced 200ms), case-insensitive, client-side only. Empty query shows all problems. No results shows a 'No matches' state. Do not touch the API."
The second is buildable. Someone (human or agent) could implement it without guessing.
What every spec needs
- Goal: one or two sentences on what and why.
- Scope: what is in, and explicitly what is out. The "out" list keeps the agent from wandering.
- Behavior: concrete inputs and expected outputs, including empty, error, and loading states.
- Constraints: files or systems it may and may not touch, patterns to follow, libraries to use or avoid.
- Acceptance criteria: how you will know it works, ideally phrased as testable statements.
A worked example
Here is a spec small enough to review in one sitting, in the format you would drop into docs/specs/:
# Spec: Export results to CSV
Goal: let a signed-in user download their own submission
history as a CSV, so they can analyze it outside the app.
Scope
- In: a "Export CSV" button on /profile/[user_id], own profile only.
- Out: PDF export, other users' data, scheduled/email export.
Behavior
- Columns: date, problem_slug, score, dimension breakdown (4 cols).
- Empty history: button disabled with tooltip "No submissions yet".
- Large history (>5k rows): stream, do not buffer whole file in memory.
Constraints
- Reuse getSubmissions() from @/lib/data. Do not add a new table.
- Auth: reject if requested user_id != current user (403).
Acceptance criteria
- Downloading with 3 submissions yields a 3-row CSV plus header.
- Requesting another user's export returns 403.
- Header row matches the column list above exactly.
Notice how each acceptance criterion is something you can literally check.
Let the agent draft it
You do not have to write the spec alone. This is the discover phase from the plan-first workflow. Give the agent the rough ask and have it interview you:
"I want to let users export their results. Ask me clarifying questions until you have enough to write a short spec, then write it."
The agent will surface decisions you had not made (which format, which fields, permissions, size limits). Answer them, and you end up with a spec you both understand.
Scoping: smaller is safer
A good spec is small enough to review in one sitting. If it describes a week of work, break it into milestones and spec the first one only. Large specs lead to large diffs, and large diffs get rubber-stamped instead of reviewed. Ship a thin vertical slice, verify it, then spec the next.
Keep the spec in the repo
Save the spec as a markdown file (for example docs/specs/export.md) and point the agent at it: "Implement docs/specs/export.md, step by step." This gives the agent a stable reference it can reread, keeps intent version-controlled, and makes it obvious later why the code looks the way it does.
Anti-patterns to avoid
- The kitchen sink: ten features in one spec. Split it.
- Solution smuggling: specifying implementation details ("use a red-black tree") when you mean behavior ("lookups must stay fast at 10k items"). State outcomes and let the agent choose the how, unless the how genuinely matters.
- No acceptance criteria: without them, "done" is a matter of opinion, and the agent will declare victory early.
- Silent scope creep: the agent "helpfully" refactors nearby code. An explicit "out of scope" list plus "do not change files outside X" prevents it.
Rules of thumb
- If you cannot write the acceptance criteria, you do not understand the task well enough to build it yet.
- Every spec gets an explicit out-of-scope list.
- One reviewable slice per spec; milestone the rest.
- Prefer behavior ("fast at 10k items") over implementation ("use a cache") unless the implementation is the actual requirement.
A spec is not bureaucracy. It is the cheapest place to be wrong, because editing a sentence is free and editing a thousand lines is not.