A practical guide to writing specs that turn vague AI prompts into production-quality code — from functional requirements to edge cases, with real before-and-after examples.
Let me paint a picture you've probably lived.
You open Claude Code or OpenCode, type a vague prompt like "add a user dashboard with analytics," and hit enter. The agent spins up, writes a bunch of code — it even looks decent at first glance. Then you realize: the data model is wrong, the API endpoints don't match your existing patterns, there's no error handling, and the "analytics" is just a row of four hardcoded numbers.
You spend the next hour correcting, prompting, correcting again. You would have been faster writing it yourself.
Now imagine a different scenario. You spend 15 minutes writing a structured specification, paste it into the agent, and it produces exactly what you wanted — following your conventions, handling edge cases, wired into your existing auth and data layer. One shot.
That's not luck. That's the difference between treating your AI coding agent like a chatbot and treating it like a senior engineer who needs a clear design document — also known as spec-driven development.
The Core Problem: AI Agents Are Powerful but Undirected
Claude Code, OpenCode, Cursor Agent — these tools are incredible when pointed at a well-defined task. They can read your entire codebase, understand your conventions, and produce production-quality code. But they share a fundamental limitation: they don't know what you want unless you tell them, precisely and completely.
When you give an agent a one-liner prompt, you're leaving an enormous amount of ambiguity. The model will fill in the gaps — but it fills them with its own assumptions, which are often generic, incomplete, or just wrong for your context.
A spec closes those gaps. It transforms an open-ended creative writing exercise into a constrained engineering task.
What Makes a Spec Actually Work for AI Coding Agents?
A spec that works for an AI agent is different from a traditional software specification. It doesn't need to be a 20-page document with UML diagrams. It needs to be concise, unambiguous, and machine-actionable.
Here's what a good agent spec includes:
1. The "What" — Clear Functional Requirements
Don't describe the solution. Describe the desired behavior.
❌ Vague:
Add a login form.
✅ Spec-driven:
Add a login form that:
- Accepts email and password
- Calls
POST /api/auth/login- On success, stores the JWT from the response in
localStorageunderauth_tokenand redirects to/dashboard- On 401, shows a red inline error "Invalid email or password" above the submit button
- Disables the submit button and shows a spinner while the request is in flight
- Validates that email is non-empty and looks like an email before submitting
The second version removes every decision the model would otherwise have to guess.
2. The "Where" — File and Component Structure
Tell the agent exactly where things go. AI agents can search a codebase, but they can't read your mind about your preferred architecture.
Create the following files:
frontend/src/pages/Dashboard/DashboardPage.tsx— the page componentfrontend/src/pages/Dashboard/DashboardStats.tsx— the stats cards rowfrontend/src/pages/Dashboard/useDashboardData.ts— custom hook for data fetchingfrontend/src/pages/Dashboard/types.ts— TypeScript interfaces
This alone can save multiple rounds of "no, put it over here" corrections.
3. The "How" — Technical Constraints and Patterns
- Use React Query (
useQuery) for data fetching, notuseEffect+fetch- Follow the existing error handling pattern in
frontend/src/hooks/useApi.ts- Use the shared
Buttoncomponent fromfrontend/src/components/ui/Button.tsx- Colors: use CSS variables (
var(--color-primary),var(--color-error))- Add a loading skeleton, not a standalone spinner
These constraints are what turn generic code into code that looks like your team wrote it.
4. The "Edge Cases" — What Happens When Things Go Wrong
AI agents are naturally optimistic. They code the happy path brilliantly. The unhappy path — not so much, unless you tell them about it.
Edge cases to handle:
- Dashboard data is still loading → show skeleton placeholders
- API returns an error → show error toast with retry button
- User has zero data (new account) → show empty state with CTA
- API response is slow (>3s) → show the skeleton, don't unmount it early
- Browser tab regains focus after >5 minutes → silently refetch data
A single paragraph like this can prevent half a dozen follow-up prompts.
5. The "Tests" — Expected Behaviors to Verify
Tests to write:
DashboardPagerenders stat cards when API returns dataDashboardPageshows skeleton while loadingDashboardPageshows error toast + retry button on API failureDashboardPageshows empty state when user has no datauseDashboardDatarefetches on window focus after 5+ minutes of inactivity
Why This Actually Makes the Agent "Smarter"
It's not magic. Here's what's happening when you provide a spec:
Reduced ambiguity → fewer hallucinations. The model doesn't have to invent database schemas, API contracts, or error messages. You've provided them. Every decision you make upfront is a decision the model can't get wrong.
Constrained search space. Modern coding agents work by searching, reading, and reasoning about your codebase. A spec acts as a map — it tells the agent which files to read, which patterns to follow, and which components to reuse. Without it, the agent wastes context window scanning irrelevant code.
Better context utilization. AI agents have limited context windows. When you spend 2,000 tokens on a precise spec, you save 20,000 tokens of back-and-forth corrections. That's a 10x return on the most precious resource in AI-assisted development.
Deterministic output. With a spec, two runs of the same prompt produce similar results. Without one, they can diverge wildly. This matters when you're working on a team — you want predictable, reviewable output.
A Real Before-and-After
Before (vague prompt):
Add RBAC middleware to the API.
Result: The agent invents its own role model, introduces a new middleware pattern inconsistent with the existing codebase, and misses that the project already has an internal/server/rbac/ package.
After (spec-driven prompt):
Add a new RBAC middleware
RequirePermissiontointernal/middleware/rbac.gothat:
- Wraps an
http.Handlerand checks the caller has the required permission- Reads the authenticated user's roles from
internal/apis.Session.Roles- Accepts a permission string via a functional option:
RequirePermission("org:write")- Looks up the permission-to-role mapping from the existing
internal/server/rbac.Controller- Returns 403 with
{"error": "insufficient_permissions"}when unauthorized- Follows the middleware chaining pattern in
internal/middleware/auth.go- Must be covered by table-driven tests in
internal/middleware/rbac_test.gotesting: authorized, unauthorized, unauthenticated, and admin-override cases
Result: The agent reads the existing RBAC package, middleware patterns, and test conventions, then produces middleware that slots in perfectly — passing tests on the first run.
The difference is stark: 15 minutes of spec writing saves an hour of prompt wrestling.
How to Start Writing Better Specs Today
Write the spec before you open the agent. Force yourself to think through what you actually want. If you can't describe it, the agent can't build it.
Use a template. Create a reusable template with sections: Purpose, Files to Create/Modify, Functional Requirements, Technical Constraints, Edge Cases, Tests. Fill it out in 10–15 minutes.
Be ruthlessly specific. Every time you catch yourself writing a vague word like "handle errors," replace it with exactly what that means: which errors, what message, what UI state.
Reference existing code. Tell the agent which files to use as examples. "Follow the pattern in X" is one of the most powerful lines you can write in a spec.
Iterate the spec, not the code. When the output is wrong, ask yourself: did I specify this? If not, update the spec and re-run. You're building a reusable asset, not just fixing a one-off bug.
The Bigger Picture
We're entering an era where the bottleneck in software development is shifting from writing code to specifying intent. The engineers who thrive will be the ones who can articulate precisely what they want — not the ones who type the fastest.
AI coding agents are force multipliers. But like any multiplier, they amplify what you put in. Feed them ambiguity, and they'll generate a mess — just faster. Feed them a precise spec, and they'll generate production code — in minutes, not days.
The spec isn't overhead. It's leverage.
If you found this useful, follow me for more on AI-assisted development, engineering practices, and building with Claude Code and OpenCode.
Top comments (0)