Chapter 6 of 7 · 9 min read
Each new rule is paid for by removing an old one
On this page
INC-3418 ran for two hours and eleven minutes on a Tuesday evening. A migration on ledger took an ACCESS EXCLUSIVE lock on a table, which blocks every other read and write against that table until the migration commits, and because ledger and checkout had ended up on the same Postgres instance eight months earlier during a cost-reduction exercise, the connection pool saturated and checkout requests started timing out. The retro was calm and productive, and it produced three action items, each of which was written down as "add to AGENTS.md" by a different person.
The file was at 1,487 tokens against a budget of 1,500. The three proposals came to 315 tokens between them.
| Proposal | Size | Reach | Enforceable elsewhere | Verdict |
|---|---|---|---|---|
checkout and ledger must never share a database instance | 24 tok | High — any infra or persistence change to either | Partly, in Terraform CI | Accept, and pay for it |
| Retry policy: backoff, jitter, caps, which calls are idempotent | 203 tok | Medium — every outbound call | Yes, in the client | Push down into code |
| Write careful, detailed commit messages during incidents | 88 tok | Low, and it addresses humans | No, and nothing to enforce | Refuse |
Working each of those decisions is the whole job, and the third one is where the social difficulty sits, so it is worth taking them in the order that makes the reasoning visible rather than the order that makes the meeting comfortable.
The invariant is the easy accept and the interesting one
Never share a database instance between checkout and ledger is the shape of thing that belongs in an always-loaded file by 12.8's reach test: an agent could violate it while writing a Terraform module, while adding a connection string to a Helm values file, while proposing a "simplification" that consolidates two RDS instances, or while answering an innocent question about where to put a new table. It is short, it is falsifiable, it names two specific services rather than gesturing at a principle, and there is an incident number to attach as provenance.
The interesting part is the third column. This invariant can be checked mechanically — the two services' database endpoints are in Terraform, and a policy test can assert that they differ:
# infra/tests/test_isolation.py
def test_ledger_and_checkout_are_not_colocated(plan: TerraformPlan) -> None:
ledger = plan.resource("aws_db_instance.ledger").address_endpoint
checkout = plan.resource("aws_db_instance.checkout").address_endpoint
assert ledger != checkout, (
"INC-3418: ledger migrations take exclusive locks that starve "
"checkout's pool. These services must not share an instance."
)Given that test, does the config line still earn 24 tokens? It does, and the reason generalises. The test fires at the end of a change, after the agent has designed a consolidation, written the module, updated the values files and explained the cost saving in a pull request description. The config line fires at the beginning, when the design is being chosen. Enforcement catches the violation; the instruction prevents the wasted work, and the two are complements rather than substitutes. Where an invariant is both high-reach and expensive to discover late, paying tokens for it on top of the test is defensible. Where the check fires early and cheaply — a linter that rejects the import as you write it — the config line is redundant and should not be bought.
Refusing the commit-message paragraph
The proposal reads: During an incident, write detailed commit messages explaining what you changed, why, what you observed, and what you expect to happen, so the timeline can be reconstructed afterwards. Nobody in the retro disagreed with it. Refusing it is uncomfortable precisely because agreeing costs nothing in the room and the cost lands somewhere nobody is looking.
Three things are wrong with it as a standing instruction. It addresses humans, since the person committing during an incident at 21:40 is usually a person, and the file is loaded into agent sessions. It has no failure mode the agent can detect, because "detailed" is a judgement the model will resolve differently every time, which produces variation rather than compliance. And its subject is a narrow window — incidents — so on 195 of the last 200 sessions it is 88 tokens of instruction about a situation that is not happening.
The alternative is not "write it somewhere else and feel better". It is to put it where it fires at the moment it applies: a commit message template that the incident tooling installs when a war room opens, or a section in the incident runbook that the incident commander reads out. Both of those reach the person doing the committing, at the time, which the config file does not.
Pushing the retry policy into code
The retry proposal is the most reasonable-sounding of the three and the clearest case for pushing down. In prose it runs to a couple of hundred tokens describing exponential backoff from 100 ms, a cap of five attempts, full jitter, no retries on 4xx other than 429, and a note that POST /transfers is only idempotent — safe to send a second time without applying the transfer twice — when an Idempotency-Key header is present. That paragraph asks the agent to reconstruct a policy correctly, from memory, on every outbound call it writes, forever.
The same policy as code is executed rather than remembered:
# platform/http.py — the only client services are expected to use
_RETRY = Retry(
total=5, backoff_factor=0.1, backoff_jitter=1.0,
status_forcelist=(429, 502, 503, 504),
allowed_methods=frozenset({"GET", "HEAD", "PUT", "DELETE", "POST"}),
respect_retry_after_header=True,
)
def client(service: str, *, idempotency_key: str | None = None) -> Session:
"""Configured session for internal calls. POSTs require an idempotency key."""
...The config file then carries a pointer rather than a policy, which is 14 tokens instead of 203:
Use `platform.http.client()` for internal calls; it carries the retry policy.That trade has a second benefit beyond size. The prose version decays silently when the policy changes — someone adjusts the cap from five to three in a design document, the file still says five, and now you have a stale entry of exactly the kind chapters 1 through 4 were about. The code version cannot drift from itself. Guidance that describes behaviour available in a library is guidance you are choosing to maintain by hand, and How to actually force Claude Code to use the right CLI makes the sharper version of the argument: when you need the agent to do a specific thing every time, an instruction is the weakest available mechanism, and a wrapper that makes the wrong path awkward or impossible is the strongest.
Part of the retry proposal resists becoming code, which is where the third destination comes in. The rule that POST /transfers is only safe to retry when an Idempotency-Key is present is a judgement about the endpoint's semantics; the client can require the header, and it cannot decide for a new endpoint whether retrying is safe. That fragment belongs in a skill about writing outbound calls — loaded when the agent is doing that work, absent from the other 190 sessions — where it can afford to be 400 tokens and include the two examples that make it usable. Splitting a proposal this way is normal: the mechanical part becomes code, the judgement part becomes on-demand guidance, and the always-loaded file keeps a pointer to the client and nothing else.
Not everything pushes down cleanly, so it is worth being explicit about where each kind of guidance can live:
| Encoding | Enforced or advisory | Cost per session | Fails when |
|---|---|---|---|
| Always-loaded config line | Advisory | Tokens on every session | The claim rots; nothing detects it |
| Lint, type, or policy test | Enforced | Zero context | Feedback arrives late in the work |
| Wrapper library or CLI | Enforced by construction | Zero context | The agent can still bypass it |
| Pre-tool hook | Enforced at the moment of the action | Zero until it fires | Only covers actions the hook can see |
| Skill loaded on demand | Advisory, in scope | Zero until invoked | Not loaded when it was needed |
| Human runbook | Advisory, for people | Zero | Nobody reads it during an incident |
Paying for the line
With the retry policy pushed down and the commit-message paragraph refused, one 24-token entry remains, and the file is 13 tokens under budget. The reflex is to note that 1,500 was an arbitrary number chosen last spring and that 1,524 is obviously fine.
That reflex is where budgets die. A cap that can be raised by the person who wants to exceed it is not a constraint, since the same person judges both the value of the addition and the cost of the increase, and the cost is paid by future sessions that are not in the room. The coupling to removal is what makes the budget do work, because it forces a comparison the reviewer would otherwise never perform: is this new entry worth more than the weakest entry currently in the file? That question has an answer, and asking it is the only routine occasion on which anybody reads the existing entries at all.
Here is the accounting that actually happened:
| Entry | Tokens | Status | Action |
|---|---|---|---|
Prefer pytest fixtures over factory helpers | 19 | Style; also in ruff config as a lint | Remove — duplicated |
Run pnpm lint before proposing a change as done | 22 | Pre-commit hook does this already | Remove — enforced elsewhere |
Do not commit generated files under gen/ | 17 | .gitignore covers it | Remove — enforced elsewhere |
checkout and ledger must never share a DB instance | +24 | New, INC-3418 | Add |
The file went from 1,487 to 1,433 tokens, then to 1,457 with the new entry, and the three removals took eleven minutes because each one had an existing mechanism to point at. Removals of that kind are the reserve you spend on days like this: entries duplicated by a lint, a hook, or .gitignore are cheap to justify and nobody defends them.
A budget check belongs in CI so the number is not a matter of opinion:
#!/usr/bin/env bash
# tools/config-budget.sh
BUDGET=1500
tokens=$(agent config render --assembled | agent tokens --count)
echo "assembled config: ${tokens} / ${BUDGET} tokens"
[ "$tokens" -le "$BUDGET" ] || {
echo "over budget by $((tokens - BUDGET)) — remove an entry or justify the raise"
exit 1
}Measure the assembled text rather than one file, for the reason chapter 3 gave: the user-level file, the directory-scoped files and the MCP server instructions all land in the same context window, and a repository budget that ignores them is measuring a third of the object.
The specific number matters much less than its existence. A budget of 1,500 tokens against a 200,000-token window is not about cost, and anyone arguing the economics has the wrong model of the constraint — the standing instructions compete for attention with the task evidence the agent gathers, and they are present before any of it. Somewhere between 500 and 2,000 tokens most teams find the point where the file is still read as a whole by humans during review, which is the property that keeps its contents defensible. Pick a number in that range, write down why, and treat raising it as a decision that needs the same evidence as any other change to the file.
Where the discipline runs out
Every removal in the accounting above was easy because each entry had a mechanism to point at, and pointing at a pre-commit hook ends the conversation. The entries that consume a budget are not those. They are the ones with no lint behind them, no owner, no incident number, and no way to tell from the outside whether they are describing a real constraint or somebody's 2024 preference — and when you propose removing one of those to pay for a new line, someone will say that keeping it costs a few tokens while removing it might cost an incident. That objection is not stupid, it is right about the direction of the asymmetry, and answering it is the last piece of this topic.
Config hygiene — read the current version at https://daviddominguez.dev/learn/en/config-hygiene/each-new-rule-is-paid-for-by-removing-an-old-one