← Back to list

How OpenSpec Actually Works: A Three-Phase Workflow That Keeps AI Honest

You’ve heard the pitch. Here’s the plumbing — and why the 50KB limit is the most important line of code in the whole framework.

Apurv Sheth · 2026-06-16 02:31 · 0 claps · 6.6 min read
#ai #software-development #java #python #ai-agent
Open on Medium ↗
Wiki topics: AGT · AI Agents AI · AI · General

How OpenSpec Actually Works: A Three-Phase Workflow That Keeps AI Honest

You’ve heard the pitch. Here’s the plumbing — and why the 50KB limit is the most important line of code in the whole framework.

I got a comment on my last post that I want to address before anything else.

Someone wrote: “This sounds like you’re just writing documentation before you code. We’ve been doing that since 2003. What’s new?”

It’s a fair challenge. And the answer is in a detail that’s easy to miss in the high-level pitch.

The difference between SDD and traditional upfront documentation is not what you write. It’s who reads it.

Traditional documentation is written for humans and then politely ignored by the tools. SDD specs are written for your AI agent — structured so that the model can reference them mid-generation, check its output against them, and recover context when a session ends and a new one begins.

The spec isn’t a human artefact that happens to be near the code. It’s a machine-readable contract that the AI is constrained to honour.

That distinction changes everything about how you write specs, how you structure them, and which tool you use to manage them.

Why I Chose OpenSpec

I evaluated both major open-source options — GitHub’s SpecKit and OpenSpec by Fission-AI. I’ll go deeper on the comparison in a future post, but the headline is this:

SpecKit is powerful. It has a “constitution” model that encodes cross-cutting rules (TDD requirements, coding standards, compliance constraints) into every feature workflow. For an enterprise compliance scenario, that’s genuinely valuable.

OpenSpec is fast. Three commands. No constitution to write upfront. Works with 25+ AI tools out of the box. And critically — it has a hard 50KB context limit per spec, which I’ll explain why matters enormously in a moment.

I’m building with Claude Code and occasionally Cursor, across a brownfield codebase with existing patterns I need to respect. OpenSpec suited me better. If you’re in a regulated industry and need to enforce rules like “every API endpoint requires OpenAPI documentation,” SpecKit’s constitution model might suit you better — and that’s a legitimate tradeoff.

The Three-Phase Workflow

OpenSpec enforces a strict state machine. No phase is optional. No phase can be skipped. Here’s how it actually works.

OpenSpec: 3 Phase Workflow

OpenSpec: 3 Phase Workflow

Phase 1: Propose

You start with an intent. Not code — intent.

/opsx:propose "add rate limiting to the public API endpoints"

The agent reads your existing openspec/specs/ (the source of truth about your current system), then generates a new folder: openspec/changes/add-rate-limiting/ containing:

**proposal.md** — structured document covering: what problem this solves, what will change (marked ADDED, MODIFIED, or REMOVED), what won't change, and key risks or dependencies.

