ADR 0070: An agent that reads the canonical AGENTS.md is modelled as "reuse-canonical"
Status: accepted; extends ADR 0034 (the one-canonical-file model) and ADR 0043 (registry-derived aggregators)
Context
§discern compiles one canonical agent file — AGENTS.md (codex) — that holds the full guidance body, and emits each other configured agent's file as a @AGENTS.md pointer (Claude Code's CLAUDE.md, Gemini's GEMINI.md), so the body lives in exactly one place and the mirrors can't drift (ADR 0034/0043).
Agents such as Cursor and GitHub Copilot change the shape of the problem: each reads the canonical AGENTS.md natively, with no file of its own. So for those agents discern should emit no provider-specific guidance file: not a duplicate body, not even a pointer. The registry had no way to say that. Every Provider carried a guidanceFile discern writes; the only states were "canonical full body" and "pointer mirror".
The naïve workaround — point a second provider's guidanceFile.path at AGENTS.md — is a trap. The aggregators (allGuidanceFilePaths, agentArtifactPaths) and the writer all iterate the configured providers; a second provider whose path is AGENTS.md would leak a duplicate AGENTS.md into the gitignore/neutral-scope sets and make the writer emit the file two or three times. The duplicate is silent and exactly the kind of drift the single-source policy forbids.
Decision
§A provider that reads the canonical file natively is modelled explicitly as guidanceFile.reuseCanonical = true, and every emit site and aggregator gates on a single predicate so the file is produced and counted exactly once.
GuidanceFilegainsreuseCanonical?: boolean. Itspathnames the canonical file it reads (AGENTS.md), but it is mutually exclusive withcanonicalandpointer: discern writes no provider-specific file for it. If a configured set contains a reuse-canonical provider but no canonical provider, the renderer emits that path once as the canonical full-body file.emitsGuidanceFile(gf)is the ONE predicate ("does this entry write its own provider file?"), false only for a reuse-canonical entry. Every consumer gates on it:emittedGuidancePaths(files)— the shared core behindallGuidanceFilePaths()— collapses reuse-canonical entries into the canonical path when a canonical provider is present, and emits that path when no canonical provider is configured, soAGENTS.mdappears once;renderAgentFiles's pure coreagentFileContents(files, body)gives that synthesized canonical path the full body and points mirrors at it;- the writer (
compileGuidelines) writes the rendered map, so it cannot skip a synthesized canonical file by re-checking provider rows.
- The gitignore/neutral-scope satellites read the deduped aggregators, so a reuse-canonical provider's read path stays covered (the canonical provider contributes it) without a second rule.
The explicit nos:
- No re-aiming a second
pathatAGENTS.mdwithout the flag. That is the trap above; the flag + theemitsGuidanceFilegate is what prevents the leak. - Exactly one provider stays
canonical. A parity guard asserts it, and that a reuse-canonical provider'spathequals that one canonical path (it genuinely reuses the canonical, not an arbitrary file). - No provider-specific duplicate. Cursor and Copilot can be configured without Codex and still get
AGENTS.md; they still do not get their own separate guidance file.
Consequences
§- Adding a canonical-reading agent is one flag.
reuseCanonical: true(plus the registry entry) wires it end-to-end: it gets the shared skills dir and the guidance it reads (AGENTS.md), and discern writes nothing redundant. - The duplicate-
AGENTS.mdfailure mode is structurally impossible. The singleemitsGuidanceFilegate and the path-keyed content map mean a reuse-canonical provider cannot leak a second write or a second aggregator entry — and a parity guard fails the build if the canonical invariant is ever broken. - The pure cores are testable without an install.
emittedGuidancePathsandagentFileContentstake the guidance entries as input, so a synthetic reuse-canonical set proves "no duplicate write, correct aggregator output, gitignore coverage" directly. - A reuse-canonical-only set still gets guidance. If only Cursor or Copilot is configured,
AGENTS.mdis emitted with the full compiled body instead of silently producing no guidance files.
Alternatives considered
§- Make
guidanceFileoptional (absent ⇒ reads the canonical). Rejected:guidanceFile.pathis read across the renderer, the writer, doctor, the init prompt, and the aggregators; making it optional scatters?.-guards everywhere for a state better expressed as one flag on the existing shape (mirroring howcanonical/pointer?are already modelled there). - A distinct
GuidanceFileunion variant with nopath. Cleaner in the abstract, but the same wide.pathread-surface would need a discriminant check at every site; a boolean flag plus one shared predicate is the lighter tie. - Let a reuse-canonical provider point at
AGENTS.mdand dedupe downstream only. Rejected: the dedup would have to be re-implemented in each aggregator and the writer, and forgetting one reintroduces the silent duplicate. The singleemitsGuidanceFilegate is the one place the decision lives.