Chapter 01 — The problem

OpenSpec assumed one repo.

Every command asked the same implicit question: which repo am I in? Changes were authored under one repo, specs resolved locally, apply and archive were repo-local lifecycle steps. For normal single-repo work this was fine.

But real features don't always stay in one repo. A behavior change can span a service, a client, and a shared-contract repo. There was nowhere honest to write that one plan down. So it scattered — into Slack threads, side docs, and three half-plans in three repos, none of them the source of truth.

Before

One plan per repo. Either one repo pretends to own it — dishonest — or three parallel plans drift. The cross-repo change is legible nowhere.

What was needed

A single planning home that doesn't collapse repo ownership. One place to see the whole thing, with each repo still the source of truth for its own specs and execution.

Chapter 02 — The bet

Plan centrally. Execute locally. Don't touch ownership.

The POC committed to a small, opinionated thesis before any code got written:

The workspace is a persistent coordination home. Canonical specs stay in the owning repo. Repo-local execution stays repo-local. The primary user-facing primitive is still change. The methodology is still spec-driven. WORKSPACE_POC_PRD.md — Core Principles

This ruled out some tempting shortcuts. No new primitive. Users still think in change, not some new "initiative" concept. No new methodology schema. Workspace behavior is a topology concern, not a fork of spec-driven. No fully multi-root-aware CLI across every command. Just the minimum surface to prove the idea.

Chapter 03 — The shape

A workspace is the set of repos, not a feature folder.

The important mental model: one workspace holds many changes over time, across the same set of registered repos. You name a workspace after the coordination surface (a product, a team, a logical slice), not after an individual feature. Changes come and go inside it.

It lives in a managed location so users don't pick a home: $XDG_DATA_HOME/openspec/workspaces/<name>, or ~/.local/share/openspec/workspaces/<name> as the Unix fallback. Inside, committed metadata is deliberately split from local paths:

~/.local/share/openspec/workspaces/platform/ # one workspace ├─ .openspec/ │ ├─ workspace.yaml # committed. aliases + optional owner/handoff │ └─ local.yaml # gitignored. machine-specific repo paths └─ changes/ # many changes live here over time ├─ add-auth/ # one cross-repo change │ ├─ proposal.md │ ├─ design.md │ ├─ tasks/coordination.md │ └─ targets/ │ ├─ app/ (tasks.md, specs/) │ └─ api/ (tasks.md, specs/) ├─ rotate-secrets/ # another change, different targets └─ rename-events/ # another, maybe already archived

There's no inner openspec/ directory and no repo-local state anywhere in the workspace. The workspace is a planning root, not a repo. Repo-local openspec/changes/ only shows up inside a target repo once a change is materialized there.

Chapter 04 — The rhythm

26 phases. One day. A deliberate cadence.

The POC shipped on 2026-04-17 across 26 numbered phases. The order is not an accident — you can read the rhythm off a bar:

4 research locked v0 contracts in DECISION.md before any code 11 features shipped against those locked contracts 10 test phases followed feature phases directly

Every feature had a test phase right after it. Every risky design decision had a research phase right before it. The four DECISION.md files (phases 07, 10, 13, 18) are short but load-bearing — they pin what's in and what's out of v0 so implementation doesn't drift.

Chapter 05 — Act I · Foundation (00–04)

The fixtures came before the feature.

Phase 00 is the tell. Before any workspace create code existed, a sandbox harness shipped — a helper that clones workspace fixtures into temp roots and canonicalizes absolute paths at runtime. That one choice forced everything later to stay honest about what's committed vs. what's local.

Phases 01–04 built on top: workspace create (reusing the existing setup bootstrap instead of forking it), then add-repo and doctor. The registry split is the big idea:

.openspec/workspace.yaml · committed

version: 1 name: platform repos: app: description: Application repo api: description: API repo docs: description: Docs repo

.openspec/local.yaml · gitignored

version: 1 repoPaths: app: /Users/me/code/app api: /Users/me/code/api docs: /Users/me/code/docs

doctor reports drift (missing mappings, stale paths, extra aliases) and exits non-zero — but never mutates files. The user fixes it.

Chapter 06 — Act II · One change, many targets (05–06)

The central command: new change --targets.

Inside an existing workspace, openspec new change add-auth --targets app,api doesn't just create a change — it partitions it. You get a shared proposal and design at the top, plus a per-target slice underneath, ready to be worked independently. The change lives alongside any other changes already in the workspace.

$ openspec new change add-auth --targets app,api ✓ created workspace change add-auth targets: app, api (aliases must exist in workspace.yaml) ├─ proposal.md (shared) ├─ design.md (shared) ├─ tasks/coordination.md (shared) ├─ targets/app/ (tasks.md, specs/) └─ targets/api/ (tasks.md, specs/)

Key invariant: no repo-local openspec/changes/ gets written yet. The target folders are planning drafts inside the workspace. The repos haven't been touched. That's important for the next act.

Chapter 07 — Act III · Materialization, the authority handoff (10–12)

When a target becomes a repo-local change.

This is the most interesting decision in the POC. Phase 10 spent an entire research phase locking down the contract in a DECISION.md before any code:

apply is create-only. No overwrite. No refresh. Explicit failure on repeat-apply. A minimal sidecar trace in .openspec.materialization.yaml for later roll-up. notes/workspace-poc/phase-10-materialization-contract-research/DECISION.md

Then phase 11 shipped it. openspec apply --change add-auth --repo service-a stages the bundle under openspec/changes/ with an atomic rename, writes the sidecar, and says so explicitly in the CLI output. The handoff isn't implicit:

Before apply

The workspace target slice is the planning truth for that repo. Edits happen in changes/add-auth/targets/service-a/.

