Chapter 5 of 7 · 9 min read
Standing instructions expire when their subject changes
On this page
The annual control ran in the first week of March. Forty-one teams received a ticket titled "AI configuration accuracy attestation", each with their AGENTS.md attached as a PDF and two checkboxes: the contents of this file are accurate as of today and an owner is identified below. All forty-one closed within the three-week window, most of them in under six minutes of recorded time. Compliance was reported at 100% for the second year running.
In the same twelve months the platform group logged three incidents whose root cause was an agent following a standing instruction that was confidently wrong. The events index line, which by then had been shipping full-table date-range fetches for seven months. A rule stating that the payments sandbox rejects amounts above 10,000 minor units, removed by the vendor in a release note eighteen months earlier, which sent an agent into a week of building a splitting scheme for test transactions that did not need splitting. A line asserting that the deploy pipeline has no rollback command, which turned a four-minute revert into a fifty-minute forward fix during an outage. Every one of those files had been attested as accurate in March, by someone who read the attached PDF, recognised it, and clicked the box.
Why the number and the incidents are both real
The attestation — a periodic sign-off in which the owner of a document declares it accurate, familiar from access reviews and audit controls generally — was not fraudulent, and the teams were not lazy. The control measured whether a person would affirm the file, and people affirm files. Chapter 2 put recollection at the bottom of the evidence table, and an attestation is recollection performed on schedule and countersigned, which does not change what it is made of. The engineer confirming in March remembers the index line as something the team decided, and it reads like something a careful team would decide, so it is affirmed. Whether the underlying fact still holds is a question that was never asked, because the form asked a different one.
There is a second failure in the timing, and it is the more structural of the two. The events index landed on 14 January, seven weeks before the March attestation and eleven months before the next one. Whatever a calendar picks, it is uncorrelated with the moment a rule becomes false, so the expected time-to-detection for an annual cycle is around six months even if every attestation were a genuine check. Quarterly attestation moves that to roughly six weeks and multiplies the signature-gathering by four; the arithmetic improves and the mechanism does not, since running an unreliable check more often mostly increases the rate of unreliable checks. It also consumes the same attention that a working control would need, while producing a compliance number that goes up.
It is possible to build a calendar review that is not theatre, and the shape of it shows why the attestation failed. Replace "confirm this file is accurate" with "for each entry, paste the command you ran and its output", and the six-minute click-through becomes a half-day of real work per team, most of which nobody will do. That is the useful discovery hiding in the compliance number: the reason attestation is fast is that it is not asking for anything, and any version that asks for evidence costs so much per cycle that no organisation runs it across forty-one teams. Periodic verification is the wrong unit of work, because it re-checks sixty entries of which perhaps two have changed.
What separates a control that works is that it fires on the event that invalidates the rule rather than on a date. The index line stopped being true at a specific commit, reviewable by a specific person, in a pull request that somebody approved. That is the moment when the fact is in someone's head, the evidence is on screen, and the correction costs thirty seconds.
Binding rules to the changes that invalidate them
Every durable instruction makes a claim about some subject that lives somewhere, and most of those subjects change through a reviewable event.
| Invalidating event | Instruction class it expires | Where the trigger attaches |
|---|---|---|
| Migration adding or dropping an index, column, table | Claims about query shape, table size, what is safe to filter | paths: migrations/** in CI |
| API contract change (OpenAPI diff) | Claims about endpoints, parameters, pagination, limits | Spec diff job in the producing repo |
| Tool or MCP server upgrade | Claims about tool capability, permissions, side effects | Lockfile or server-version change |
| Model pin change | Rules that compensate for a model's habits | The pin lives in the config; changing it fires the check |
| Policy or access revision | Claims about approvals, environments, who may do what | Policy repo change fans out to dependents |
| Pipeline or workflow change | Procedures: manual steps, gates, smoke tests | paths: .github/workflows/** |
The fourth row is easy to skip and causes an unusual amount of trouble. A file accumulates entries that exist to patch a particular model's tendencies — always run the tests before claiming done, do not invent library names, stop and ask before deleting files. When the pin moves to a newer model, some of those become unnecessary and one or two become actively wrong, because the new model's default behaviour is different from the one the workaround assumed. Nothing about a model swap looks like a change to the repository's facts, so nobody thinks to re-read the file, and the entries survive as archaeology.
Wiring the trigger means putting it in the path of the change rather than in a document about the change:
# .github/workflows/config-triggers.yml
name: config-triggers
on:
pull_request:
paths:
- 'migrations/**'
- '.github/workflows/promote.yml'
- 'openapi/**'
jobs:
check-standing-instructions:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Which entries claim something about what you changed?
run: python tools/config_triggers.py --diff origin/main...HEAD
# exits 1 unless every matched entry is re-verified in this PROwnership that survives the person leaving
A trigger needs somewhere to fire, and "the team" is not somewhere. Each entry needs an owner, a date it was last verified, and the evidence that verified it, which raises an immediate practical problem: that metadata is four or five lines per entry, and putting it inline in the config file means the agent reads it on every session. A file of sixty entries would spend more of its budget on provenance than on instructions.
Keeping the metadata in a sidecar — a companion file that sits beside the config, is read by tooling and by people, and is never loaded into the agent's context — joined to each entry by a hash of that entry's own text, avoids that:
# AGENTS.entries.yml — never loaded into the agent's context
- id: events-customer-filter
text_sha256: "7d3a91c0" # hash of the entry text in AGENTS.md
owner: "@team-platform-data"
added: 2024-11-02
last_verified: 2026-08-09
verified_by: "SELECT indexname FROM pg_indexes WHERE tablename='events'"
triggers: ["schema:events", "migrations/**"]
provenance: "INC-3312: full scan of events during Black Friday"The content hash is what makes this more than documentation. If somebody edits the entry text in AGENTS.md without touching the sidecar, the hashes stop matching and CI fails, so a rule cannot be quietly reworded into a different claim while carrying a verification date that belonged to the old wording. It also gives the trigger job something to match on: a migration touching events looks up every entry whose triggers mention schema:events and comments them onto the pull request with the verification command already filled in, so responding means running one query rather than opening an investigation.
The provenance field, which records where the entry came from and what happened that made somebody write it, earns its place separately. Most arguments about deletion stall on not knowing why a line exists, and "INC-3312: full scan of events during Black Friday" tells the next reader both that the concern was real and that the concern was about scans rather than about that particular index, which is what lets them replace the entry rather than either keeping or dropping it blindly. Chapter 7 returns to what happens when there is no provenance at all.
CODEOWNERS, the file in which a repository maps paths to the reviewers whose approval a change to them requires, closes the loop in the other direction, so that changes to the file reach the people who own its contents:
/AGENTS.md @team-platform-data @team-checkout
/AGENTS.entries.yml @team-platform-data @team-checkout
/services/events/AGENTS.md @team-platform-dataWhat the trigger control costs, and what it still misses
The attestation cost each team about one hour per year and produced nothing. This costs a few days of engineering to build the trigger job and the entry index, and then it costs roughly two minutes on every migration pull request, paid by whoever is trying to ship a schema change on a Friday afternoon. That is a real tax on real work, and it is the reason this kind of control gets built and then quietly disabled six months later. Two defences help: keep the trigger set small, so it fires on the four or five event classes that have actually produced incidents rather than on everything that might, and make the response cheap by shipping the evidence command in the comment. A check that asks you to think is a check people route around; a check that asks you to run a query and paste eleven lines of output is one they answer.
The control also needs a number of its own, and it is not a compliance rate. What tells you whether the trigger is working is the fraction of firings that produce an edit to the config file or the sidecar. Over the first quarter on the events service, 34 pull requests matched the migration path filter, 6 of them changed an entry and 3 more recorded a fresh last_verified date with the query output pasted in. Roughly a quarter of firings changing something is a control doing work. A quarter of that rate, and you are looking at a check people are dismissing, in which case the honest options are to narrow the trigger set until it fires only where it earns the interruption, or to admit it has become theatre and remove it. A control nobody responds to is worse than none for the same reason a stale instruction is: it produces a record that says the question was asked.
There are three things this does not catch, and pretending otherwise is how you end up with a second 100% compliance number.
Some subjects live outside the repository. A vendor's sandbox limit, a partner's rate cap, a policy owned by another department — nothing about those changes produces a commit in your CI, and the payments sandbox rule is exactly that shape. The available response is a small number of contract probes: for the handful of external claims that matter, a scheduled job that asserts the claim against the real system and fails when reality moves. A test that posts a 12,000-unit transaction to the sandbox monthly and fails if it succeeds would have caught that rule eighteen months earlier than the incident did.
Some claims expire without any event at all. A rule reading "the sessions table is small enough to scan" is not falsified by a migration; it is falsified by growth, one row at a time, and it crossed the line from true to false on no particular day. Claims with an implicit threshold need to be written with the threshold in them, and monitored on the number rather than reviewed on a trigger.
And a trigger cannot distinguish an entry that expired from one that was never true. The mechanism assumes each entry was correct when written and asks whether its subject has moved since, which is the whole design; an entry that was somebody's mistaken belief in 2024 will be carried forward across every trigger it survives, with an increasingly reassuring verification history attached. That case needs the instruments from chapters 2 through 4 rather than a trigger.
None of this addresses the pressure that fills the file in the first place. A file with owners, triggers and provenance grows exactly like a file without them, because every addition arrives with a good reason and an incident behind it, and the cost of an addition is paid by a future session rather than by the person adding it. That is the next chapter's problem.
Config hygiene — read the current version at https://daviddominguez.dev/learn/en/config-hygiene/standing-instructions-expire-when-their-subject-changes