AI Engineering Software Development System Design Best Practices

Spec-Driven Development with AI: From Vibe Coding to Reliable Engineering

Why spec-driven development is the antidote to AI-generated technical debt. Learn the 3 levels of SDD rigor and how to write specs that AI coding agents can actually execute.

AS
Aryan Singh

What Is Spec-Driven Development and Why Does It Matter?

Spec-driven development (SDD) flips the typical AI coding workflow. Instead of asking an AI to write code and hoping it matches your intent, you write the intent first — in a structured, unambiguous spec — and the AI implements against it. The spec defines scope, constraints, acceptance criteria, and edge cases before any code is generated.

This matters because AI coding assistants are fast but context-hungry. A scattered chain of prompts, also called “vibe coding,” produces code that drifts from the original goal. Each new prompt risks reintroducing assumptions, breaking constraints, and creating technical debt. SDD forces the hard thinking up front and gives the AI a stable target to aim at.


Why Vibe Coding Fails at Scale

Vibe coding works for prototypes and one-off scripts. It fails when:

  • The feature spans multiple files or modules
  • Multiple engineers review or iterate on the output
  • The code must follow existing architecture, security, or style rules
  • The feature needs to be maintained over months

The problem is context decay. The AI does not remember your entire codebase unless you feed it every time. Each prompt is a fresh conversation with partial context. Without a spec, the AI optimizes for the immediate request, not the long-term health of the system.


The Three Levels of SDD Rigor

SDD is not one size fits all. Teams operate at three levels depending on risk, compliance needs, and tooling maturity.

Level 1: Spec-First

You write a lightweight requirements document before generating code. The spec is a planning artifact. Once the feature is implemented, the spec may be archived or left behind.

Best for: Internal tools, prototypes, low-risk features.

Tradeoff: Better than no spec, but the spec does not protect against future drift.

Level 2: Spec-Anchored

The spec is a living document. It evolves with the code and is used for maintenance, debugging, and future iterations. The AI references the spec every time it touches the feature.

Best for: Production systems, team projects, features with ongoing maintenance.

Tradeoff: Requires discipline to keep the spec and code in sync.

Level 3: Spec-as-Source

The spec is the sole source of truth. Humans edit only the spec. Code, tests, and binaries are generated byproducts. This is the most radical form and requires the most mature tooling.

Best for: High-compliance environments, generated systems, or teams building AI-native products.

Tradeoff: Full adoption is hard and not appropriate for every codebase.

LevelHuman EditsSpec LifetimeBest For
Spec-FirstSpec + codeUntil implementationPrototypes, low-risk
Spec-AnchoredSpec + codeOngoingProduction systems
Spec-as-SourceSpec onlyPermanentHigh-compliance, AI-native

Spec-First vs. Spec-Once

Most teams think they are doing spec-first development when they are actually doing spec-once. They write a detailed spec at the start of the project, then abandon it once implementation begins. The spec launches the work but does not guide it.

This is the easiest trap to fall into with AI coding tools. The spec helps you define the first prompt. Then the excitement of generation takes over. The AI starts writing code, the project grows, and the original document gathers dust.

Real spec-driven development is a continuous feedback loop. The spec is not a launch checklist. It is the steering document. You revisit it when you discover edge cases, when you change architecture, and when you hand the work to another agent or teammate. The value is not the initial drafting. The value is the discipline of forcing yourself to think about requirements before acting on them.


How to Write a Spec That AI Can Execute

A good SDD spec is not a prose document. It is a structured contract with explicit sections. The AI should be able to read it and know exactly what to build, what to avoid, and how to verify success.

Required Sections

  1. Overview — One sentence describing the feature and the user outcome.
  2. Goals — What the feature must achieve.
  3. Non-Goals — What is explicitly out of scope.
  4. Constraints — Architecture, security, performance, or style rules that must hold.
  5. Acceptance Criteria — Verifiable conditions for completion.
  6. Edge Cases — How to handle error states, empty inputs, and limits.
  7. Dependencies — APIs, services, or files the feature interacts with.
  8. Verification — How the implementation will be tested or reviewed.

Example Spec for a Rate Limiter

# Rate Limiter Spec

## Overview
Implement a token-bucket rate limiter that protects the public API from abuse.

## Goals
- Enforce per-user request limits.
- Return clear 429 responses with Retry-After headers.
- Support both in-memory and Redis backends.

## Non-Goals
- Global rate limiting across all users.
- Billing or metering integration.

