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?

AnswerOwnershipTypical content
YesShared boundary contractAPI fields and error semantics, cross-client journeys, inter-service event contracts
NoLocal implementation Spec / changeComponent 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.

LayoutWhat it appears to solveWhat 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 submodulesVisible in the workspaceWithout its own git history and review boundary, it is a convenient folder—not a versioned contract owner
A copy in every repoLocal autonomyDrift; 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:

  1. 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.
  2. 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:

  1. A store is an ordinary git repo — commit, push, and PR review stay human; the tool never syncs for you.
  2. Declarations change visibility, not action scope by stealth — a store: pointer or references: read-only link changes where commands default and what instructions can index—not a silent fan-out of implementation tasks into other repos.
  3. 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.