Direct Answer
OpenSpec is a repository-local workflow for planning and tracking AI-assisted code changes with plain-text artifacts. Install the CLI, initialize the repository, create a change through the workflow generated for your AI tool, review proposal.md, delta specs, optional design.md, and tasks.md, then implement and verify the change. Archive only after the implementation, tests, and maintained specifications agree.
The important boundary is simple: OpenSpec makes intent and change history reviewable; it does not make generated code correct. openspec validate can reject malformed artifacts, and the optional verify workflow can identify mismatches, but your compiler, tests, security checks, reviewers, and release controls remain the sources of executable evidence.
Table of Contents
- The Mental Model: Specs Versus Changes
- Install and Initialize OpenSpec
- Terminal Commands and AI Workflows Are Different
- Choose the Core or Expanded Workflow
- Tutorial: Add a Request Body Limit to a Go API
- Review the Artifacts Before Code
- Implement and Prove the Change
- Update, Sync, Verify, and Archive
- Use OpenSpec in a Team
- Failure Modes and Troubleshooting
- When OpenSpec Is Worth the Overhead
- FAQ
- Official Sources
The Mental Model: Specs Versus Changes
OpenSpec separates the behavior the team currently accepts from a proposed modification:
openspec/
├── config.yaml
├── specs/
│ └── <domain>/spec.md
└── changes/
├── <active-change>/
│ ├── proposal.md
│ ├── specs/<domain>/spec.md
│ ├── design.md
│ └── tasks.md
└── archive/
openspec/specs/describes maintained system behavior. Treat it as a reviewed documentation baseline, not as an enforcement engine.openspec/changes/<name>/holds the intent and delta for one in-flight change.proposal.mdexplains why the change exists, what is in scope, and what is not.specs/contains deltas: requirements being added, modified, or removed.design.mdrecords consequential technical decisions. It can be conditional for small or non-technical changes, depending on the configured schema.tasks.mdorders implementation and verification work. A checked box is a status claim, not proof.changes/archive/preserves closed change records after their deltas have been reconciled with the main specs.
The artifacts form a dependency graph, not four independent documents:
If implementation reveals a missed constraint, revise the governing artifact and its downstream tasks. Quietly changing only the code creates spec drift.
For the broader method, including requirements engineering and traceability independent of any product, see the Spec Coding guide. This page focuses on the current OpenSpec workflow.
Install and Initialize OpenSpec
OpenSpec currently requires Node.js 20.19.0 or newer when installed through npm. Check the runtime and CLI before changing a repository:
node --version
npm install -g @fission-ai/openspec@latest
openspec --version
Then initialize from the repository root, preferably on a branch so the generated files are easy to inspect:
cd existing-go-service
git switch -c feature/request-body-limit
openspec init
git status --short
openspec init asks which AI tools you use and writes:
- the
openspec/project structure; and - generated workflow skills and/or commands in tool-specific directories such as
.agents/,.claude/,.cursor/, or.trae/.
Review those files before committing them. Initialization does not understand your architecture merely because it found the repository. Put durable project context and constraints in openspec/config.yaml, then verify generated workflows against the actual codebase.
Re-running openspec init can add or refresh tool integrations. After upgrading the CLI, changing the profile, or adding an AI tool, refresh generated files:
openspec update
Generated skills and commands do not update themselves. Restart the AI IDE if newly generated commands are not visible.
Terminal Commands and AI Workflows Are Different
This distinction prevents the most common setup failure:
| Surface | Example | What it does |
|---|---|---|
| Terminal | openspec init |
Creates or refreshes project integration |
| Terminal | openspec list |
Lists active changes |
| Terminal | openspec show add-request-body-limit |
Displays a change |
| Terminal | openspec validate add-request-body-limit |
Checks artifact structure and schema rules |
| Terminal | openspec view |
Opens the interactive project dashboard |
| AI chat | propose workflow | Asks the assistant to create change artifacts |
| AI chat | apply workflow | Asks the assistant to implement tasks |
| AI chat | archive workflow | Asks the assistant to reconcile and close a change |
Do not type /opsx:propose in a shell, and do not type openspec validate into AI chat expecting deterministic CLI validation.
The workflow is the same across supported tools, but invocation syntax is not:
| Host | Propose | Apply | Archive |
|---|---|---|---|
| Claude Code | /opsx:propose |
/opsx:apply |
/opsx:archive |
| Cursor or Trae | /opsx-propose |
/opsx-apply |
/opsx-archive |
| Codex | $openspec-propose |
$openspec-apply-change |
$openspec-archive-change |
These examples reflect the current generated names. The authoritative answer for your installation is the completion output from openspec init and the files it generated. Delivery can be skills, commands, or both; some hosts support only skills.
Choose the Core or Expanded Workflow
The default core profile installs six workflows:
| Workflow | Use it when |
|---|---|
explore |
The problem or approach is still ambiguous; no change artifacts are needed yet |
propose |
You want proposal, specs, design where needed, and tasks drafted in one pass |
apply |
The artifacts are reviewed and implementation may begin |
update |
Discovery requires an existing plan or requirement to change |
sync |
You need to merge deltas into main specs while keeping the change active |
archive |
Implementation is accepted and the change can be closed |
The expanded set adds new, continue, ff, verify, bulk-archive, and onboard. Use new plus continue when reviewers want to approve artifacts incrementally; use ff to create all planning artifacts in one pass; enable verify when you want an artifact-to-code comparison before closure.
Configure the workflow set globally, then apply it to the current project:
openspec config profile
openspec update
Adding more workflows is not automatically better. A team that cannot reliably review four artifacts will not gain control by generating twelve commands.
Tutorial: Add a Request Body Limit to a Go API
Assume a mature Go service accepts JSON imports at POST /v1/import. Large request bodies can exhaust memory before validation. The desired change is:
- reject bodies above 1 MiB with HTTP
413; - accept a body exactly at the limit;
- handle bodies with unknown
Content-Length; - preserve all unrelated routes and authentication behavior;
- add tests and an observable rejection counter;
- avoid changing the public JSON schema.
This is a useful brownfield example because the requirement touches security, HTTP semantics, memory behavior, and existing middleware order.
Step 1: Explore the Existing Behavior
Use the explore workflow before creating artifacts when the insertion point is uncertain. Ask the assistant to inspect, not modify:
Inspect POST /v1/import and its middleware chain. Identify where the body is
first read, current size controls, error response conventions, metrics, tests,
and any reverse-proxy limit. Do not edit files. Report evidence with paths.
The review should answer:
- Does the proxy already reject large bodies, and is the application limit still needed?
- Is the body streamed, buffered, decompressed, or decoded more than once?
- Should the limit apply before or after authentication?
- Does the API use a shared error envelope instead of
http.Error? - What happens when
Content-Lengthis absent or false? - Which tests can prove that unrelated routes remain unchanged?
Exploration is not a substitute for reading the evidence. Reject claims that lack file paths, configuration keys, or test names.
Step 2: Propose One Bounded Change
Invoke the propose workflow using the syntax generated for your host. For example:
/opsx:propose add-request-body-limit
Give the assistant the observed facts, required behavior, non-goals, and verification gates. A useful prompt is:
Create change add-request-body-limit for POST /v1/import only.
Use a 1 MiB application limit, return the existing API error envelope with
HTTP 413, handle unknown Content-Length, increment the existing rejection
metric, and preserve middleware order and all other routes. Include Go unit
tests, the repository lint gate, and a regression test for the exact limit.
Do not change authentication, proxy configuration, or the JSON schema.
The generated plan should be specific enough that a reviewer can reject a wrong implementation before code exists.
Step 3: Write a Precise Delta Spec
A new behavior belongs under ## ADDED Requirements:
# Delta for HTTP Import
## ADDED Requirements
### Requirement: Import request body limit
The service MUST reject `POST /v1/import` request bodies larger than 1 MiB
before JSON decoding and MUST return the existing API error envelope with
HTTP status `413`.
#### Scenario: Declared body exceeds the limit
- **GIVEN** an authenticated import request with `Content-Length` above 1 MiB
- **WHEN** the request reaches the import body-limit middleware
- **THEN** the service returns HTTP `413`
- **AND** the import handler is not invoked
- **AND** the rejection counter increments once
#### Scenario: Unknown body length exceeds the limit
- **GIVEN** an authenticated chunked import request without `Content-Length`
- **WHEN** reading detects more than 1 MiB
- **THEN** the service returns HTTP `413`
- **AND** no unbounded buffer is allocated
#### Scenario: Body is exactly at the limit
- **GIVEN** an authenticated import request whose body is exactly 1 MiB
- **WHEN** the body is read
- **THEN** the import handler receives the complete body
Use ## MODIFIED Requirements only when replacing an existing requirement. Include the entire revised requirement and all scenarios, not a sentence fragment or patch note: archive replaces the prior requirement with the modified version. Use ## REMOVED Requirements only when the maintained behavior should disappear, and state the migration or compatibility consequence in the proposal.
The delta is a behavioral contract. Implementation details such as package names, helper signatures, or buffering strategy belong in design.md unless they are externally observable constraints.
Step 4: Make Design Tradeoffs Explicit
For this change, the design review should compare at least:
| Decision | Option | Consequence |
|---|---|---|
| Limit layer | Reverse proxy only | Cheapest rejection, but behavior varies by deployment and local tests miss it |
| Limit layer | Application only | Portable contract, but traffic consumes app resources before rejection |
| Body handling | Read at most limit + 1 bytes |
Simple for small JSON payloads; bounded memory but still buffers |
| Body handling | Stream through http.MaxBytesReader |
Lower copying; downstream decode errors must map reliably to 413 |
| Middleware order | Before auth | Protects auth from large bodies but may change error precedence |
| Middleware order | After auth | Preserves auth semantics but spends auth work on rejected requests |
The correct answer depends on existing architecture. Record the selected option, rejected alternatives, memory bound, status mapping, metrics, and rollback strategy. Do not hide those decisions in generated code.
Step 5: Turn Requirements into Traceable Tasks
A useful tasks.md maps work to behavior and evidence:
## 1. Request limiting
- [ ] 1.1 Add a bounded reader for POST /v1/import
- [ ] 1.2 Preserve the current API error envelope and middleware order
- [ ] 1.3 Increment the existing rejection metric exactly once
## 2. Verification
- [ ] 2.1 Test declared oversized bodies
- [ ] 2.2 Test unknown-length oversized bodies
- [ ] 2.3 Test a body exactly at 1 MiB
- [ ] 2.4 Test that another route is unaffected
- [ ] 2.5 Run Go tests, lint, and the repository security gate
Tasks should not introduce work absent from the proposal or requirements. Conversely, every risk-bearing scenario needs an implementation or verification task.
Review the Artifacts Before Code
Read generated artifacts in dependency order and stop at the first invalid assumption:
| Artifact | Review question | Reject when |
|---|---|---|
proposal.md |
Are the problem, scope, non-goals, and affected domains correct? | It adds unrelated cleanup or omits compatibility impact |
| delta specs | Is every required outcome observable and testable? | Terms such as "large", "fast", or "secure" lack thresholds |
design.md |
Are risks, alternatives, boundaries, and rollback explicit? | It selects an approach without considering middleware or memory behavior |
tasks.md |
Does each task trace to approved behavior? | A task has no requirement, or a requirement has no evidence task |
For high-risk changes, maintain a small traceability table in the proposal or PR:
| Requirement | Implementation | Evidence |
|---|---|---|
| Reject declared oversized body | import middleware | unit test for Content-Length > limit |
| Reject unknown-length body | bounded reader | chunked-body unit test |
| Accept exact limit | bounded reader boundary | exact-limit unit test |
| Preserve other routes | route-scoped registration | router regression test |
| Emit one metric | rejection branch | metric assertion |
OpenSpec does not create approval, ownership, or access control. Use code owners, protected branches, CI, and deployment policy for enforcement.
Implement and Prove the Change
After review, run the apply workflow. The assistant may update task boxes while it works, but inspect the diff after every meaningful group of changes.
The following compact Go implementation demonstrates the bounded-read behavior for a small JSON endpoint. A production service should adapt its existing error envelope and metrics instead of copying the strings:
package bodylimit
import (
"bytes"
"io"
"net/http"
)
func Middleware(maxBytes int64, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if maxBytes < 1 {
http.Error(w, "invalid body limit", http.StatusInternalServerError)
return
}
if r.ContentLength > maxBytes {
http.Error(w, "request body too large", http.StatusRequestEntityTooLarge)
return
}
body, err := io.ReadAll(io.LimitReader(r.Body, maxBytes+1))
if err != nil {
http.Error(w, "invalid request body", http.StatusBadRequest)
return
}
if int64(len(body)) > maxBytes {
http.Error(w, "request body too large", http.StatusRequestEntityTooLarge)
return
}
r.Body = io.NopCloser(bytes.NewReader(body))
next.ServeHTTP(w, r)
})
}
The corresponding tests exercise both detection paths and prove that an accepted body reaches the next handler unchanged:
package bodylimit
import (
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestMiddleware(t *testing.T) {
const limit = int64(8)
tests := []struct {
name string
body string
contentLength int64
wantStatus int
wantBody string
}{
{
name: "declared body exceeds limit",
body: "123456789",
contentLength: 9,
wantStatus: http.StatusRequestEntityTooLarge,
},
{
name: "unknown length exceeds limit",
body: "123456789",
contentLength: -1,
wantStatus: http.StatusRequestEntityTooLarge,
},
{
name: "body at exact limit",
body: "12345678",
contentLength: 8,
wantStatus: http.StatusNoContent,
wantBody: "12345678",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
t.Fatalf("read accepted body: %v", err)
}
w.Header().Set("X-Accepted-Body", string(body))
w.WriteHeader(http.StatusNoContent)
})
request := httptest.NewRequest(
http.MethodPost,
"/v1/import",
strings.NewReader(tc.body),
)
request.ContentLength = tc.contentLength
response := httptest.NewRecorder()
Middleware(limit, next).ServeHTTP(response, request)
if response.Code != tc.wantStatus {
t.Fatalf("status = %d, want %d", response.Code, tc.wantStatus)
}
if got := response.Header().Get("X-Accepted-Body"); got != tc.wantBody {
t.Fatalf("accepted body = %q, want %q", got, tc.wantBody)
}
})
}
}
Run evidence independently of the assistant:
openspec validate add-request-body-limit
go test ./...
go vet ./...
Then run repository-specific lint, race, security, contract, and integration checks according to the risk. For this example, add tests for the real API error envelope, metrics, route scope, compressed bodies if supported, proxy behavior, and memory under concurrent load.
Use this evidence ladder:
- Artifact validity:
openspec validateconfirms the change follows the configured schema. - Build validity: compilation and static checks reject type, API, and common correctness errors.
- Behavioral evidence: tests map to each scenario, including boundaries and failures.
- Diff review: a human confirms scope, design, generated files, dependencies, and operational impact.
- Operational evidence: metrics, load tests, rollout checks, and rollback prove production assumptions where necessary.
A task checkbox or a model's statement that tests passed is not evidence unless the command output is available and the test actually covers the requirement.
Update, Sync, Verify, and Archive
These workflows solve different lifecycle problems:
Update
Use update when implementation discovery changes an active artifact. For example, if the service already enforces 512 KiB at the proxy, decide whether the application contract should match or intentionally differ. Revise the proposal, spec, design, and tasks before continuing. Re-run artifact validation and affected tests.
Sync
Sync merges a change's delta specs into openspec/specs/ without closing the active change. It is useful when implementation lasts across several PRs and later work needs the accepted behavior baseline. Review conflicts first, especially when two active changes modify the same requirement.
Verify
The optional verify workflow compares the artifacts and implementation across completeness, correctness, and coherence. It can flag unchecked tasks, missing scenario coverage, and design drift. It remains an AI-assisted review:
- it does not execute every relevant test unless explicitly instructed and permitted;
- it does not prove a security or performance property;
- it does not independently approve its own implementation; and
- it does not block archive automatically.
Treat its output as findings to investigate, not a release certificate.
Archive
Archive closes the change. OpenSpec applies delta semantics:
ADDEDrequirements are added to the maintained specs;MODIFIEDrequirements replace their previous complete versions;REMOVEDrequirements are deleted; and- the change folder moves under
openspec/changes/archive/.
Before archiving, confirm:
- approved requirements match shipped behavior;
- all required checks ran on the final diff;
- known deviations are resolved or explicitly accepted;
- overlapping active changes have been reconciled;
- operational rollout and rollback notes are stored where the team maintains them; and
- the main specs still describe current behavior after the merge.
An archive is an audit record, not automatic long-term memory for every future model. Future usefulness depends on retrieval, current specs, repository context, and reviewers consulting the record.
Use OpenSpec in a Team
Adopt It Incrementally
Do not reverse-engineer the whole legacy system before the first change. Start with one bounded change whose behavior and risk are observable. Let accepted deltas build the maintained spec tree over time.
Put Artifacts in Version Control
Review openspec/, generated workflow files, and code in normal pull requests. A practical sequence is:
- draft and review artifacts;
- implement against the approved revision;
- attach scenario-to-test evidence;
- review the final code and artifact diff together;
- archive after acceptance, either in the same PR or a controlled follow-up.
If planning and implementation use separate PRs, identify the exact artifact commit that governed implementation.
Keep Global Rules Separate
Repository instruction files such as AGENTS.md, CLAUDE.md, or tool rules describe durable coding conventions. OpenSpec change artifacts describe one behavioral modification. Neither is a security boundary. Enforce critical rules with code, tests, policies, permissions, and CI.
Measure Outcomes, Not Artifact Volume
Compare similar changes using:
- review defects found before implementation;
- requirement changes after coding began;
- lead time and review time;
- escaped defects and rollback rate;
- unplanned files changed;
- stale or conflicting specs; and
- maintenance time spent on artifacts.
More Markdown is not evidence of better engineering. Keep the workflow only where its traceability value exceeds its review and maintenance cost.
Failure Modes and Troubleshooting
| Symptom | Likely cause | Corrective action |
|---|---|---|
/opsx:propose is missing |
The host uses another syntax or commands were not generated | Read init output, inspect the host folder, run openspec update, and restart the tool |
| Only six workflows appear | The default core profile is active | Run openspec config profile, select needed workflows, then update the project |
design.md is absent |
The configured schema judged it unnecessary or the artifact graph is incomplete | Inspect status and schema rules; create design only when the change needs it |
openspec validate passes but code fails |
Validation covered artifact structure, not implementation | Run compiler, tests, static analysis, and scenario-to-code review |
| Apply changes unrelated files | Scope or tasks were weak, or the agent drifted | Stop, inspect the diff, tighten non-goals and tasks, revert only unwanted edits, then resume |
| Code and specs disagree | Requirements changed only in chat or implementation | Use update, review revised artifacts, and rerun affected evidence |
| Archive creates a surprising spec | A MODIFIED delta was incomplete or active changes conflict |
Restore the full requirement, compare main specs and deltas, resolve before archive |
| Generated workflows look stale | CLI/profile changed but project files did not | Run openspec update and review the generated diff |
Avoid copying commands from an old tutorial without checking the generated integration. Legacy command names and directory layouts can remain in search results long after the package changes.
When OpenSpec Is Worth the Overhead
| Change | Recommended workflow |
|---|---|
| Typo or obvious one-line correction | Normal issue and code review; a full change may add noise |
| Local feature with clear acceptance behavior | Propose, review, apply, tests, archive |
| Ambiguous brownfield change | Explore first, then propose after codebase evidence is known |
| Auth, payment, privacy, migration, or irreversible data change | Incremental artifacts, explicit design, threat/failure review, independent gates |
| Multi-PR feature | Propose, update as evidence changes, sync when the baseline must advance, archive after final acceptance |
| Emergency incident fix | Restore service under incident controls, then reconcile artifacts and evidence; do not delay containment for ceremony |
| Documentation-only or tooling-only change | Use a lighter schema when no behavioral delta is meaningful |
OpenSpec is most useful when misunderstanding is expensive and reviewable intent reduces that risk. It is less useful when artifact maintenance costs more than the uncertainty it removes.
FAQ
What is OpenSpec and how do I use it?
OpenSpec is an open-source, artifact-based workflow for spec-driven development with AI coding assistants. Install its CLI, run openspec init, invoke the generated explore or propose workflow in AI chat, review the artifacts, apply the tasks, collect executable evidence, and archive only after behavior and maintained specs agree.
Why does an OpenSpec command work in one AI tool but not another?
Hosts use different invocation syntax and may install skills, commands, or both. Claude Code can use /opsx:propose, Cursor and Trae use /opsx-propose, and Codex uses $openspec-propose. The files and completion message generated by your installed CLI are authoritative. Run openspec update and restart the host after profile or integration changes.
Can I introduce OpenSpec into an existing codebase?
Yes. Initialize on a branch and begin with one bounded change. Inspect current behavior first, capture only the affected domain and discovered constraints, and let accepted changes grow the maintained specs. A speculative rewrite of every legacy requirement creates more unverified documentation, not more control.
Does OpenSpec verify that generated code is correct?
No. CLI validation checks artifacts. The optional verify workflow performs an AI-assisted comparison of artifacts and code. Neither replaces builds, tests, security and performance checks, human review, or production controls. Match evidence depth to the consequence of failure.
What is the difference between sync and archive in OpenSpec?
Sync merges delta specs into the main openspec/specs/ tree while keeping the change active. Archive reconciles the deltas, closes the change, and moves its folder to the archive. Before either action, review conflicts and ensure the maintained specs still describe accepted behavior.
Summary
The reliable OpenSpec loop is not merely propose, apply, and archive. It is:
inspect current behavior
-> explore uncertainty
-> propose artifacts
-> review scope and scenarios
-> apply bounded tasks
-> run independent evidence
-> reconcile discoveries
-> sync when needed
-> archive accepted behavior
Use the generated command syntax for your AI host, preserve the distinction between current specs and change deltas, and treat every artifact as a reviewable claim. The workflow earns its place when those claims map cleanly to code, tests, operational evidence, and a controlled release.
Official Sources
- OpenSpec installation
- Set up an OpenSpec project
- OpenSpec quickstart
- Supported tools and invocation syntax
- Workflow profiles
- Workflow skill contracts
- Reviewing and correcting a plan
- Spec-driven schema reference
- OpenSpec source repository