One feature touches web, API, and mobile—whose openspec/ folder should hold the Spec?
In multi-repo or submodule workspaces, that argument returns almost weekly. Some bury it in the “main” repo (usually the backend). Others put a folder next to every submodule so it is visible in the IDE. Others duplicate a Spec in each repo and hope review keeps them aligned. The surface fight is about paths. The real question is: who owns this contract, who may change it, and who treats it as the acceptance anchor?
This continues the line in SDD as the source of truth: when agents generate most of the code, what stays stable is human-written specs. Multi-repo work only sharpens that claim—if the source of truth is fragmented or hosted in the wrong place, neither agents nor people can align.
One test: do the consequences bind other repos?
Do not start with a directory tree. For every Spec you are about to write, ask:
Do the behaviors or constraints in this Spec require people in other repos to comply?
| Answer | Ownership | Typical content |
|---|---|---|
| Yes | Shared boundary contract | API fields and error semantics, cross-client journeys, inter-service event contracts |
| No | Local implementation Spec / change | Component split, internal module boundaries, this repo’s task breakdown |
This is the same instinct ADR practice uses for “do consequences spill outward?”: if other services must honor a constraint, it needs a central or independent home; if consequences are local, keep them in the service. In multi-repo SDD, shared contracts need independent ownership. Physical placement is a projection of ownership—not a substitute for it.
Why three common layouts often misfire
Layouts are not wrong; using a path to fake ownership is.
| Layout | What it appears to solve | What usually breaks |
|---|---|---|
| Inside one submodule | “At least it lives somewhere” | Silently nominates a host repo; other repos become second-class for review and agent entrypoints |
| Sibling folder next to submodules | Visible in the workspace | Without its own git history and review boundary, it is a convenient folder—not a versioned contract owner |
| A copy in every repo | Local autonomy | Drift; agents read conflicting sources of truth; cross-repo features rework |
The sibling folder is especially deceptive: it sits beside web/ and api/ in the IDE and looks cross-cutting. If it is only an ordinary directory under a monorepo root, with no independent review or release cadence, ownership never formed—anyone can edit it, and nobody owns the drift.
Two layers: planning owns contracts; code repos own implementation
A stabler default is not “pick a submodule as host,” but an explicit split:
- Planning layer (shared contracts) — what remains true across repos: boundary protocols, cross-client journeys, the product/contract part of a change. It has its own history and review, treated like real code.
- Code repos (local implementation) — each repo keeps its own changes and tasks: how to land here, what to test, how to split PRs. Local proposals cite the shared contract instead of pasting its body.
Express consumption as declarations, not copies:
- A code repo declares “planning is fully externalized to store X”—commands default there;
- Or it keeps a local
openspec/and read-only references an upstream planning store—local designs stay local; upstream supplies a contract index.
The two declarations solve different problems: the first fits “this product’s spec truth already lives elsewhere”; the second fits “I consume platform/cross-team contracts I do not own.” Shared point: contracts have an owner, implementation has an owner, and pointers connect them.
OpenSpec Stores: planning can be its own repo
OpenSpec Stores (beta) turn that principle into a workable topology—not another ad-hoc folder convention.
The shape is simple:
team-plans (store: planning repo)
└── openspec/specs + changes
▲
│ registered by name; shared via git
┌───────┼────────┐
web-app api-server mobile-app
What matters is not a command checklist, but three boundaries:
- A store is an ordinary git repo — commit, push, and PR review stay human; the tool never syncs for you.
- Declarations change visibility, not action scope by stealth — a
store:pointer orreferences:read-only link changes where commands default and what instructions can index—not a silent fan-out of implementation tasks into other repos. - A workset is a personal workspace, not shared truth — opening the planning repo with several code repos helps agents and people see the set; it does not copy source into the store or define who owns the contract.
So a cross-repo promo, login, or checkout change usually splits like this: approve the shared contract in the planning store first; then open a local change in each code repo whose tasks describe only that repo’s work and cite the approved boundary contract. That matches the intuition that “one Spec covers frontend + backend + mobile”—it covers boundary behavior, not a bound volume of three implementation plans.
For tool spectrum context, see SDD tools compared; for OpenSpec as the truth layer in a delivery stack, see the three-layer stack.
For leads and practitioners
Tech leads: Decide who the contract repo is, who may merge it, and whether cross-repo features must land a contract PR first—before arguing submodule layout. A sibling folder without a review boundary will not stop drift.
Practitioners: On a cross-repo ask, run the one-line test. Shared parts go to the planning layer; this repo only opens an implementation change and cites the upstream contract version. Do not “casually” define API semantics inside the frontend repo.
Anti-patterns
- Implementation tasks in the shared contract — the planning layer becomes a bound task list for three repos; review and archive cannot stay independent per repo.
- Shared field conventions buried only in the frontend repo — wrong host; backend and mobile agents miss them or read stale copies.
- Wiki or hallway sync as the cross-repo source of truth — people may muddle through; agents will not. SDD needs a versioned, citable anchor.
- Worksets mistaken for ownership — opening folders together does not assign a contract owner.
Ask who owns it, then where it lives
In multi-repo SDD, whether Specs sit under a submodule or beside them can be an ergonomics choice. Who owns the contract is the methodology question. Give shared boundary behavior an independent planning layer; keep per-repo implementation local; connect them with declarations. OpenSpec Stores are one clear shape of that idea.
You can retire the directory debate: answer “who owns this contract,” and the path usually follows.