# AGENTS.md is advisory. Here's what it takes to make it executable

> Source: <https://dev.to/artyhoo/agentsmd-is-advisory-heres-what-it-takes-to-make-it-executable-111a>
> Published: 2026-07-10 17:48:36+00:00

AGENTS.md is a great convention: one predictable place where a coding agent learns your build commands, style, and boundaries. Over 60k open-source projects ship one.

But look at what the format actually promises. From the official FAQ at [agents.md](https://agents.md/): "AGENTS.md is just standard Markdown. Use any headings you like; the agent simply parses the text you provide." And: "Treat AGENTS.md as living documentation."

*Parses the text.* Nothing checks whether the text is true, and nothing fails when the agent ignores it. Every claim in your AGENTS.md ("we never read `process.env`

directly", "these rules auto-load", "run make self-audit") is prose. Prose drifts. Agents skip prose under context pressure, and nobody notices, because a document that lies looks exactly like a document that doesn't.

Docs lie; tests don't. So what does it take to make an AGENTS.md that *can't* lie?

Three properties, each mechanical:

This is not a mock-up. It's the root [ AGENTS.md](https://github.com/artyhoo/getff/blob/main/AGENTS.md) of the getff repo.

**Claim 1, AGENTS.md:76:** "Read configuration through the injected config accessor, never

`std::env::var`

directly" carries the line:

```
> Enforced: cargo-clippy-toml ✅ · npm-eslint-declarative — FF7001 (typed rules are not
> expressible in the no-restricted-syntax declarative class; route to a type-aware backend)
```

The ✅ isn't decoration. `packages/core/composition/demo/root-agents-demo.test.ts:97`

backs it with the live-fired cell of the cargo capability matrix. And note the second half: where the npm backend *can't* enforce it, the doc says so with an error code (FF7001) instead of pretending.

**Claim 2, AGENTS.md:82:** the mirror convention for TypeScript ("never

`process.env`

directly") is enforced by the ESLint backend. The test at `root-agents-demo.test.ts:73`

feeds the "Never (fires)" example (`const url = process.env.DATABASE_URL;`

) to the real generated `no-restricted-syntax`

rule and asserts it fires; `:86`

asserts the "Always (clean)" accessor form stays silent. If someone edits the example into something the rule no longer catches, the suite goes red.**Claim 3, AGENTS.md:26-51:** the rule index (20 rules, each with class and enforcement channel) sits inside a generated region.

`scripts/render-rule-index.mjs --check`

re-renders it from the actual rule files and exits 1 on any drift, wired into the pre-push hook (`packages/core/hooks/pre-push.ts:792-799`

). You physically can't push an AGENTS.md whose rule table disagrees with the rules on disk.On top of all three sits a ratchet: `root-agents-demo.test.ts:41`

re-composes the whole demo region and requires the committed bytes to be equal. One `make self-audit`

(`Makefile:3`

) runs the lot.

Comment out the rule a claim references, run `make self-audit`

, and the gate fails naming the specific claim: not "docs may be stale", but "this enforcement line no longer matches reality".

The point isn't the tooling; it's the inversion. In a normal repo, the doc describes the checks and rots independently of them. Here the doc is *downstream* of the checks: enforcement status is derived from live outcomes, so the earliest reachable channel (pre-push, not a human reader six weeks later) catches the lie.

This works today on our own repository: the executable AGENTS.md you can open and audit is ours. Generating *your* executable AGENTS.md from *your* conventions is the next milestone, not a shipped feature.
