openspec/explorations/workspace-user-journeys.md
This document describes the exact user experience for OpenSpec across:
It is intentionally UX-first. The goal is to define:
This is a proposed UX model, not an implementation spec.
Users should not need one mental model for repos and a separate mental model for monorepos.
OpenSpec should operate on scopes.
A scope is an owned planning and implementation boundary. Depending on the codebase, a scope may be:
The user experience should be:
There are three different durable objects:
Spec A canonical behavior contract. Always stored in the owning scope.
Change A repo-local or scope-local planning artifact. Stored with the owner of the implementation/spec delta.
Initiative A cross-scope coordination artifact. Only needed when work spans multiple owning roots. Stored in a neutral coordination workspace.
The key rule is:
Canonical specs never move into the coordination workspace.
The workspace coordinates. Owner roots remain canonical.
Some behaviors are true across multiple scopes, apps, or repos:
These are not just "references between local specs." They are first-class shared contracts.
OpenSpec should model them explicitly as:
Canonical shared contract A spec that defines what must be true across boundaries.
Local implementation specs Per-scope specs describing how each owner satisfies that contract locally.
Initiative A change or project to update the contract and its implementations together.
The first-principles rule is:
A cross-boundary spec may have many consumers, but it must still have one canonical owner.
If there is no owner yet, the shared behavior can live temporarily as an initiative note during exploration, but OpenSpec should not promote it into a canonical spec until ownership is explicit.
Users should still recognize the existing OpenSpec workflow:
openspec initopenspec update/opsx:explore/opsx:propose/opsx:apply/opsx:archiveThe new behavior is that OpenSpec may ask:
Users should not have to pre-register all repos or modules just in case.
OpenSpec should support:
If a change spans multiple repos, the planning artifact should not be forced into one repo.
Instead, OpenSpec should create a neutral coordination workspace for the initiative.
Cross-scope or cross-repo references should initially be documentation-only.
OpenSpec may surface them to users and agents, but should not require them to resolve or validate across roots in the first version.
When the user describes behavior that spans multiple boundaries, OpenSpec should help them decide whether they are creating:
If the work is a real long-lived contract, OpenSpec should prompt for a canonical owner rather than burying it in whichever repo the user happened to be standing in.
OpenSpec should help teams create shared contract ownership, not ambiguous co-ownership.
That means:
When a user starts in web-client and describes a checkout behavior spanning web, iOS, Android, and backend, OpenSpec should treat the current cwd as a clue about likely consumers, not as proof that web-client should own the shared contract.
A single OpenSpec root where canonical specs and changes can live.
In practice a project may be:
openspec/openspec/A logical boundary inside or across projects. Scopes are what users pick during planning.
Examples:
repocontracts/checkoutapps/webservices/billingmobile/iosA neutral directory used only for cross-root coordination. It stores initiative-level planning data and agent workspace instructions.
Example:
~/work/openspec-workspaces/add-3ds/
.openspec-workspace/
workspace.yaml
initiative.md
links.yaml
agents/
claude.md
codex.md
This coordination workspace may exist in two modes:
Personal coordination workspace A local directory used by one person for ad hoc or exploratory cross-root planning.
Shared coordination workspace A committed coordination repo or shared workspace used by a team or multiple teams.
The UX should support both without changing the core mental model.
The repo or scope that canonically owns a spec, codebase area, or repo-local change.
The scope or repo that canonically owns a cross-boundary spec.
This may be:
This should not be an accidental consequence of where the user started the agent.
repo/
openspec/
specs/
changes/
config.yaml
monorepo/
openspec/
specs/
changes/
config.yaml
apps/
web/
admin/
services/
billing/
packages/
contracts/
Optional scope markers:
monorepo/
apps/web/openspec.scope.yaml
services/billing/openspec.scope.yaml
packages/contracts/openspec.scope.yaml
~/work/
contracts/
openspec/
web-client/
openspec/
ios-client/
openspec/
openspec-workspaces/
add-3ds/
.openspec-workspace/
workspace.yaml
initiative.md
links.yaml
agents/
claude.md
codex.md
OpenSpec needs to answer two separate questions:
These are not the same problem.
OpenSpec discovers candidate scopes from:
openspec/specs/**For multi-repo work, durable identifiers should not be raw paths.
Stable project identifiers should look like:
github.com/Fission-AI/web-clientgithub.com/Fission-AI/contractsUser shorthand may be accepted:
Fission-AI/web-clientfission/web-clientOpenSpec resolves these to local paths using this order:
Relative paths like ../web-client may be used as temporary hints for a single run, but should not be the durable identifier.
For team use, OpenSpec should separate:
Shared coordination state Stable information that can be committed and shared with teammates.
Local resolution state Machine-specific path mappings and local availability.
This separation is critical for a cohesive team story.
Without it, coordination workspaces become either:
This section describes how the same UX should scale from one person to a large org.
The user may create a local coordination workspace and keep everything local.
This is fine for:
Once cross-repo work becomes collaborative, the coordination workspace should be shareable and committed.
That means:
When work spans teams, the initiative needs a clear coordination owner even if specs and code remain distributed.
This is not the same as canonical spec ownership.
There are now two distinct responsibilities:
Shared contract owner Owns the canonical cross-boundary spec.
Initiative sponsor/driver Owns the coordination process for the current change.
These may be the same team, but they do not have to be.
Examples:
OpenSpec should communicate this clearly:
If the work is collaborative, treat the coordination workspace as a lightweight shared planning repo or committed workspace. Commit stable initiative metadata there, and keep machine-specific repo path mappings local.
This gives a clean answer to:
This section answers a key product question:
How should a team or agent decide where a shared cross-boundary spec belongs?
OpenSpec should help teams choose the owner that can most credibly do all of the following:
The owner is not necessarily:
The owner is the boundary where the contract is most stable and most authoritative.
When OpenSpec detects a likely shared contract, it should ask or infer from these questions:
Example:
packages/contracts/checkout already exists in a monorepocontracts repo already exists in multi-repo environmentOpenSpec should default to that existing owner.
Example:
OpenSpec should suggest that existing domain owner if there is no dedicated shared area.
Example:
OpenSpec should help create a new shared contract scope.
Example:
OpenSpec should allow the behavior to remain in initiative notes temporarily and explicitly mark it as non-canonical until ownership is chosen.
Default suggestion order:
Default suggestion order:
OpenSpec should not:
When the user describes a likely cross-boundary behavior, OpenSpec should treat this as a first-class planning moment.
Signals that a shared contract may be needed:
During /opsx:propose or /opsx:explore, OpenSpec should ask something like:
This looks like behavior shared across multiple scopes:
- web
- iOS
- Android
- backend
What kind of artifact is this?
- A local change within one scope
- A shared contract that multiple scopes must follow
- An initiative note for now; ownership is not clear yet
If the user chooses shared contract:
Where should the canonical shared contract live?
- Existing shared contracts scope
- Existing domain owner
- Create a new shared contract scope
- Decide later and keep this initiative-only for now
If the user chooses "create a new shared contract scope," OpenSpec should guide them.
Prompt feel:
Suggested new shared scopes:
- openspec/specs/contracts/checkout
- openspec/specs/shared/checkout
- packages/contracts/checkout
Who should own review for this contract?
OpenSpec then:
Prompt feel:
No shared contract owner exists yet.
Choose where the canonical contract should live:
- Create it in an existing contracts repo
- Create a new shared contracts repo later; keep initiative-only for now
- Assign it to an existing domain owner repo
OpenSpec should avoid auto-creating a brand-new repo. It can scaffold the plan and record the decision, but repo creation may be organizationally sensitive and usually belongs outside the CLI.
If ownership is unclear, OpenSpec should support:
This is important because many teams discover the need for a shared contract while exploring, before they know how to govern it.
The product should feel like one system, not three separate features.
No matter the setting, the user experience should always reduce to this:
These things should not change with org size:
If these invariants hold, the UX remains coherent across solo, team, and org-wide usage.
Cross-root UX only works if the agent can actually see the relevant roots.
OpenSpec should support three agent capability levels.
The tool can explicitly attach multiple directories to one session.
Desired UX:
The tool runs from one working directory but can still read sibling absolute paths if the environment permits it.
Desired UX:
The tool can only reliably operate within one repo at a time.
Desired UX:
/opsx:apply runs in each repo separatelyThis must be treated as a first-class case, not a fallback afterthought.
Today the flow is:
openspec init/opsx:explore or /opsx:proposeopenspec/changes/<change>//opsx:apply implements within that repoThat should remain the exact feel for single-repo work.
The only extension is:
The user has one repo:
~/work/acme-app/
They enter the repo:
cd ~/work/acme-app
They initialize OpenSpec:
openspec init
OpenSpec creates:
openspec/
specs/
changes/
config.yaml
They open Claude, Codex, Cursor, or another agent inside this repo.
The user types:
/opsx:propose add-dark-mode
OpenSpec should:
Output feel:
Created openspec/changes/add-dark-mode/
Using scope: repo
Generated:
- proposal.md
- specs/ui/spec.md
- design.md
- tasks.md
Ready for implementation with /opsx:apply
The user types:
/opsx:apply
OpenSpec reads:
Only from this repo.
The user types:
/opsx:archive
OpenSpec archives the change in this repo, as today.
The user has:
~/work/platform/
Inside:
platform/
openspec/
apps/web/
services/api/
packages/ui/
The user enters the monorepo root and runs their agent there.
The user types:
/opsx:propose add-invoice-filtering
OpenSpec detects this is still one project root, but there are multiple candidate scopes.
If the user’s request clearly mentions one area, OpenSpec may infer:
apps/webOtherwise it asks:
Which scope does this change affect?
- apps/web
- services/api
- packages/ui
- shared/contracts
The user picks apps/web.
OpenSpec should:
openspec/changes/add-invoice-filtering/apps/webapps/webThe user should not feel like they are using a different product because they happen to be in a monorepo.
Same monorepo:
platform/
openspec/
apps/web/
services/api/
packages/contracts/
The user types:
/opsx:propose add-3ds-checkout
OpenSpec detects likely affected scopes:
packages/contractsservices/apiapps/webIt asks:
This appears to affect multiple scopes.
Which scopes should be included?
[x] packages/contracts
[x] services/api
[x] apps/web
[ ] apps/admin
[ ] packages/ui
OpenSpec creates:
platform/openspec/changes/add-3ds-checkout/
The change includes scope metadata listing all three selected scopes.
OpenSpec reads:
OpenSpec ignores unrelated specs by default.
The user runs:
/opsx:apply
OpenSpec should:
This is still not a coordination workspace case, because there is still one owning project root.
Some monorepos are operationally equivalent to multi-repo systems:
This means OpenSpec cannot assume:
platform/
openspec/
apps/web/
apps/mobile/
services/billing/
services/orders/
packages/contracts/
Optional scope markers exist:
apps/web/openspec.scope.yaml
apps/mobile/openspec.scope.yaml
services/billing/openspec.scope.yaml
packages/contracts/openspec.scope.yaml
The web team enters the monorepo root, or a repo-aware subtool enters the web area.
The user types:
/opsx:propose add-checkout-loading-state
OpenSpec detects this is limited to apps/web.
The user should experience this exactly like a single-scope change.
The user types:
/opsx:propose add-3ds
OpenSpec detects:
packages/contractsservices/billingapps/webapps/mobileAt this point OpenSpec must make a product decision.
If all affected scopes live under one owning root and the user is comfortable with one monorepo-local change, keep using one root-level change.
If the monorepo is configured or inferred to behave like many owned sub-projects, offer to upgrade to a coordination-style monorepo initiative.
Prompt feel:
This monorepo has multiple independently owned scopes.
How would you like to plan this work?
- One monorepo change
- Coordination initiative with linked scope changes
If the user chooses the coordination flow, OpenSpec creates:
This is the same conceptual flow as multi-repo, but all roots happen to be inside one VCS root.
This prevents the UX from hardcoding "monorepo is always simpler."
That assumption fails for exactly the enterprise-style teams this feature is for.
The user is in:
~/work/web-client
They already use OpenSpec there.
They open Claude in web-client and type:
/opsx:propose add-3ds
During exploration or proposal generation, it becomes clear that the work also affects:
contractsbilling-serviceios-clientAt this point OpenSpec should not silently create one cross-repo change inside web-client.
That would be misleading, because:
web-clientOpenSpec interrupts the default single-repo flow and says:
This work spans multiple owning roots:
- github.com/Fission-AI/contracts
- github.com/Fission-AI/billing-service
- github.com/Fission-AI/web-client
- github.com/Fission-AI/ios-client
For cross-repo work, OpenSpec recommends creating a coordination workspace.
Suggested location:
~/work/openspec-workspaces/add-3ds
Create it now?
If the user agrees, OpenSpec creates the workspace.
If the user wants another location, they can choose it.
Proposed CLI feel:
openspec workspace create add-3ds --at ~/work/openspec-workspaces/add-3ds
OpenSpec writes:
~/work/openspec-workspaces/add-3ds/
.openspec-workspace/
workspace.yaml
initiative.md
links.yaml
agents/
claude.md
codex.md
OpenSpec now resolves local paths for:
github.com/Fission-AI/contractsgithub.com/Fission-AI/billing-servicegithub.com/Fission-AI/web-clientgithub.com/Fission-AI/ios-clientUsing:
OpenSpec then tells the user:
Next step:
1. Open your coding agent in ~/work/openspec-workspaces/add-3ds
2. Attach these roots if your tool supports multi-root:
- /Users/me/work/contracts
- /Users/me/work/billing-service
- /Users/me/work/web-client
- /Users/me/work/ios-client
OpenSpec has generated workspace instructions at:
.openspec-workspace/agents/claude.md
The user now starts the agent in the coordination workspace and runs:
/opsx:propose add-3ds
Or OpenSpec may have already scaffolded the initiative and tell the agent to continue it.
The coordination workspace stores the initiative-level planning object:
Each repo stores its own execution change:
contracts/openspec/changes/add-3ds-contract/billing-service/openspec/changes/add-3ds-billing/web-client/openspec/changes/add-3ds-web/ios-client/openspec/changes/add-3ds-ios/Those repo-local changes are where repo-specific tasks, delta specs, and local implementation state live.
This gives the user a truthful answer to:
The answer is:
The user already knows the work is cross-repo.
They start in a neutral directory:
cd ~/work
They run:
openspec workspace create add-3ds --at ~/work/openspec-workspaces/add-3ds
Or a future higher-level shortcut:
openspec initiative new add-3ds
OpenSpec asks:
Which repos or scopes are involved?
The user enters:
github.com/Fission-AI/contractsgithub.com/Fission-AI/billing-servicegithub.com/Fission-AI/web-clientgithub.com/Fission-AI/ios-clientOpenSpec resolves local clones and writes the workspace files.
The user opens the coordination workspace in their agent.
OpenSpec-generated agent instructions contain:
When the user runs:
/opsx:explore
or
/opsx:propose add-3ds
the agent reads:
Same as Journey 5, but the user never had to start from one repo first.
Some users will intentionally want the initiative to live outside any single repo from the start.
OpenSpec should support that directly.
A team knows the work spans multiple repos and will involve multiple people over several days or weeks.
They create or choose a shared coordination repo, for example:
~/work/openspec-initiatives/
Or a team-owned repo such as:
github.com/Fission-AI/initiatives
Inside it, OpenSpec creates:
initiatives/
add-3ds/
.openspec-workspace/
workspace.yaml
initiative.md
links.yaml
agents/
claude.md
codex.md
The team commits:
Each teammate keeps local path mappings outside the shared repo, for example in OpenSpec local config/data:
github.com/Fission-AI/contracts -> /Users/alice/src/contractsgithub.com/Fission-AI/contracts -> /home/bob/work/contractsEach teammate:
openspec workspace doctor or openspec workspace syncOpenSpec generates agent instructions using:
This means every teammate sees the same initiative structure, but with their own valid filesystem paths.
This is the team-scale version of "enter the coordination workspace."
Without this distinction, the phrase sounds cohesive for one person but falls apart for shared planning.
The work spans:
OpenSpec should support the initiative being created in a shared coordination repo owned by the sponsoring or driving team.
That sponsor is responsible for:
But the sponsor does not automatically own:
add-3dsWhen another team member opens the shared coordination workspace, OpenSpec should make this explicit:
Initiative sponsor:
- payments-platform
Canonical shared contract owner:
- contracts
Participating owners:
- billing-service
- web-client
- ios-client
Without this, "shared ownership" becomes ambiguous and teams do not know whether they are reading:
The user wants to plan work affecting:
But only has these locally:
OpenSpec should still allow planning.
It creates the coordination workspace and records:
contracts and web-clientbilling-service and ios-clientPrompt feel:
Resolved locally:
- contracts
- web-client
Not currently available locally:
- billing-service
- ios-client
Planning can continue with partial context.
Implementation in unresolved roots will remain pending until linked.
The agent should:
The coordination workspace may contain unresolved links such as:
projects:
- id: github.com/Fission-AI/contracts
path: /Users/me/work/contracts
status: resolved
- id: github.com/Fission-AI/billing-service
status: unresolved
- id: github.com/Fission-AI/web-client
path: /Users/me/work/web-client
status: resolved
- id: github.com/Fission-AI/ios-client
status: unresolved
This keeps planning useful even when the environment is incomplete.
Blocking planning here would make the feature brittle.
This also matters for large teams, because not every teammate will have every participating repo cloned or accessible.
This is one of the most important behavioral rules.
OpenSpec should never blindly read all specs from all roots.
Read:
Do not read:
Read:
Do not read:
Read:
Do not read:
References should appear like:
Related references:
- github.com/Fission-AI/contracts: openspec/specs/checkout/spec.md
- github.com/Fission-AI/web-client: openspec/specs/checkout/web/spec.md
The agent can use them as navigation hints.
They are not validation blockers.
When a selected change involves a shared contract, OpenSpec should prefer this read order:
This matters because the shared contract defines the boundary-level truths, while local specs describe how each consumer satisfies them.
This must stay simple and deterministic.
Examples:
contractsweb-clientios-clientExamples:
contracts/openspec/changes/add-3ds-contract/web-client/openspec/changes/add-3ds-web/These changes are the execution artifacts for each owning repo. They should carry repo-specific tasks, delta specs, and local implementation state.
Examples:
The coordination workspace may reference specs and summarize them.
It should not become a second spec source of truth.
If a cross-boundary behavior has not yet been assigned a canonical owner, OpenSpec should store it as initiative-level draft material rather than pretending it is already a canonical spec.
If a coordination workspace is committed for team use, it should contain:
It should not contain:
/Users/... pathsC:\\... pathsThat information belongs in local overlay data.
/opsx:apply In Cross-Root WorkImplementation UX must remain honest about tool limits.
The user is in the coordination workspace.
They run:
/opsx:apply
OpenSpec responds:
This initiative has linked changes in:
- contracts
- billing-service
- web-client
- ios-client
Choose apply mode:
- Apply one linked change
- Apply in suggested sequence
Recommended default:
This keeps task state and implementation context manageable.
The user is in the coordination workspace and runs:
/opsx:apply
OpenSpec should say:
This initiative spans multiple repos.
Implementation must be run per repo in your current tool.
Next suggested step:
- open /Users/me/work/contracts and run /opsx:apply add-3ds-contract
Cross-repo planning and cross-repo implementation are not the same capability.
The UX must not assume all agents can do both well.
The user is in a monorepo with:
platform/
openspec/
apps/web/
apps/ios/
apps/android/
services/billing/
There is no existing canonical checkout shared contract.
The user types:
/opsx:propose add-3ds-checkout
The agent discovers the request spans:
This looks like a cross-boundary behavior shared by multiple scopes.
Should OpenSpec treat this as:
- A shared contract
- Independent local changes only
- An initiative note for now
The user chooses shared contract.
OpenSpec then asks:
Where should the canonical shared contract live?
- openspec/specs/contracts/checkout
- openspec/specs/shared/checkout
- keep it initiative-only for now
OpenSpec creates:
This keeps one source of truth for the cross-boundary behavior, instead of smearing the same logic across web, iOS, Android, and backend specs.
The user is planning a checkout initiative involving:
contractsweb-clientios-clientandroid-clientbilling-serviceThere is no existing shared checkout contract.
The user creates or enters a coordination workspace.
They run:
/opsx:propose add-3ds
OpenSpec detects this likely requires a shared contract.
No canonical shared contract owner was found for this behavior.
Choose how to proceed:
- Create the canonical contract in an existing contracts repo
- Assign the contract to an existing domain owner repo
- Keep it as initiative-only for now
If the user selects an existing contracts repo, OpenSpec:
If the user keeps it initiative-only, OpenSpec:
This avoids a bad default where whichever app team starts the change accidentally becomes the long-term owner of a cross-org contract.
Same as today:
/opsx:archive
The user in the coordination workspace runs:
/opsx:archive
OpenSpec checks linked repo changes:
Prompt feel:
Initiative: add-3ds
Linked change status:
- contracts/add-3ds-contract: complete
- billing-service/add-3ds-billing: complete
- web-client/add-3ds-web: active
- ios-client/add-3ds-ios: unresolved
Archive options:
- Archive completed linked changes only
- Mark initiative partially complete
- Wait until all linked changes are done
Allow partial completion states.
Cross-repo work often lands asynchronously.
The setup path needs to feel lightweight.
cd repoopenspec init/opsx:proposeNo workspace concepts shown.
cd monorepoopenspec initopenspec updateDuring planning, OpenSpec asks for scope selection when needed.
No separate workspace admin step is required.
openspec initThis is important:
The system should not require a platform team to pre-register every repo before any real work can begin.
When OpenSpec creates a coordination workspace, it should generate agent-facing files.
Minimum contents:
claude.mdYou are working on initiative `add-3ds`.
Resolved roots:
- /Users/me/work/contracts
- /Users/me/work/web-client
Unresolved roots:
- github.com/Fission-AI/billing-service
- github.com/Fission-AI/ios-client
Ownership:
- contracts owns shared checkout contract
- web-client owns web checkout behavior
Rules:
- Canonical specs must be edited in owning roots
- Initiative-level notes live in this coordination workspace
- Cross-repo references are informational only
This file should be generated for any supported agent integration that benefits from deterministic startup context.
Example:
web-clientcontracts, web-client, ios-clientExpected behavior:
web-clientExpected behavior:
Expected behavior:
Expected behavior:
Expected behavior:
Expected behavior:
Expected behavior:
Expected behavior:
Expected behavior:
Examples:
Expected behavior:
openspec workspace doctor or equivalent relinks rootsExpected behavior:
Expected behavior:
Expected behavior:
Expected behavior:
Expected behavior:
If OpenSpec adopts these journeys, several design conclusions follow.
Without this, OpenSpec has no honest answer to where the user should stand or where the cross-repo initiative should live.
Without this, OpenSpec creates duplicate or misleading sources of truth.
OpenSpec must support both:
OpenSpec must separately handle:
Cross-root planning only works reliably if OpenSpec writes deterministic workspace context for the agent.
If OpenSpec validates cross-root references by default, it has effectively shipped a dependency graph system.
That should be a later, optional capability.
Without this, teams will either:
OpenSpec should help users choose among:
To support teams and orgs cleanly, OpenSpec should distinguish:
Without that split, the UX will either:
These journeys intentionally leave some implementation choices open.
openspec/ roots, or keep one root and model scopes separately?Convert these journeys into a concrete design proposal covering: