DEV Community

Abhishek Banerjee
Abhishek Banerjee

Posted on Originally published at deeper-in-tech.hashnode.dev on

A Deep-Dive into OpenSpec and Delta-Driven V&V for AI Agents - Vibe Coding

Let’s be honest about the current state of AI-assisted engineering: we are trading structural integrity for raw velocity.

If you’ve used Cursor, Claude Code, or Copilot CLI on a real-world, 50,000-line codebase, you know the script:

  1. You prompt the agent in a chat box to add a feature or refactor a module.

  2. The agent reads a few files, builds a mental model with whatever context fits in its window, and writes 400 lines of typescript/go/python.

  3. It works! You celebrate.

  4. Two days later, you discover that while adding that feature, the agent quietly modified an exported interface, introduced a subtle race condition in an un-tested edge case, and broke a downstream background job.

This isn't an LLM intelligence problem it's a context and verification problem.

When we rely on chat windows, our intent lives in transient prompts. Once the chat buffer clears, the reasoning is gone. The agent has no persistent, version-controlled source of truth to check its work against.

To fix this, Fission AI released OpenSpec an open-source framework designed to bring classical Software Verification and Validation (V&V) into agentic workflows without dragging engineering teams back into 1990s waterfall spec-writing.

Here is an opinionated, hands-on teardown of OpenSpec: how it works under the hood, a real code walkthrough, and an honest assessment of its production trade-offs.

The Theoretical Core: Why "Delta Specs" Fix Brownfield Codebases

Most Spec-Driven Development (SDD) frameworks fail in the real world for one simple reason: they assume you are building a greenfield app.

Writing a 2,000-line spec for a brand-new project is easy. Writing a spec for a monolithic, brownfield application that has been accumulating technical debt since 2021 is practically impossible.

OpenSpec solves this with Delta Specifications.

Instead of forcing you to document your entire repository upfront, OpenSpec treats specs like Git commits. You maintain a living baseline spec (openspec/specs/), and every new feature or bug fix creates an isolated, directory-bound Change Package containing Delta Requirements (ADDED, MODIFIED, or REMOVED).

openspec/
├── config.yaml # Agent slash command mapping
├── specs/ # Living Source of Truth (Merged State)
│ ├── auth.md
│ └── payment-pipeline.md
└── changes/ # Active Change Packages
    ├── archive/ # Historical record of merged changes
    └── 2026-09-24-stripe-webhooks/
        ├── proposal.md # The WHY: Context, trade-offs, scope
        ├── design.md # The HOW: Architecture, schemas, boundaries
        ├── tasks.md # The DO: Granular, executable task list
        └── specs/ # The DELTA: Exact spec changes
            └── payment-pipeline.md

Enter fullscreen mode Exit fullscreen mode

This structural separation separates Validation ("Are we building the right feature?") from Verification ("Did the agent build it correctly according to the spec?").

Hands-On Walkthrough: Refactoring an API Gateway under OpenSpec

Let's step out of theory and look at a real-world scenario: Adding idempotent request handling to a Node.js/TypeScript payment webhook handler.

1. Installation & Config Check

OpenSpec runs locally via Node (requires Node v20.19.0+). You install the CLI globally and initialize it in your project root:

# Install global CLI
npm install -g @fission-ai/openspec@latest

# Initialize inside your repo
cd backend-api
openspec init

Enter fullscreen mode Exit fullscreen mode

During initialization, OpenSpec detects your coding environment (Claude Code, Cursor, Copilot, Windsurf, etc.) and populates local slash commands or system prompt instructions inside your project.

Inspect .claude/commands/ or .cursor/rules/ you’ll see that OpenSpec injects deterministic instructions that instruct your LLM agent how to read, write, and validate .md artifacts inside openspec/changes/.

2. Drafting the Proposal and Delta Spec

Rather than asking your agent to "add Redis idempotency to webhooks", you execute the proposal command in your agent chat window:

/opsx:propose stripe-idempotency

Enter fullscreen mode Exit fullscreen mode

The agent inspects your codebase and writes the initial change directory: openspec/changes/stripe-idempotency/.

Instead of jumping into .ts files, you open openspec/changes/stripe-idempotency/specs/payment-pipeline.md and review the generated Delta Requirements :

# Delta Spec: Stripe Webhook Idempotency

## MODIFIED Requirements

### Requirement: Webhook Ingestion
The system SHALL process incoming Stripe events idempotently using a Redis key lock.

#### Scenario: Duplicate Event Ingestion
- GIVEN a request to `POST /v1/webhooks/stripe`
- AND a header `Stripe-Signature` containing event ID `evt_3M012345`
- WHEN the event ID exists in Redis with state `COMPLETED`
- THEN the service SHALL return `200 OK` immediately with body `{ "status": "already_processed" }`
- AND the service SHALL NOT execute downstream database writes.

#### Scenario: Concurrent Event Processing (Lock Contention)
- GIVEN two identical Stripe events arriving within 10ms of each other
- WHEN event A acquires the Redis lock `idempotency:evt_3M012345`
- THEN event B SHALL receive HTTP `429 Too Many Requests` or wait for lock release.

Enter fullscreen mode Exit fullscreen mode

Notice the format: RFC 2119 keywords (SHALL , MUST) combined with strict GIVEN / WHEN / THEN scenarios.

