Where this comes from
Discord feedback from henrik_denmark (thread), at the end of a longer conversation about running one store across several code repos:
One thing I doubt is the right choice […] is the notion that the store should not know anything about the repos involved. In order to effectively know which tasks group where, it needs to know about the purpose of each repository so that it does not need to be specified in each change. For now I put it in the context part of the openspec config and in the CLAUDE.md of the store repository. I would lean towards having a standard way of describing it, but one can also argue it just belongs in the context.
This issue generalizes that into a design discussion for the stores beta.
Problem
Stores are deliberately unaware of their consumers. All pointing goes one way — code repo → store, via store: and references: in openspec/config.yaml — and the user guide states the invariant plainly: "Declarations, not machinery." That keeps boundaries clean, but it leaves a gap on the planning side:
The repo landscape is stable per team, yet it must be restated per change. Every change planned in a store that fans out to multiple repos needs the same background — which repos exist, what each one is for, and therefore where each task belongs. Today the agent planning tasks.md either gets that respelled in every proposal, or it gets smuggled in as free text (the context: field, the store's CLAUDE.md).
What exists today (current main)
| Mechanism |
Direction |
Shape |
What reads it |
store: pointer (project-config.ts) |
code repo → store |
store id string |
root resolution (fallback) |
references: |
code repo → store |
id + optional remote |
instructions (spec index), doctor, context |
Store identity (.openspec-store/store.yaml, foundation.ts) |
store, about itself |
strict schema: version, id, remote — unknown keys are a parse error |
registration, health checks |
context: in the resolved root's config.yaml |
free text, ≤50KB |
injected verbatim into every artifact instruction (instruction-loader.ts) |
planning agents |
defaultStore (global config) |
machine → store |
store id string |
root resolution (last fallback) |
| Worksets |
personal, uncommitted |
member folder paths |
workset open |
Two observations worth anchoring the discussion on:
- The workaround works — by accident of root resolution. When commands resolve to the store, it is the store's own
openspec/config.yaml context: that gets injected into planning instructions. So describing your repos there does reach the agent. But it is an opaque blob: agents may use it, no command can. Nothing can group, validate, scope, or health-check against it.
- There is no structured, committed place for this even if we wanted one. The store's identity file has a strict schema, so a hand-added
repos: key there fails parsing today. The project config schema has no repo-shaped field either.
Concept: a committed repo directory in the store
A standard, structured way for a store to say "here are the repos that build what I plan, and what each is for" — committed and shared like everything else in the store:
# lives in the store; exact home is design question 1
repos:
- id: api-server
purpose: REST API and business logic. Owns the OpenAPI contract.
remote: git@github.com:acme/api-server.git
- id: web-app
purpose: Customer-facing SPA. Consumes the API; no business logic.
The "declarations, not machinery" invariant would hold: this changes what OpenSpec can tell you, never where commands act. No cloning, no orchestration, no cross-repo execution.
What it could unlock
| Capability |
Surface |
| Repo directory injected once into planning instructions — changes stop restating it |
instructions in the store |
Task grouping becomes a convention tooling knows about (## api-server sections in tasks.md), suggestable and checkable |
templates, validate |
| A code repo can identify its slice of a cross-repo change (match itself against a manifest entry) |
apply-side skills, status |
| "Declared but not registered / remote drift" checks with pasteable fixes |
doctor |
| Suggested members when assembling the working set |
context, workset |
Open design questions
- Where does it live? The identity file (strict, currently minimal on purpose), the store's own
openspec/config.yaml (already injected into instructions, but the project schema is repo-agnostic today), or a new committed file (e.g. openspec/repos.yaml)?
- Structured data or blessed convention? Minimum: a documented shape that renders into instruction context — no new readers. Maximum: a typed schema consumed by
instructions, validate, doctor, and apply-side tooling.
- How does a code repo know which entry is itself? Match by git remote, an explicit
repo: <id> alongside store:, or not at all (guidance-only)?
- Relationship to
references:. This is the reverse edge of an existing declaration. Do they stay independent, or should doctor cross-check both directions?
- Does task grouping become semantic? Do
validate/apply-side skills understand per-repo sections, or does grouping stay a documented convention the manifest merely feeds?
- Drift. The manifest is user-maintained and can go stale like any doc. What, if anything, does
doctor verify?
Alternatives considered
Stores are beta and the file formats are explicitly allowed to change shape between releases — which makes now the cheap moment to settle this.
Related
Where this comes from
Discord feedback from henrik_denmark (thread), at the end of a longer conversation about running one store across several code repos:
This issue generalizes that into a design discussion for the stores beta.
Problem
Stores are deliberately unaware of their consumers. All pointing goes one way — code repo → store, via
store:andreferences:inopenspec/config.yaml— and the user guide states the invariant plainly: "Declarations, not machinery." That keeps boundaries clean, but it leaves a gap on the planning side:The repo landscape is stable per team, yet it must be restated per change. Every change planned in a store that fans out to multiple repos needs the same background — which repos exist, what each one is for, and therefore where each task belongs. Today the agent planning
tasks.mdeither gets that respelled in every proposal, or it gets smuggled in as free text (thecontext:field, the store's CLAUDE.md).What exists today (current main)
store:pointer (project-config.ts)references:remoteinstructions(spec index),doctor,context.openspec-store/store.yaml, foundation.ts)version,id,remote— unknown keys are a parse errorcontext:in the resolved root'sconfig.yamldefaultStore(global config)workset openTwo observations worth anchoring the discussion on:
openspec/config.yamlcontext:that gets injected into planning instructions. So describing your repos there does reach the agent. But it is an opaque blob: agents may use it, no command can. Nothing can group, validate, scope, or health-check against it.repos:key there fails parsing today. The project config schema has no repo-shaped field either.Concept: a committed repo directory in the store
A standard, structured way for a store to say "here are the repos that build what I plan, and what each is for" — committed and shared like everything else in the store:
The "declarations, not machinery" invariant would hold: this changes what OpenSpec can tell you, never where commands act. No cloning, no orchestration, no cross-repo execution.
What it could unlock
instructionsin the store## api-serversections intasks.md), suggestable and checkablevalidateapply-side skills,statusdoctorcontext,worksetOpen design questions
openspec/config.yaml(already injected into instructions, but the project schema is repo-agnostic today), or a new committed file (e.g.openspec/repos.yaml)?instructions,validate,doctor, and apply-side tooling.repo: <id>alongsidestore:, or not at all (guidance-only)?references:. This is the reverse edge of an existing declaration. Do they stay independent, or shoulddoctorcross-check both directions?validate/apply-side skills understand per-repo sections, or does grouping stay a documented convention the manifest merely feeds?doctorverify?Alternatives considered
context:blob + store CLAUDE.md. Works today, zero code, but opaque and single-purpose (henrik's "one can also argue it just belongs in the context").tasks.mdheading convention, without any new config surface.Stores are beta and the file formats are explicitly allowed to change shape between releases — which makes now the cheap moment to settle this.
Related