# ADR 0034: AGENTS.md is an untracked build artifact, guarded by a currency check

> **Amendments.**
>
> - **Vocabulary ([ADR 0120](0120-launch-verb-canon.md), [ADR 0169](0169-the-launch-glossary-canon.md)):** current pointers use `done` (formerly `finish`), `accept` (formerly `graduate`), and `Agent file` (formerly `Compiled agent file`); the decision and reasoning are unchanged.
> - **[ADR 0128](0128-enumerated-ownership-tracked-guidance.md) — tracking reversed:** the tracking default is reversed — the Agent files (canonical and mirrors) are now **tracked** by default, and the managed ignore block enumerates only materialized/local paths. This record's surviving contributions are the banner removal, the stateless currency check, and the tracked-artifacts guard (now scoped to the enumerated block).

**Status**: accepted; the tracking default is superseded by [ADR 0128](0128-enumerated-ownership-tracked-guidance.md) (the currency check and tracked-artifacts guard remain in force); supersedes the _tracking_ decision in — and **consolidates** — [ADR 0032](_superseded/0032-claude-md-imports-agents-md.md) (whose surviving contribution, the `@AGENTS.md` pointer, is described in §1–2 below), and revises the `tracked` flag from [ADR 0031](0031-typed-provider-integration.md). Builds on the orientation verb from [ADR 0033](0033-status-verb-and-location-aware-scope.md).

## Context

`discern refresh` compiles one body — `[built-in base] + [a section per enabled
feature] + [your sources]` — and writes it to every provider file in `[guidance].agents`. Two things sat on top of that body to discourage edits:

1. **A do-not-edit banner** at the top of every generated file (an HTML comment — "GENERATED by discern refresh — do NOT edit this file"), and a second, longer banner inside the `CLAUDE.md` pointer.
2. **`AGENTS.md` was git-tracked** (ADR 0031/0032: "the one tracked file, so guidance changes show in review"), with a CI `git diff --exit-code` catching a stale copy.

Both exist for one reason: coding agents reflexively edit their own instruction file, and an edit to a _generated_ file is silently overwritten on the next `refresh` — lost unless the agent knew to put it in the source instead.

Two problems surfaced:

- **The banner burns prime context for no one who benefits.** Claude Code strips HTML comments before the model sees them, so the banner is invisible to the agent it most needs to deter — while Codex and most other agents receive it at the top of the file, where attention is most valuable. The banner is also **redundant**: the always-included `base.md` already carries the never-hand-edit guidance _in the body_, visible to every agent (it is prose, not a comment).
- **Tracking a derivative invites the confusion it was meant to prevent.** The meaningful, reviewable artifact is the **source** (`guidance.md` + the config), which is already tracked. `AGENTS.md` is a pure function of those; committing it adds a second copy that drifts, shows up in every guidance diff twice, and still does not stop an agent from editing it.

A "don't edit me" rule is better **enforced than asserted**. And the enforcing check is not really about hand-edits at all — it is the general property _"the generated files match what the sources would produce right now,"_ which also catches the far more common case: you edited `guidance.md` or flipped a `[features]`/config value and forgot to `refresh`.

## Decision

**1. Drop the do-not-edit banners.** The compiled body starts at its first real heading; the `CLAUDE.md` pointer is just `@AGENTS.md`. The in-body "never hand-edit" section in `base.md` carries the message to every agent, Claude included.

**2. `AGENTS.md` is untracked by default.** All Agent files (`AGENTS.md`, `CLAUDE.md`, `GEMINI.md`) are gitignored — `AGENTS.md` is a build artifact like the rest. The seed `.gitignore` ignores it; a schema-8→9 migration adds the ignore to existing installs and notes the one-time `git rm --cached AGENTS.md`. The `Provider.guidanceFile.tracked` flag — whose only functional job was naming the **canonical** pointer target — becomes `canonical`, decoupling "the full-body file other mirrors import" (still `AGENTS.md`) from git-tracking (now uniform: none). The `@AGENTS.md` import resolves a local file regardless of git status, so the pointer mechanism is unchanged.

**3. A "generated artifacts are current" check, surfaced at two strengths.** `renderAgentFiles` computes the expected content of each agent file; it is the **single source** both the writer (`compileGuidelines`) and the checker (`checkGuidanceCurrent`) use, so the check can never disagree with what `refresh` writes. The check is **stateless** — recompile in memory, compare to disk — so there is no stored hash to keep in sync. It distinguishes:

- **stale** — the file exists but its bytes differ from the recompiled body;
- **missing** — the file is absent (the expected state of an untracked artifact on a fresh checkout).

It is surfaced as:

- **`discern status`** — an advisory hint on _either_ condition (read-only observation; never blocks).
- **`discern done`** — a built-in gate step (a `guidance-check`, disposition `gate`, like the merge check) that **blocks on `stale` only**, gated on the `guidance` feature. A `missing` file is _not_ a failure: an untracked artifact is legitimately absent on a fresh clone, so blocking it would break first-checkout CI for every consuming project. The failure carries a `Diagnostic` whose `reproduce_cmd` is `discern refresh`, a capped diff of what refresh would change, and the redirect: _edits belong in your `[guidance].sources`, not the generated file._

**4. A "generated/local artifacts stay untracked" check.** The currency check catches byte drift, but it cannot catch a byte-current artifact that was forced into Git with `git add -f`. `discern status` therefore reports `tracked_ignored_artifacts` for any tracked path matched by discern's own generated/local ignore model, and `discern done` blocks with `failed_stage: "tracked_artifacts"`. The diagnostic tells the agent to remove the paths from the index with `git rm -r --cached -- <path...>`, then run `discern refresh`. The detector is derived from the same provider-registry artifact set and canonical `.gitignore` block as setup/upgrade, so a new generated file or skills directory auto-enrols.

## Consequences

- **Prime context reclaimed.** The most valuable tokens now open with real guidance, not a deterrent the deterred agent can't even see.
- **The source is the reviewable unit.** Guidance changes show up in review as `guidance.md`/config diffs — the thing a human should actually read — instead of as a regenerated `AGENTS.md`. The derivative no longer churns the tree.
- **The deterrent is now real and self-explaining.** An agent that edits `AGENTS.md` is told, at `done` time, exactly what it would lose (the diff) and where the edit belongs. The same check doubles as the guard that config and guidance edits were actually compiled.
- **CI shifts off the tracked-file trick.** The `git diff --exit-code` guard for a stale `AGENTS.md` (ADR 0032) is replaced by the `done` check. Because the check blocks on `stale` and not `missing`, a fresh checkout with no `AGENTS.md` stays green; drift is caught the moment the file exists and disagrees.
- **Forced tracking is self-healing.** If an agent overrides the ignore block and stages a generated/local artifact anyway, the next `status` names it and the next `done` refuses to bless the branch until the index is repaired.
- **One self-healing path.** `renderAgentFiles` being the single renderer means a future change to the compile (a new feature section, a provider) is reflected in the check automatically.
- **Extended by [ADR 0035](0035-guidance-templating-engine.md).** The compile now templates the built-in sections against a context derived purely from committed config; routing it through the same `renderAgentFiles` keeps this currency check authoritative, and the config-only context is what keeps the recompile deterministic.

## Alternatives considered

- **Keep the banner, just shorten/relocate it.** Rejected: any banner in the generated file is still wasted on the agents that can see it and invisible to the one that can't, and `base.md` already says it in-band.
- **Keep `AGENTS.md` tracked; add only the check.** Viable for a project that wants the compiled body visible in its own diffs — tracking is a per-project `.gitignore` choice, so such a project simply does not ignore `AGENTS.md`. Rejected as the shipped default, and not taken by discern itself (it dogfoods the untracked default): tracking a derivative is the confusion this removes, the source (`guidance.md`/built-ins) is already the reviewable diff, and the currency check makes the tracked-file guard redundant. Untracking also stops a regenerated `AGENTS.md` from dirtying the tree every time a worktree refreshes.
- **Warn only in `status`, but let `done` pass.** Rejected: status is the right orientation surface for the next agent, but the branch should not be able to accept with a known generated/local artifact in history. The gate is the durable guardrail.
- **Block `done` on `missing` too.** Rejected: an untracked artifact is expected to be absent on a fresh checkout, so blocking would red-light first-run CI for every consumer. `status` still surfaces `missing` advisorily, and the normal flows (worktree create, init, upgrade) all run `refresh`, so a working tree has the file.
- **Auto-heal: run `refresh` in the `fix` stage.** Rejected: silently regenerating would erase a deliberate hand-edit before anyone saw it — the opposite of the rescue. The check shows the diff first and lets the agent move the intent to the source.
- **Store an expected hash and compare.** Rejected: a second source of truth to keep in sync. Recompile-and-compare is stateless and always correct.
