openspec/explorations/workspace-architecture.md
While simplifying skill installation, we identified deeper questions about how profiles, config, and workspaces should work together. This doc captures what we've decided, what's open, and what needs research.
Update: Initial exploration revealed that "workspaces" isn't primarily about config layering—it's about a more fundamental question: where do specs and changes live when work spans multiple modules or repositories?
Before (original proposal):
openspec profile set core|extended
openspec profile install <workflow>
openspec profile uninstall <workflow>
openspec profile list
openspec profile show
openspec config set delivery skills|commands|both
openspec config get delivery
openspec config list
8 subcommands, two concepts (profile + config)
After (simplified):
openspec config profile # interactive picker (delivery + workflows)
openspec config profile core # preset shortcut
openspec config profile extended # preset shortcut
1 command with presets, one concept
$ openspec config profile
Delivery: [skills] [commands] [both]
^^^^^^
Workflows: (space to toggle, enter to save)
[x] propose
[x] explore
[x] apply
[x] archive
[ ] new
[ ] ff
[ ] continue
[ ] verify
[ ] sync
[ ] bulk-archive
[ ] onboard
One place to configure both delivery method and workflow selection.
Profiles as an abstraction allow for future extensibility:
We researched how similar tools handle config layering:
| Tool | Model | Key Pattern |
|---|---|---|
| VSCode | User → Workspace → Folder | Objects merge, primitives override. Workspace = committed .vscode/ in repo |
| ESLint (flat) | Single root config | Deliberately killed cascading - "complexity exploded exponentially" |
| Turborepo | Root + package extends | Per-package turbo.json with extends: ["//"] for overrides |
| Nx | Integrated vs Package-based | Two modes - shared root OR per-package. Hard to migrate from integrated. |
| pnpm | Workspace root defines scope | pnpm-workspace.yaml at root. Dependencies can be shared or per-package |
| Claude Code | Global + Project | ~/.claude/ for global, .claude/ per-project. No workspace tracking. |
| Kiro | Distributed per-root | Each folder has .kiro/. Aggregated display, no inheritance. |
Key insight from ESLint: The ESLint team explicitly removed cascading in flat config because cascading was a complexity nightmare. Their new model: one config at root, use glob patterns to target subdirectories.
Recommendation for profiles/config: Two layers is enough.
~/.config/openspec/).openspec/ or committed to repo)No "workspace" layer needed for config. This matches Claude Code's model.
Keep it simple:
openspec initopenspec init applies current profile to projectThis is explicit and doesn't prevent future features.
The workspace question isn't about config—it's about where specs and changes live when:
OpenSpec currently assumes:
openspec/ per repo, always at rootopenspec/
├── specs/
│ ├── auth/spec.md # Domain-organized specs
│ ├── payments/spec.md
│ └── checkout/spec.md
├── changes/
│ └── add-oauth/
│ ├── proposal.md
│ ├── design.md
│ ├── tasks.md
│ └── specs/ # Delta specs (can touch multiple)
│ ├── auth/spec.md
│ └── checkout/spec.md
└── config.yaml
This works well for single-project repos. But what about:
Imagine a payment system with:
Questions:
SCOPE
│
Narrow │ Broad
(team/module) │ (cross-cutting)
│
┌─────────────────┼─────────────────┐
│ │ │
│ "Our team's │ "Shared │
│ checkout │ checkout │
│ behavior" │ contract" │
│ │ │
────┼─────────────────┼─────────────────┼──── OWNERSHIP
│ │ │
│ Easy: │ Hard: │
│ One team, │ Multiple │
│ one spec │ stakeholders │
│ │ │
└─────────────────┴─────────────────┘
| Domain | Shared Stuff | Specific Stuff | How They Connect |
|---|---|---|---|
| Protobuf | common/ at root | domain/service/ per service | Imports from common |
| Design Systems | Design tokens, component names, APIs | Platform implementations | "Same properties, different rendering" |
| DDD | Shared Kernel | Bounded Contexts | Context mapping defines relationships |
| RFCs | Cross-cutting RFCs | Team-scoped RFCs | Different review processes |
| OpenAPI | Base schemas | Per-service specs | $ref to shared definitions |
proto/
├── common/ # Shared, low-churn types
│ └── money.proto
│ └── address.proto
├── billing/ # Domain-specific
│ └── service.proto
└── checkout/
└── service.proto # Imports from common/
Key insight: "Most engineering organizations should keep their proto files in one repo. The mental overhead stays constant instead of scaling with organization size."
"Components can look quite different between iOS and Android, as they use native app design standards, but still share the same exact properties in code. This is what makes properties so powerful—it's the one source of truth for every component."
Key insight: Shared spec defines the contract (properties, behavior). Platform specs define implementation details (how it looks/works on that platform).
"One context, one team. Clear ownership avoids miscommunication."
Key insight: Specs should have clear ownership. Cross-cutting concerns use a "Shared Kernel" pattern—explicitly shared code/specs that require coordination to change.
openspec/
├── specs/
│ ├── checkout-contract/ # Shared contract
│ ├── checkout-web/ # Web-specific
│ ├── checkout-ios/ # iOS-specific
│ ├── checkout-android/ # Android-specific
│ ├── billing/ # Backend
│ └── ... (50+ specs at root level)
└── changes/
Pros:
Cons:
checkout-*)openspec/
├── specs/
│ ├── checkout/
│ │ ├── spec.md # Shared contract (the "interface")
│ │ ├── web/spec.md # Web implementation spec
│ │ ├── ios/spec.md # iOS implementation spec
│ │ └── android/spec.md # Android implementation spec
│ └── billing/
│ └── spec.md
└── changes/
Pros:
Cons:
checkout/web vs checkout)Open question: What does "extends" mean?
# checkout/ios/spec.md
extends: ../spec.md # Inherits all requirements?
requirements:
- System SHALL support Apple Pay # Adds to base?
monorepo/
├── services/
│ └── billing/
│ └── openspec/specs/billing/spec.md
├── clients/
│ ├── web/
│ │ └── openspec/specs/checkout/spec.md
│ ├── ios/
│ │ └── openspec/specs/checkout/spec.md
│ └── android/
│ └── openspec/specs/checkout/spec.md
└── openspec/ # Root-level for cross-cutting
├── specs/
│ └── checkout-contract/spec.md # Shared contract
└── changes/ # Where do cross-cutting changes live?
Pros:
openspec/)Cons:
openspec/ directories = ???openspec/ roots to manageUse one openspec/ root per project, but allow nested specs within that root for clear ownership and shared contracts.
For multi-repo work, use a workspace manifest to coordinate multiple projects without duplicating canonical specs.
Monorepo shape (single project, nested specs):
repo/
└── openspec/
├── specs/
│ ├── contracts/
│ │ └── checkout/spec.md
│ ├── billing/
│ │ └── spec.md
│ └── checkout/
│ ├── web/spec.md
│ ├── ios/spec.md
│ └── android/spec.md
└── changes/
└── add-3ds/
├── proposal.md
├── design.md
├── tasks.md
└── specs/
├── contracts/checkout/spec.md
├── billing/spec.md
├── checkout/web/spec.md
├── checkout/ios/spec.md
└── checkout/android/spec.md
Multi-repo shape (multiple projects + workspace orchestration):
~/work/
├── contracts/
│ └── openspec/
│ ├── specs/checkout/spec.md
│ └── changes/add-3ds-contract/
├── billing-service/
│ └── openspec/
│ ├── specs/billing/spec.md
│ └── changes/add-3ds-billing/
├── web-client/
│ └── openspec/
│ ├── specs/checkout/spec.md
│ └── changes/add-3ds-web/
├── ios-client/
│ └── openspec/
│ ├── specs/checkout/spec.md
│ └── changes/add-3ds-ios/
└── payments-workspace/
└── .openspec-workspace/
├── workspace.yaml
└── initiatives/add-3ds/links.yaml
workspace.yaml lists projects/roots. links.yaml maps one cross-cutting initiative to per-project changes.
Canonical specs stay in owning repos; workspace data is coordination metadata only.
Pros:
Cons:
For multi-repo setups, Model C (or the coordination half of Model D) is almost forced:
~/work/
├── billing-service/
│ └── openspec/specs/billing/
├── web-client/
│ └── openspec/specs/checkout/
├── ios-client/
│ └── openspec/specs/checkout/
└── contracts/ # Dedicated repo for shared specs?
└── openspec/specs/
└── checkout-contract/
Where do shared specs live?
Where do cross-repo changes live?
~/.config/openspec/workspaces/my-platform/changes/?How do changes propagate?
checkout-contract affects all client reposIf we add workspace support, it could be:
A workspace is a collection of OpenSpec roots that can be operated on together.
# ~/.config/openspec/workspaces.yaml (or similar)
workspaces:
my-platform:
roots:
- ~/work/billing-service
- ~/work/web-client
- ~/work/ios-client
- ~/work/contracts
shared_context: |
All services use TypeScript.
API contracts follow OpenAPI 3.1.
This would enable:
Option A: No inheritance, just organization
Option B: Explicit inheritance
# checkout/ios/spec.md
extends: ../spec.md
requirements:
- System SHALL support Apple Pay # Adds to base
Option C: References without inheritance
# checkout/ios/spec.md
references:
- ../spec.md # "See also" but no inheritance
requirements:
- System SHALL implement checkout per checkout-contract
- System SHALL support Apple Pay
Option A: Root level (Model B)
openspec/specs/checkout/spec.md is the shared kernelOption B: Dedicated area
openspec/specs/_shared/checkout-contract/spec.mdopenspec/specs/_contracts/checkout/spec.mdOption C: Separate repo (Model C for multi-repo)
contracts or specs repoIf we introduce workspaces:
| Concept | Definition |
|---|---|
| Project | Single OpenSpec root (one openspec/ directory) |
| Workspace | Collection of projects that can be operated on together |
A workspace would enable:
Question: Do we need explicit workspace tracking, or just ad-hoc multi-root (like Claude Code's /add-dir)?
If checkout-web depends on checkout-contract:
checkout-contract warn about downstream specs?Trade-off:
For monorepos (Model B):
specs/For multi-repo (Model C):
Based on research, teams love:
Ambitious:
OpenSpec automatically understands your repo structure, detects cross-cutting specs, and helps you create changes that flow to the right places.
Simpler:
You organize specs however you want. OpenSpec just works.
Practical:
Nested specs for organization. Explicit dependencies for cross-cutting. No magic.
Don't solve spec organization now. Keep scope to:
openspec init improvementsA separate change to explore and implement:
If needed, a separate change for:
For OpenSpec, a spec should be treated as a verifiable behavior contract at a boundary:
Include:
Avoid:
Use progressive rigor:
This keeps day-to-day usage lightweight while preserving clarity where failures are expensive.
OpenSpec is often agent-authored from human exploration. To make that reliable:
In short: humans shape intent; agents produce consistent, verifiable contracts.
To avoid losing this in exploration notes, codify it in:
docs/concepts.md for human-facing framingopenspec/specs/openspec-conventions/spec.md for normative spec conventionsopenspec/specs/docs-agent-instructions/spec.md for agent-instruction authoring rulesAfter evaluating the models above against real multi-repo use cases (see #725), we converged on the following design direction.
The workspace itself is not the durable thing. For large teams, the durable planning object is the initiative or plan, while repo-local specs and changes remain the execution artifacts owned by each repo. The set of repos involved in a feature is typically feature-scoped and changes over time, so a static workspace manifest that must be configured before work begins creates ceremony that doesn't match how teams actually work.
Choose Model D (Hybrid) from Part 4, but make the workspace manifest optional and lazy, not prerequisite.
openspec/ — no change to the fundamental storage model.For larger multi-team work, repo-centric planning is the wrong primary abstraction. Teams and repos are many-to-many facets over the same work. OpenSpec should treat the initiative / plan as the first-class planning object, then link repo-local changes to it.
This is especially important because a change today bundles:
proposal.mddesign.mdtasks.md.openspec.yamlThat bundled shape works well for repo-local work, but becomes awkward when one piece of work spans multiple repos or teams. In those cases, a single repo-local change is trying to act as both:
Those should be split.
The preferred model is:
coordination workspace /
.openspec-workspace/
workspace.yaml
initiatives/
add-3ds/
initiative.yaml
proposal.md
design.md
links.yaml
repo-A/
openspec/
changes/
add-3ds-api/
.openspec.yaml
tasks.md
specs/
repo-B/
openspec/
changes/
add-3ds-web/
.openspec.yaml
tasks.md
specs/
The initiative holds the shared planning layer:
Each repo-local change holds the execution layer for that repo:
Cross-repo linking still matters, but it should hang off the initiative and the repo-local changes:
# billing-service/openspec/changes/add-3ds/.openspec.yaml
schema: spec-driven
created: 2026-04-12
initiative: add-3ds
links:
- project: github.com/fission/web-client
change: add-3ds-checkout
- project: github.com/fission/ios-client
change: add-3ds-checkout
Each repo still holds its own change with its own deltas. A cross-repo effort is represented as one initiative plus N linked single-repo changes. This is preferable to a single mega-change because:
For small single-repo work, a repo-local change may still be "good enough" as both plan and execution bundle. The initiative-first split matters once work becomes cross-team, cross-module, cross-repo, or otherwise coordination-heavy.
Cross-repo links must use stable project identifiers, not filesystem paths.
host/org/repo tuple (e.g., github.com/fission/web-client).org/repo (e.g., fission/web-client) and infers the host from the current repo's remote.The CLI resolves project identifiers to local paths using an offline-first chain:
~/.config/openspec/ or ~/.local/share/openspec/ (see src/core/global-config.ts).The registry is populated progressively: when the CLI discovers a clone (via scanning or user prompt), it persists the mapping for future resolution. The registry also stores "known scan roots" (e.g., ~/work/) so scanning improves over time without upfront configuration.
Spec-level cross-repo references are documentation-only pointers:
# web-client/openspec/specs/checkout/spec.md frontmatter
references:
- project: github.com/fission/contracts-service
spec: checkout-contract
lint, doctor, or a feature flag — not baseline behavior.This avoids accidentally committing OpenSpec to a full dependency graph system before the use cases justify it.
When a spec cannot be mapped to a single implementation repo (e.g., a shared API contract):
| Concern | Monorepo | Multi-Repo |
|---|---|---|
| Spec organization | Nested specs inside one openspec/ (Model B) | Each repo has its own openspec/ |
| Cross-cutting specs | Nested under a contracts/ or shared/ directory | Dedicated owner repo, others reference it |
| Planning object | Initiative optional for simple work, useful for large cross-team efforts | Initiative is the primary coordination object |
| Changes | One or more repo-local changes can implement one initiative | Linked per-repo changes implement one initiative |
| Relationships | References (no inheritance in v1) | Project identifier links, informational only |
| Workspace | Usually not needed, but can host initiative planning for complex work | Coordination workspace hosts initiative planning; optional manifest for reuse |
references field for cross-repo spec pointers.Nested specs (Model B inside a single repo) are a prerequisite for clean monorepo support and should be tackled first, as outlined in #662.
| Question | Status | Notes |
|---|---|---|
| Profile UX | Decided | openspec config profile with presets |
| Config layering | Decided | Two layers: global + project (no workspace layer) |
| Spec organization | Direction set | Nested specs per repo, explicit owner repos for shared contracts, references for cross-repo context |
| Spec philosophy | Direction set | Behavior-first contracts, progressive rigor, and agent-aligned authoring |
| Spec inheritance | Decided | References only, no inheritance in v1 |
| Initiative / planning model | Direction set | Initiative-first planning for larger work, with repo-local changes as execution artifacts |
| Multi-repo support | Direction set | Linked per-repo changes under shared initiatives; workspace is coordination, not canonical execution storage |
| Dependency tracking | Decided | Out of scope for v1; references are informational only |
| Cross-repo resolution | Decided | Offline-first resolution chain with local registry |
| Shared contracts | Decided | Explicit owner repo required; no default pure-spec-repo pattern |
The "workspace" question is really two separate questions:
These should be separate changes with separate explorations.