**specs/** — behavioural scenarios in GIVEN/WHEN/THEN format. These are the acceptance criteria. They are specific enough that the AI can use them as test cases.

**design.md** — technical approach. Libraries, patterns, architectural decisions.

**tasks.md** — implementation checklist broken into small, independently reviewable chunks.

You — the human — review all of this before a single line of implementation code is written. This is the gate. If you approve, Phase 2 begins. If the proposal is wrong, you correct it here, not after three hours of implementation.

The delta markers (ADDED/MODIFIED/REMOVED) deserve special attention. They force you — and the AI — to be explicit about what exists now versus what will exist after. This sounds bureaucratic. In practice it catches a surprising number of “oh wait, that module already does this” moments before they become technical debt.

Phase 2: Apply

Once the proposal is approved:

/opsx:apply

The AI works through tasks.md one task at a time, reading the spec at each step. Not your prompt from memory — the spec file. This is the key difference from unstructured AI coding.

Each task is small and independently testable. If a task fails or produces unexpected output, you can see exactly which spec scenario was violated. The feedback loop is tight.

When Apply finishes, you run:

/opsx:verify

This checks the implementation against the specs. It doesn’t just run the tests — it validates that the code’s behaviour matches the GIVEN/WHEN/THEN scenarios. When it finds a gap, it tells you exactly which scenario is unaddressed.

I’ve run verify on maybe thirty features now. It finds a gap on about a third of them. Always something small — a missing error state, an edge case the tasks didn’t cover. The fact that it catches these before merge is the entire value of the framework.

Phase 3: Archive

/opsx:archive

The change folder moves to openspec/changes/archive/, and the delta specs merge into the main openspec/specs/ — the project's unified source of truth.

This is the part that distinguishes OpenSpec from SpecKit structurally. SpecKit maintains separate spec files per feature indefinitely. OpenSpec consolidates everything into one living document that represents the current state of the system.

The result: at any point in time, you can read openspec/specs/ and understand the full system without assembling context from dozens of feature files. The AI can read it too — and does, at the start of every new proposal.

The 50KB Context Limit: A Feature, Not a Bug

Let me explain why this seemingly arbitrary constraint is one of the most important design decisions in the framework.

Modern LLMs have massive context windows — some over a million tokens. You might think: great, load in everything. All the specs, all the code, all the history.

The problem is that large context doesn’t mean focused context. When you feed an agent 800KB of specs and code, it doesn’t read all of it equally. It attends to some parts more than others — and the parts it attends to aren’t necessarily the parts you care about for this specific task.

OpenSpec’s 50KB limit forces you to be disciplined about what goes into the spec. You can’t dump everything in. You have to think about what the agent actually needs to know to implement this feature correctly.

This discipline has a side effect: your specs get better. Concise, targeted, genuinely useful. Not a brain-dump — a brief.

For teams running multiple agents in parallel across a large codebase (which I’ll cover in part three), this constraint also means you can run several agents concurrently without them eating each other’s context budget. That’s an operational concern that becomes critical at scale.

A Real Example: The Rate Limiting Feature

Let me make this concrete. Here’s a condensed version of what a real proposal looks like from a project I’m working on.

**proposal.md excerpt:**

## What is changing
ADDED: Rate limiting middleware on all /api/v1/* routes
MODIFIED: Express app configuration to mount rate limiter before routes
ADDED: Redis-backed rate limit counter (per API key, per 15-minute window)
## What is NOT changing
Authentication flow, response format, existing error codes.
## Risk
Redis dependency — if Redis is unavailable, the middleware must fail open 
(allow requests) not fail closed (block all traffic).

**specs/rate-limiting.md excerpt:**

GIVEN a valid API key making requests
WHEN the key exceeds 100 requests per 15-minute window
THEN the API returns 429 with a Retry-After header
GIVEN Redis is unavailable
WHEN a request arrives at the rate limiter
THEN the request proceeds as normal (fail-open behaviour)

This second scenario — the fail-open Redis behaviour — was something I added during review. It wasn’t in the AI’s first draft of the spec. It came from me thinking through the failure mode.

That’s the spec review doing its job: forcing me to think about the system, not just describe a feature.

The resulting code handled Redis failures gracefully. Not because the AI is smart about resilience. Because the spec told it the behaviour it needed to implement.

What Makes Specs Good

After writing dozens of these, here’s what I’ve learned separates a useful spec from a useless one.

Be behavioural, not prescriptive. Describe what the system should do, not how to implement it. “Users must be notified within 30 seconds” is a good spec. “Use a WebSocket to push notifications” is an implementation decision that belongs in design.md.

Name your edge cases. The happy path is easy. The spec earns its value in the edge cases. What happens when the thing fails? What happens when it’s called twice? What happens with empty input?

Keep each scenario atomic. One GIVEN/WHEN/THEN per scenario. Don’t nest conditions. If you need to express complex logic, use multiple scenarios.

Write specs in plain English you’d be comfortable showing a non-technical stakeholder. If it requires three paragraphs to set up the context, the scenario is too complex.

The Honest Trade-off

Proposal time is real overhead. On a good day, writing and reviewing a proposal takes 20–30 minutes. On a complex feature, it can take a couple of hours.

I’ve stopped thinking of this as overhead. I think of it as the actual work — the thinking that needs to happen regardless, just moved to before implementation rather than during or after. The debugging and rework it replaces is almost always longer than the spec took to write.

If you’ve ever spent an afternoon untangling an AI-generated implementation that did something subtly but completely wrong — you’ll understand the appeal of 30 minutes upfront.

Next: The Hard Problem

Part 1 covered why SDD matters. Part 2 (this one) covered how OpenSpec implements it in a single repo.

But there’s a third problem nobody talks about in the SDD tutorials: what do you do when your system isn’t a single repo? What do you do when a feature change touches the auth service, the orders service, and the notification service — each in their own repository, each potentially worked on by a different agent running in a different context window?

That’s Part 3. And it’s where I think SDD is still genuinely immature — but where the most interesting patterns are emerging.

Liked this breakdown? The next post covers multi-repo SDD. Follow so you don’t miss it — and drop any questions in the comments. I read all of them.


메타데이터
post_id
3d4aeb61e9fa
slug
how-openspec-actually-works-a-three-phase-workflow-that-keeps-ai-honest-3d4aeb61e9fa
url
https://medium.com/@apurvsheth/how-openspec-actually-works-a-three-phase-workflow-that-keeps-ai-honest-3d4aeb61e9fa
canonical_url
https://medium.com/@apurvsheth/how-openspec-actually-works-a-three-phase-workflow-that-keeps-ai-honest-3d4aeb61e9fa
author_url
https://medium.com/@apurvsheth
status
ok
fetched_at
2026-06-16 19:09:56