This gives the agent an unambiguous contract. You aren't guessing what the LLM will generate; you are reviewing the behavior before a single line of code is modified.

3. Architectural Design & Task Breakdown

Next, the agent generates design.md and tasks.md inside the same change folder:

<!-- openspec/changes/stripe-idempotency/design.md -->
# Design: Redis Idempotency Layer

## Schema & Key Storage
- **Key Pattern:** `idempotency:stripe:{event_id}`
- **TTL:** 86,400 seconds (24 Hours)
- **Values:** `PROCESSING` | `COMPLETED` | `FAILED`

## Error Handling Bounds
- If Redis connection times out (> 100ms), fallback to PostgreSQL transactional check (`SELECT 1 FROM stripe_events WHERE event_id = $1`).

Enter fullscreen mode Exit fullscreen mode

And the corresponding tasks.md:

<!-- openspec/changes/stripe-idempotency/tasks.md -->
# Implementation Tasks

- [] 1. Create Redis connection helper in `src/lib/redis.ts` with connection pooling.
- [] 2. Implement `withIdempotency` higher-order function in `src/middleware/idempotency.ts`.
- [] 3. Write unit test mocking Redis lock acquisition failure in `tests/idempotency.test.ts`.
- [] 4. Update Express route handler in `src/routes/webhooks.ts`.

Enter fullscreen mode Exit fullscreen mode

4. Executing the Change (/opsx:apply)

Now, you run:

/opsx:apply

Enter fullscreen mode Exit fullscreen mode

Your agent reads tasks.md and begins working through the list step-by-step. Because the scope is strictly bounded by design.md and specs/payment-pipeline.md, the agent doesn't refactor unrelated files, hallucinate new dependencies, or invent custom response formats.

As tasks complete, the agent updates tasks.md in real-time, giving you full visibility into execution progress.

5. Automated Verification (/opsx:verify) and Archiving (openspec archive)

Once code generation finishes, you invoke:

/opsx:verify

Enter fullscreen mode Exit fullscreen mode

The agent runs your test runner (npm test), reads the test output, and compares the runtime behavior against the GIVEN/WHEN/THEN scenarios defined in your spec delta. If a scenario fails (e.g., Redis timeout isn't handled properly), the agent flags the discrepancy and corrects the code.

When everything passes, you archive the change package:

openspec archive

Enter fullscreen mode Exit fullscreen mode

This CLI command performs two automated actions:

  1. It takes the MODIFIED delta in openspec/changes/stripe-idempotency/specs/payment-pipeline.md and merges it into your main repository spec (openspec/specs/payment-pipeline.md).

  2. It moves the change directory into openspec/changes/archive/2026-09-24-stripe-idempotency/.

Your repository now has an updated, living specification that accurately reflects the new codebase state.

OpenSpec vs. GitHub Spec Kit vs. Raw Prompting

To understand where OpenSpec fits in your toolchain, here is how it compares to alternative approaches:

Feature

|

Raw Chat Prompting (Cursor/Claude)

|

GitHub Spec Kit (SDD)

|

OpenSpec (Fission AI)

|
|

Context Persistence

|

❌ Transient (Lost when chat ends)

|

✅ Markdown Files

|

✅ Markdown Files

|
|

Brownfield Adaptation

|

⚠️ High risk of breaking legacy code

|

⚠️ Requires heavy spec setups

|

✅ Delta Specs (ADDED/MODIFIED)

|
|

Tool Lock-In

|

❌ Locked to specific IDE/Agent

|

✅ Agent Agnostic (38+ harnesses)

|

✅ Zero Lock-In (Local NPM / Configs)

|
|

Verification Layer

|

❌ Manual code review / developer tests

|

✅ Convergence phase

|

✅ Strict GIVEN/WHEN/THEN scenario audits

|
|

Cross-Repo Sync

|

❌ Impossible

|

⚠️ Single repo focused

|

✅ OpenSpec Stores & Cloud Agents

|

The Verdict: Production Strengths vs. Real-World Friction

Where OpenSpec Wins:

  • No More Context Drift: Because artifacts are stored as standard Markdown inside Git, you can stop mid-feature, switch branches, or swap from Claude Code to Cursor without losing intent or context.

  • Brownfield Efficiency: Delta specs allow you to introduce rigorous specification to a legacy 100k-line app without spending weeks writing baseline documentation.

  • Auditable History: The openspec/changes/archive/ folder acts as an architectural decision record (ADR) tied directly to Git commits.

Where Friction Remains:

  • Spec Overhead on Small Fixes: If you just need to fix a typo or change a single CSS class, running the full /opsx:propose (\rightarrow) /opsx:apply (\rightarrow) /opsx:archive cycle adds unnecessary friction. (Recommendation: save OpenSpec for complex features, multi-file refactors, or critical API logic).

  • Agent Discipline Required: Smaller LLM models (e.g., sub-7B models or cheaper API tiers) sometimes attempt to edit code directly without updating tasks.md first. Stronger models (Claude 3.7 Sonnet, GPT-4o) handle the constraint loop significantly better.

Final Thoughts

The era of blind "vibe coding" was a fun novelty, but it doesn't scale to production software engineering. If we want AI agents to write reliable, maintainable code, we must provide them with clear, version-controlled constraints.

By using lightweight delta specs and repo-native Markdown artifacts, OpenSpec gives developers a practical, low-friction way to govern AI agents without slowing down velocity.

Top comments (0)