After apply

The repo-local change is the execution truth for that repo. Edits happen in service-a/openspec/changes/add-auth/. The workspace slice is no longer authoritative.

Create-only is a constraint, not a limitation. It means v0 never has to answer "what gets overwritten and what gets preserved on refresh" — a question the team explicitly deferred until someone has used the create-only version first.

Chapter 08 — Act IV · Status, honestly (13–15)

Five states, derived from what's actually on disk.

Phase 13's research made a contrarian call: don't reuse the artifact-graph status primitive. Workspace changes don't have repo-local artifact paths to infer from — you'd be making things up. Instead, derive state from two concrete signals: task checkboxes, and materialization provenance from the sidecar.

planned
drafted, not applied
in-progress
some work done
blocked
real issue detected
soft-done
all work complete
hard-done
explicitly archived

Blocked is where the honesty lives. Missing overlay entries, stale repo paths, invalid materialization traces, unreadable task files — all reported. No "unknown" fallbacks that pretend things are fine.

And soft-done ≠ hard-done on purpose. Soft-done means the work looks finished. Hard-done requires an explicit human action. That action is the next chapter.

Chapter 09 — Act V · Archive, explicitly (16–17)

Hard-done is a marker, not an accumulation.

A workspace change becomes hard-done when, and only when, the user runs openspec archive <id> --workspace. That writes a single field to the workspace change's metadata:

$ openspec archive add-auth --workspace ✓ wrote workspaceArchivedAt: 2026-04-17T09:14:22Z status(add-auth) → hard-done

Repo-local archive — running archive inside a target repo — has zero effect on workspace completion. Without that rule, hard-done would silently accumulate from repo-local actions taken by different people at different times. The workspace owner would lose the one place they can say "this cross-repo effort is done."

Chapter 10 — The twist

Phase 20 was an audit. It found three gaps.

After Archive, it looked done. Phase 19 even ran a consolidated acceptance suite — happy path, interruption/re-entry, failure recovery — all passing. Signoff was next.

Instead, phase 20 audited the shipped POC against the PRD line by line and came back with three concrete product gaps:

1
Owners weren't visible. The PRD promises "identify affected repos, owners, and next actions" — but nothing recorded owners.
→ phase 21–22
2
No workspace guidance in docs. The PRD expects users to "recognize when workspace mode is the right tool" — but nothing in the shipped docs told them.
→ phase 21–22
3
No target-set adjustment post-creation. The PRD expects "adjusting targets and continuing" — but targets were locked at creation.
→ phase 23–24

Four remediation phases got inserted before sign-off: owner metadata + docs + an update-repo command (21–22), and workspace targets --add/--remove with a guardrail that blocks removal after repo-local execution (23–24). Phase 25 did the final audit and flipped the PRD from Draft to Signed Off.

Chapter 11 — The resulting CLI

Nine commands. One coherent flow.

This is what a user actually types. The first three lines are one-time workspace setup. Everything after runs inside the same persistent workspace, and you'd run the change → apply → status → archive block again per cross-repo feature, reusing the same workspace over and over:

# ── one-time setup for this coordination scope ── $ openspec workspace setup # guided: create + add-repo + doctor + pick agent (manual equivalent, if you'd rather script it:) $ openspec workspace create platform $ openspec workspace add-repo app ~/code/app --owner @alice $ openspec workspace add-repo api ~/code/api --handoff "see #1234" # ── per cross-repo change, inside the same workspace ── $ openspec new change add-auth --targets app,api # partition one plan across targets $ openspec workspace open --change add-auth --agent claude # or --agent codex | github-copilot $ openspec workspace targets add-auth --add docs # (optional) adjust target set later $ openspec apply --change add-auth --repo app # authority handoff: workspace → repo-local $ openspec status --change add-auth # roll-up across targets $ openspec archive add-auth --workspace # explicit hard-done for this change # ── next feature, same workspace ── $ openspec new change rotate-secrets --targets api,docs ...

No new primitive. No new methodology. The workspace is the durable surface that knows how to coordinate a set of repos; change is still what the user reaches for, one per feature.

Chapter 12 — What's next, and how to dig in

The POC is signed off. Here's what was left open.

Phase 18 deferred three real questions to post-POC work — shared-contract promotion, stable project IDs, team-shared workspaces — and designed a backward-compatible migration seam so v0's alias-keyed contract can grow into them later:

# future, additive. doesn't break shipped v0. .openspec/workspace.yaml repos: app: description: Application repo projectId: prj_abc123 # optional, future .openspec/local.yaml repoPaths: app: /Users/me/code/app # v0 shape stays valid projectBindings: # optional, future prj_abc123: path: /Users/me/code/app

If you want to go deeper, each take-away prompt below is a handoff back into Claude against this repo's actual notes:

Walk me through the materialization contract locked in phase 10. Why create-only? What specifically did they defer by refusing to answer the refresh question in v0? Source: notes/workspace-poc/phase-10-materialization-contract-research/DECISION.md.
Explain how workspace status derives state from task checkboxes plus .openspec.materialization.yaml, and why phase 13 rejected reusing the artifact-graph status primitive. Source: notes/workspace-poc/phase-13-status-research/ and src/core/workspace/status.ts.
Replay the full CLI flow for a 3-repo cross-repo change (service-a, client-a, shared-contract) using the actual commands from the shipped POC. Include the authority handoff at apply and what soft-done vs hard-done look like.
Summarize the PRD audit in phase 20: the three gaps found, how phases 21–24 closed each one, and what that says about running a PRD audit before declaring a POC done. Source: notes/workspace-poc/phase-20-prd-audit/ through phase-25-prd-signoff/.
01 / 12