## Constraints
- Must use the existing Redis client in `lib/redis.ts`.
- Must not add more than 5ms of latency per request.
- Must follow the TypeScript style in `src/middleware/`.

## Acceptance Criteria
- [ ] 1000 requests per minute per user are allowed.
- [ ] 1001st request returns 429 with a Retry-After header.
- [ ] Redis backend is configurable via env var.

## Edge Cases
- Missing user ID falls back to IP-based limiting.
- Redis unreachable degrades to in-memory mode.

## Dependencies
- `lib/redis.ts`
- `src/middleware/auth.ts`
- `src/types/api.ts`

## Verification
- Unit tests for bucket logic.
- Integration tests for middleware behavior.
- Load test to verify latency under 5ms.

This format is not just readable by humans. It is parseable by AI. Each section gives the model a clear signal about what to do and what to protect.


Planning and Drafting the Specification

For a multi-part project, a single spec is not enough. You need a project plan that breaks work into sub-projects, phases, and testable stacks. This is especially important for infrastructure work or anything that crosses service boundaries.

A Practical Planning Pattern

  1. Research phase — Read the relevant documentation and point the AI at it. In Claude Code, this means using the documentation or MCP tools to ingest the same sources you read.
  2. Stack decomposition — Split the project into independently deployable stacks. Each stack becomes a phase. This makes testing and iteration much faster.
  3. Spec drafting — Write the spec for each phase. Each spec should include inputs, outputs, dependencies, and verification steps.
  4. Review — Read the spec before generating code. Check that the design, architecture, and sequence match your intent.

Example: Multi-Stack Agentic Gateway

Suppose you are building an AI gateway that uses a request interceptor, an MCP server, and an OAuth credential provider. A naive approach would be one stack. A better approach is three phases:

PhaseStackWhat It BuildsWhy Separate It
1InterceptorLambda function that modifies incoming requestsCan be tested locally with SAM before the rest exists
2MCP serverServer deployed on the runtimeTested locally, then deployed and verified
3GatewayGateway resource, target, credential providerEnd-to-end integration tested last

Each phase has its own spec. The AI implements one phase at a time, tests it, and only then moves to the next. This prevents the common failure mode where everything is generated at once and nothing works.


How to Use the Spec with AI Agents

Once the spec is written, the workflow is straightforward:

Step 1: Define the Constitution

Create a codebase-level constitution that the AI must follow in every task. This includes security rules, preferred frameworks, testing standards, and style conventions. The constitution is the foundation. The spec is the blueprint.

Constitution:
- Use TypeScript strict mode.
- Never commit secrets or credentials.
- Prefer explicit types over `any`.
- Write tests for all public functions.
- Use the existing error handling pattern in `src/errors/`.

Step 2: Draft the Spec

Write the spec as a Markdown file in the project repository. Keep it in a specs/ directory or next to the relevant code. Use the format above.

Step 3: Feed the Spec to the AI

Use a modern AI coding assistant or development kit that supports multi-agent workflows. Pass the constitution, the spec, and the relevant codebase context. Ask the AI to implement the spec, generate tests, and verify against the acceptance criteria.

Step 4: Verify the Output

Review the generated code against the spec. Do not accept the code because it compiles. Accept it because it satisfies the acceptance criteria and respects the constraints.


Working with Claude Code in Practice

Most of the discussion around SDD is abstract. The real friction appears when you sit down with an AI coding assistant and start building. Here are practical observations from using Claude Code on multi-stack infrastructure projects.

Context Windows Still Matter

Claude Code has a 200k token context window on standard Pro plans. That sounds large until you feed it documentation, multiple files, conversation history, and tool outputs. On heavy projects, especially with long AWS CloudFormation templates or multiple API docs, you can hit that limit.

When the limit is reached, Claude Code compacts the conversation history. That process can take 3–5 minutes on the low end and 10–12 minutes on complex sessions. The simplest way to avoid this is to keep the spec outside the chat and feed it back in chunks. Better yet, break the project into the small phases described above so no single session grows too large.

Model Choice Affects Usage Limits

Heavier models like Claude Opus hit usage limits much faster than Sonnet. On a Pro plan, heavy Opus use can exhaust the conversation budget in 45 minutes to an hour. Sonnet can run for several hours without hitting the same ceiling. For spec-driven work, where most of the value is in planning and structured execution, Sonnet is often sufficient. Save Opus for the hardest debugging or architectural decisions.

Use Selectable Clarifying Questions

Instead of asking the AI open-ended questions, instruct it to present a menu of options. This turns back-and-forth clarification into a single selection. For example:

Before starting, ask any clarifying questions you need.
Present each question as a numbered menu with 2–4 options.
Include "Other" as the last option and let me type a custom answer.

This removes the friction of open-ended prompts and gives you a clean summary of all decisions at the end.

Build Trust Gradually

Claude Code asks for permission before most actions. You have three options: yes, yes to this category, or no. Start restrictive. Allow read-only actions first. As you see the AI respect the spec and constitution, allow writes. Eventually, you may auto-allow certain safe categories. But avoid the --dangerously-skip-permissions flag until you have a proven, repeatable workflow. The spec is your safety guard, not a substitute for oversight.


Lessons Learned

  1. Time spent planning pays off. A clear spec reduces the number of follow-up interactions from major course corrections to small tweaks.

  2. Build stepwise in small, testable chunks. Each phase should be independently deployable and verifiable. This makes failures localized and recoverable.

  3. Security belongs in the spec from day one. Adding OAuth or authorization at the end can force a full stack redeploy. Define the security model in the first draft.

  4. Flexibility is required. The tool you want to use may not support everything you need. A good spec names fallback approaches so the AI does not get stuck.

  5. Update documentation as you learn. If you course-correct during implementation, update the spec. Otherwise, the document becomes a lie and the next agent will build on bad assumptions.

  6. Create a steering document. Take lessons learned and feed them back into a CLAUDE.md or project-level skill file. The next project starts smarter.


Why This Feels Slower But Is Actually Faster

SDD feels slower than vibe coding because you must think before you generate. But the projects that skip this step pay later. The debt shows up as:

  • Re-architecting after the third iteration
  • Rewriting code that drifted from the original goal
  • Debugging edge cases that were never defined
  • Explaining generated code to teammates who did not write the prompts

At Google, I saw the same pattern with large design docs. The teams that invested in a clear spec moved faster during implementation and had fewer surprises in production. The spec was not overhead. It was the work.

With AI, the spec is even more important. The AI is fast. It will happily build the wrong thing quickly. The spec keeps it aimed at the right thing.


When to Use SDD vs. Vibe Coding

ScenarioVibe CodingSpec-Driven
Throwaway script
Prototype⚠️ lightweight spec
Production feature
Multi-file change
Security-critical code
Team-reviewed feature
Long-lived system

The rule is simple: if the code needs to survive beyond the current session, write a spec.


Frequently Asked Questions

Does SDD mean more documentation work?

Yes, but it replaces debugging and rework. A spec is documentation that pays for itself by preventing the AI from building the wrong thing.

Can I use SDD with any AI coding assistant?

Yes, but some tools are better than others. Claude Code, Cursor, and GitHub Copilot can all follow specs. Multi-agent frameworks like GitHub Spec Kit or custom LangChain pipelines can enforce them more rigorously.

What if the spec is wrong?

Then the AI will build the wrong thing correctly. The spec is the source of truth. Reviewing the spec is just as important as reviewing the code. SDD does not remove human judgment. It concentrates it at the right point.

How do I keep specs in sync with code?

Treat the spec as a living file. Update it when the code changes. For spec-anchored projects, make spec updates part of the code review. For spec-as-source projects, the spec is the only file humans edit.

Is SDD only for AI-generated code?

No. It works for human-written code too. The difference is that humans can tolerate ambiguity better than AI. With AI, the cost of ambiguity is much higher.


References & Further Reading

  1. Three Tools for Spec-Driven Development — Birgitta Böckeler on the three levels of SDD rigor
  2. GitHub Spec Kit — open-source framework for spec-driven development
  3. Anthropic Claude Code — terminal-based AI assistant that can ingest specs and code context
  4. Extending Claude Code with Skills and Plugins — AWS guide on custom skills and steering documents
  5. Building an Elite Engineering Culture — on stacked PRs and building incrementally
  6. Cursor — AI code editor with context-aware implementation
  7. LangChain Multi-Agent Workflows — patterns for coordinator and verifier agents
  8. Google Engineering Design Docs — how structured specs drive large engineering projects

Spec-driven development is part of a broader shift: treating AI as an executor rather than an oracle. The better your spec, the better the AI performs. The next step is learning how to communicate with AI efficiently so the spec is not wasted on context decay.

#AI #Engineering #Software Development #System Design #Best Practices