The same block of lines on five sheets, fainter with every layer — and the trace running through the stack peters out before it reaches the bottom.
My Own Tooling Stops My Own Agent
One day in September, my own guard blocks my own agent. The agent wants to delete a file it created itself two hours earlier, and it reaches for a spelling that my shell alias does not catch — not on my instruction; the choice was its own. rm-trash-guard steps in between: no final deletion, the redirect into the trash instead. The delete command does not run, the redirect holds. In the same session, commit-nag keeps watch over any uncommitted change to my rule store.
Both do what I built them to do. And I built both of them. rm-trash-guard sits on the path a command takes: before execution, active in every session, blocking rather than advisory. Note where it sits: in the end, what decides is not what a rule says, but where it stands.
What else is in that store: 12 hook scripts, 6 of them blocking guards. 63 permission rules. 406 lines of rules I wrote myself, spread across two files — a global one loaded into every session, and a project-local one. And all twelve scripts fire exclusively inside an agent session; from my own shell, not one of them fires. The apparatus exists only against the agent.
That is the contradiction this text takes its name from: I use a tool because I trust it to write code, and with that same tool I build an apparatus that assumes it cannot. The same rule stands there twice — once as a request to the agent, once as a barrier against it. The scene above is one such pair: at the front stands the redirect into the trash, which a different spelling walks straight past — at the back, the guard that stops exactly that.
This is not an argument against the cage — that is, the checking layer meant to secure what agents produce. The more uncomfortable question this text is heading for: could anyone draw up the bill for the cage at all?
The Corset Grew, It Was Never Designed — A Stack With No Architect
The Escalation Ladder and Its Price Tag
My store is the simplest case: rules that only I carry and only I have to enforce. One level further out, where a team carries the same checks, it looks different — a client project, a monorepo with ten sub-projects, 38 hook entries.
They are spread across two stages, and the stages build on one another. At commit time, 20 of them run over the changed files. At push time, all 38, because no entry skips the push stage — the 20 from the commit are included. In CI, the same 38, but there over every file in the repository. The stage split does not halve, it escalates: 20 → 38 → 38, and the last stage no longer checks only what changed, but everything.
Every position has a price tag, and what was measured has to be stated alongside it. Over one changed file: 2.29 seconds warm for the commit stage, 36.30 for the push stage. That is a manual run, not a commit context; the cold readings — 3.16 and 51.02 seconds — are warm-cold, not the price of a fresh clone. Over the whole tree, in CI: 907 seconds median across eleven real workflow runs; in the most recent of them, 817 seconds went to the hook step alone. That third price tag is not a third rung of the same ladder — it measures a different object and puts a number on what full coverage costs in waiting time.
A good two seconds for the commit stage is not much; no working day founders on that. And the escalation is built correctly, too: every stage buys more coverage for more time, from cheap to expensive. What is incurred is incurred all the same — it is just not waste, it is the price of coverage.
And then the number I must not smooth away: of the 20 commit checks listed, only 4 did any work; of the 38 at push time, only 6. The rest is skipped, and deliberately so — each check sees only the files of its own sub-project, and whatever a change does not touch never starts up at all. That is the economical build, not idle running. It does mean the listed number cannot be multiplied by the runtime: anyone who says "38 hooks" and puts 36 seconds next to it suggests exactly that. It was six.
The ladder shows what is listed; the row beneath it, what actually ran. The runtimes stand apart from both — the first two measure one file, the third the whole tree.
Nobody Knows the Operating State
Every layer distrusts the one below it. In my setup that is not a reading I am offering, it is written into the scripts: each one carries a line at the top naming its purpose. One guard exists to keep the agent from bypassing the git-hook level. A marker-writing script exists for no other reason than to let a checking script work: the one writes what the other requires. Two of twelve scripts are a pair like that.
A layer can be installed, tested, and still have no effect. One of my guards ran fail-open — of all cases, the one it marks as the most dangerous was the quietest in one of its branches. The fix didn't work, and eleven days later it had to be corrected three more times. A test existed throughout and was even extended along with the ineffective fix: green, while the hole stayed open. What was wrong above all was a confirmation prompt that also fired on commands that endangered nothing at all. In the code sits the sentence that supplies the cost type as well — "a warning that is wrong too often no longer protects, it trains people to click it away."
When the stack was measured, the most precise single piece of evidence sat in the client project, in one line of the CI configuration: it passed the stage argument twice. The argument takes only a single value, so the second one overrode the first — effectively, only the push stage ran. The result was correct anyway, but only by coincidence, because the push stage contains all 38 entries in any case. Two errors cancelled each other out, and that is why neither was noticed.
That a watchdog can run silently wrong is no discovery of mine. Tobias Mogdans describes it himself in part 6 of his cage series, repeatedly and at different seams: a merge gate spent a while deriving the risk tier without using it, and the same failure form turned up again at several points in the chain of the project where he is piloting the tiering — "a failure form that can arise afresh at every seam where a gate sits". My case shows the same class from the other direction: with him, installed and misconfigured, the effect too much checking; with me, configured and not installed at all, the effect no checking.
And then the gap I didn't know was one. The 63 permission rules from the opening are nothing but permissions: not a single deny rule, not a single prompt — and the file search sits on the allow list. The six blocking guards cover deletion, destructive git commands, bypassing the hook level, removing worktrees, unreviewed pull requests, and the transport of planning files. A search with no path boundary is caught by none of them — not because the level couldn't catch it, but because that guard does not exist.
That lands on me at exactly the point I called the simplest case above: a stack owned by one single person. No distributed responsibility, no team, no 38 entries — and the owner still did not know his own operating state.
Nothing but failures so far — and they stand next to an apparatus that starts up at every commit and in every session and does what it was built for. That is exactly why the operating state is worth a question at all: anyone who does not know it cannot book the cost of a layer — the bill for the cage already stands on shaky ground here. My own case is measured, and it is the core example of this text.
The Same Rule, Encoded Three Times — And the Third One Is Dead
The intention is banal: commit messages should follow Conventional Commits. In my setup it is encoded at three positions in the chain.
At the front, as prose in the global rules file, which is loaded into every session and costs context every time. In the middle, as a tracked commit template, active by configuration in one of my own tooling repositories; it costs no context. At the back, in that same repository, as a commit-msg hook that likewise costs no context — if it is wired up.
The template is the actual find: advisory like the prose and free all the same. With that, the cost and effect axes come apart, and "cheap and unreliable at the front, expensive and reliable at the back" is too coarse. There is a position that is cheap and advisory.
The three encodings already contradict each other. The prose names 11 permitted types, the template the same 11 and two more below them, and the third list does not exist at all — the configuration file the hook would need was never tracked. The most recent commit of that repository carries a type that appears in none of the three lists.
The back position is not dead for one reason, but for three. The hook directory does not exist — not "is empty", it is missing; a fresh git init creates it, so it was removed after the fact. An alternative hook path is not set. And the tool the hook calls is not installed system-wide. Three independent reasons, and none of them is visible in the configuration file: that file still claims nine commands across three events. The hook framework itself is installed, just not wired up.
Why the directory is missing, I do not know, and that is where it stays.
Behind that sits something that holds for every hook framework: the configuration is versioned and travels with every clone; the activation lies outside version control and arises only through a separate step. A layer can disappear without that becoming visible anywhere — no diff, no commit, no message.
How much this one repository carries needs saying too: four commits, all from two days in July 2025, dormant ever since. Whether the hook ever took effect can no longer be determined. The finding does not hang on that — the finding is the intention, encoded three times, in three versions that are drifting apart.
And then the finding does something that does not suit me at all: the cheap, unreliable prose rule still holds today, the expensive, reliable hook rule does not. Here, the front was the more robust position too. In a dormant repository that is not strong evidence — but neither does it settle the question of placement; it makes it more interesting, and I am leaving it standing instead of smoothing it out.
In the end the finding turns back on me. On the day before the session from the opening scene, I built a skill and an agent, semantic-twins and semantic-twin-hunter, which use agents to hunt for semantic twins — for type-4 clones: code that does the same thing but is written differently, and that the usual detectors therefore cannot find. As the second class of twin, the agent description lists a rule encoded a second time in another layer, and the body of the agent definition names exactly the case "with the two already differing". My own detector looks for precisely this defect; in my own rule set it was there three times — as the commit rule just described — and went unnoticed. The detector is older than the thesis of this text, too — so it was not built to fit it.
And this defect is not an isolated case. When my shell configuration was examined, the trash rule from the opening scene sat in it twice. One of the two places redirects into the trash tool; the other had left the final delete command standing and merely attached a confirmation prompt to it. At runtime the first one won; the weaker version had been sitting there all along, unnoticed. Two independent intentions, both encoded more than once, both already diverging, both unnoticed.
The same intention at three positions in the chain, each with its cost profile and list of types. The middle one is advisory like the front one and still costs nothing; the back one has no effect, for three independent reasons.
Three positions, three cost profiles, and nobody who ever weighed them against one another. Before the bill, though, I have to get rid of a comfortable assumption: that this stack comes from the agents at all.
The Stack Is Older Than the Agents
The Checking Layer Was There Before the Agents, and It Grows With Sub-Projects
First, back to the client project — the monorepo with the 38 hook entries.
The check configuration sits there in the very first commit of the repository — same SHA, same second. The rules file that guides the agent today arrived 220 days later. So the stack is not merely older than the agents, it is as old as the repository.
The growth is not agent-driven either. In the seven-odd months before the agent rules file, the number of entries rose from 2 to 20; in the nine-odd months after it, from 20 to 38. After the agents arrived, the checking layer grew, if anything, more slowly, and the biggest individual jumps hang on new sub-projects, without exception.
That is the comfortable causality I had to give up — and I can give it up with a quote from the series this text connects to: "Nothing about this floor is new. Linters, static analysis, a decent test suite were part of the craft long before the first agent wrote a line … What has changed is not the tooling, it is the latitude."
And here the stack even shrinks, twice. Both declines coincide with rebuilds of the sub-project structure: the rules disappeared because their object disappeared, not because anyone had drawn up a bill. What gets removed is what loses its object — never what merely became superfluous.
The timeline starts at the repository's first commit, not at the agents — they arrive 220 days later. The two dips are the only declines, and both coincide with a rebuild of the sub-project structure.
In My Own Rule Store, No Rule Is Ever Withdrawn
Now the second stack: my own rule store, where no object of checking can disappear.
326 commits across 118 days, not one single withdrawn rule. The global rules file grew from 156 to 331 lines; of the 34 commits that touched it, not one removed more than it added. The rule-relevant hook registrations rose from 0 to 14, spread across five hook events, without a single decline. And the only two withdrawals in the whole store remove permissions — they narrow, they do not loosen.
That nothing is withdrawn holds for rules, not for files, otherwise a single git log would refute it: files do get deleted, 26 of them in 9 commits, but every deletion is a merge, a move, a rename-with-extension, or housekeeping.
Eleven days before I began thinking about this, Addy Osmani published the same pattern — "Audit your Agent files", August 27, 2026; I did not know the article. He describes the growth as a reflex: one more rule with every misstep the agent makes, and it keeps adding up from there. He also names the reason nobody ever removes a layer — we lean on them like a crutch, because we do not credit either the model or the harness with having gotten good enough to do without them.
And the sentence I wrote down for myself: "Installing a useful skill and keeping it forever are separate decisions." When two practitioners with different setups independently observe the same thing, it is not the quirk of one individual with too many guards.
The pattern describes the normal case, not the only one, and the exception belongs right next to it. The best-known guard in my store, the same one that blocks in the opening scene, came out of a measurement of my own history: 422 delete calls in the logs of past sessions — specifications, source files, even my own rule files. Of those, it would have blocked 223. So no agent misstep — and no rule violation either, because at that point there was no rule. The numbers come from the same session in which the guard arose — the count the rule grew out of, not an audit from outside.
An inventory of these layers does exist: a shipped tool walks through skills, MCP servers and plugins "relative to their context cost". It covers exactly one layer — not git hooks, not CI.
That nothing gets withdrawn is settled. What it costs is not — and for one cost type, the answer works against the purpose of the apparatus.
The Corset Costs Exactly the Resource It Is Meant to Protect
Context is the only cost type that works against the purpose of the apparatus: rules are supposed to guide the agent, and to do that they take up room in the very window the agent feeds its behavior from. Except that the mechanism works differently than I long assumed.
More rules, worse adherence: that was the curve I had expected, and it is not supported by evidence — for the size of the rules file it is actively refuted. One study ran 1,650 Claude Code sessions across two projects and counted in each whether a trivial instruction was followed; the size of the rules file made no difference — and not in the sense of "not detectable", but as evidence for the null. What is supported instead is conflict: a second study stacked up to twenty verifier-checked instructions on top of one another, and the follow rate falls from around 96% to as low as 20% — not because of the quantity, but because pairs of them contradict each other. My triple-encoded commit rule is a case like that.
My own finding is a third pattern, neither quantity nor conflict: not worse adherence, but non-arrival.
At the start of every session, a script runs that I wrote myself. It assembles the index of my rule store — one line per rule — and puts it into the session's context. What this script outputs, the environment checks against a hard limit: 10,000 characters, read off a specific program version and confirmed with two control sessions. The limit applies to the script's output, not to the file behind it.
If the output goes above that, the environment writes it into a file and puts only its beginning into the context: the first 2,000 characters. Silent it is not: it reports the offload and names the path of the file — to the model, though, not to me. The notice travels along as an attachment, not as a line in the transcript I read; I have never seen it. And it does not tell the model which entries are missing either — nor that the model should go and fetch them.
At its peak, my output stood at 56,509 characters, a good five and a half times the limit. Of the 184 entries in the index, 6 reached the context. I noticed none of it.
Today it is 9,724 of 10,000 characters: 276 characters of headroom, less than one new index line, and all 70 of 70 entries arrive. Since the same script started doing its own trimming on September 8, not a single offload has occurred across the body of sessions evaluated. And it reports what the environment does not report, to me instead of to the model: which entries were dropped — and since September 10 it warns before a single one is dropped at all. The layer holds — and is itself one more layer that has to be maintained. Nothing is solved: it holds because it trims, and what it trims still does not arrive.
Five layers of rule-set administration have grown up around this limit, for one individual's rule set, in barely twelve weeks — among them a separate script that checks the structure of the rule set, and, as the most recent, the warning to me, because the environment's message never reaches me. Two things I am not smoothing over. An additional rule tells the agent to treat the index only as an excerpt and, in case of doubt, to read the full file. It came into being before the script did any trimming of its own — so it was written against somebody else's mechanism first. And the first countermeasure, the convention of sub-indexes, did not stop the growth: after it, the index grew by 148%.
Long before this trimming existed, on June 23, that very convention hit a rule that had been in the index for barely four weeks: before removing a working directory, check whether uncommitted or ignored files are still in it. Two days earlier, its topic group — the git rules — had moved into a lazily loaded sub-index. All that remained in the main index was a single line, a pointer to that sub-index, and that line lay outside the preview: 67 of 75 entries did not get through. Pointer not delivered, target only on demand — the countermeasure made the rule unreachable.
Damage was done: what was deleted was my own tooling from my Core Keeper mod project, code I wrote myself, not throwaway material.
After my reprimand, the agent went looking for the rule, found it, and concluded: a discipline failure, not a knowledge failure. Both wrong — that day the start script's output had been offloaded, not placed into the context. The agent sharpened the rule text instead of noticing that the text had never reached it: a delivery failure, read as a discipline problem, answered with more rule text that loads the same delivery further. Fourteen minutes later the same rule stood at the back of the chain as a blocking hook, on the command instead of in the context — where it no longer has to be delivered at all. The same sharpened entry carries the wrong cause and, one sentence further on, the right consequence: the trigger should hang on the command, not on remembering. The placement question had been answered without anyone having asked it. My reprimand was justified at the time — nobody could know that the rule never arrived. No blame on me, no exoneration of the agent: two parties trusting a delivery that nobody could check.
It was the third time around: the rule itself records two earlier occasions, and by the third it had outgrown the context. Osmani's pattern reaches its sharpest point here — the occasion does not even have to be real.
The hardest single piece of evidence is not an incident, though, but a number: the entry in my own index that prescribes actively reading back an offloaded output existed in 190 sessions and got through the preview in not one of them. The rule against offloading was swallowed by offloading, without exception. What is documented is delivery, not effect — whether the rule would have been followed, the number does not say; there is no comparison group.
One point I had underestimated. Tobi did not overlook the quantity question, he rejected it: "The dividing line is not 'context, yes or no', but 'from which source'" (part 5). And his own lever fills the context: the facts are meant to be in the window before the agent writes a word. His remedy loads the very resource that rule adherence is fed from. And part 6, the cost part, does not name context a single time.
How far the advertised context window is from the effectively usable one, I took apart in a separate article on the mental load of language models; my memory series and the governance part cover where an index like this comes from.
At the top, two readings of the same quantity against the hard limit; beneath them — set apart, because it is a different quantity — how many entries actually arrived. At the bottom, the five layers that grew up around this limit.
That puts the line items on the table. What is missing is the bill.
The Full Bill — What the Stack Costs Per Run
Anyone who adds to a bill drawn up by somebody else has to represent it correctly first — otherwise nobody can see which half is missing. So, first, Tobi's position, as completely as I can render it.
His model is two curves: the costs of bad code keep accumulating over the lifetime of a system, the costs of the cage do not — they are "bounded, incurred up front, and commodity goods". He names the line items of that second curve one by one. Build is cheaper than it sounds — "You don't build the floor, you set it up." Operation is "the underestimated bill": "CI minutes (those cost real money), the throughput of the merge queue, the latency per back-edge round, flaky tests, the upkeep of the ratchets, the question of who responds to the alarm." Containment costs in a different currency: friction. Pressure to bypass gets its own section, and in it this sentence: "'Pareto', 'good enough', 'this isn't worth it here' are the next escape hatch, the new .skip." Maintenance, finally, is not an additional cost type but "the residual-management loop itself".
His stop rule has two parts, and the second half is easily missed. On breadth, he stops economically: "Build a gate as long as the next gate costs less than the damage it prevents on average." On depth, expressly not via money: "Independence, not money" — the stopping point is "the point at which no independent layer is left". The maxim he himself marks as load-bearing: "Pareto on breadth, never on depth." What that means is the trade-off just described, not the popular 80/20 rule — he explicitly draws that distinction himself.
On top of that he imposes three limitations on himself. First, three zones where Pareto has no business at all — irreversibility, unknown unknowns, the intent chasm: "That is not a self-contradiction, that is the actual point: calculate where calculating is allowed, and make impossible where calculating fails." Second, his slop curve holds only for long-lived, shared code, and he frames that boundary himself as protection against self-immunization. Third, he refuses numbers deliberately and on methodological grounds: "The argument is stronger when it runs on shape, not on values."
That I nevertheless turn up with measured numbers is therefore not a counter-move. Tobi refuses invented numbers for the return; for the cost side he named the line items and left them open — "That belongs honestly on the bill, not talked away."
| Cost type | In part 6 |
|---|---|
| Build · Operation · Maintenance · Friction | ✅ named |
| Latency per run | ✅ named, under operation |
| Pressure to bypass | ✅ a section of its own |
| Overview | 🟡 there as a phenomenon (a watchdog running silently wrong), not booked as a line item |
| Ossification | 🟡 a deliberate design decision ("Anyone who changes the watchdogs triggers the highest tier") |
| Context | ❌ the one clearly untreated cost type |
Six cost types expressly named, two touched on and judged differently, one untreated.
That the time gained in generation flows back into checking and prompting has been surveyed, not discovered here: the DORA analysis for 2025 says so explicitly. What it does not say: that this verification layer belongs to three different owners.
That is exactly where my addition sits. The line items are named, and they are reckoned as dampened and bounded — "tier grading dampens that … but it does not make it go away", part 6 says. That presupposes an authority that can grade tiers. The real stack does not have one: git hooks belong to the repository, CI gates to the team, agent guards and rules files to the individual. What I am adding is not a line item, but its behavior under distributed responsibility.
The same goes for the growth thesis: "The cage grows with the domain … makes the cage better over time, not more expensive." For a cage under one responsibility that is true. The real stack lies outside that scope — same shape, different effect.
That nobody can draw up the bill affects only the breadth rule. On depth, Tobi does not ask for a cost calculation but for a design judgment about independence; nobody needs a stack-wide balance sheet for that. The breadth rule, by contrast, presupposes that the bill is open to somebody. That is precisely what is not the case: no authority knows the whole stack.
From that follows the distinction I care about. Investment costs amortize. Costs per run are incurred afresh on every use and scale with the size of the cage times how often it is used.
Both classes are the price of a service, not effort without a return. These layers run, and they catch what they were built to catch — what they lack is not effect, but a balance sheet.
I write this as someone who described the same tooling as a gain in the Claude Code Must-Haves — I am aware of the tension, and it deserves to be said.
The nine line items, sorted by the one criterion that separates them: what amortizes, and what is incurred afresh on every use? Beneath them, the three areas of responsibility — and none of them sees the whole list.
Not Whether, But Where
The one lever my findings support is not a recommendation about how big a rule set should be. It is a question of position: what decides is not whether a rule exists, but at which point in the chain it stands. The evidence is the triple-encoded commit rule — three positions of one intention, three cost profiles, already diverging.
That the front position is cheap, anyone who uses it knows. That it is unreliable has been clear to me for a long time. What I did not know was the order of magnitude. By now it is unreliable in three different ways.
It can be swallowed. The entry from my index that prescribes reading back the offloaded output got through the preview in 0 of 190 sessions.
It can arrive and be ignored. In one of the sessions evaluated, a second rule against the same offloading arrived at position 1 of the preview — and was still not followed.
And it cannot reach everything it is meant to. At the front, the trash rule is a shell shortcut: it replaces the delete command with the redirect, but only when that command is the first word on the command line. Written differently, the command walks straight past it — measured, the shortcut catches 1 of 8 invocation patterns. The guard behind it sits ahead of the shell's start and sees all eight.
The third kind is qualitatively different: being swallowed and being ignored are delivery failures, reach is a limit of construction. The rule arrives and is followed — it just does not cover what it appears to cover.
The lever is not new, and I would rather say so myself. Osmani published it: "if a rule must always hold, encode it in a test, hook, or permission rather than leaving it as prose the model might lose." I did not know that sentence when I made the observation. My contribution is not the first discovery, but the aggregation across layer boundaries, the measured field case, and the finding about responsibility.
Recipes I do not give: the obvious clean-up recipes are not covered by the empirical evidence, and for the most obvious reflex, better prompting, there are counter-findings; Tobi's part 1 reaches the same conclusion. Thought through to the end, the placement question leads to specification at the front instead of repair at the back, the lever from my SDD series. Where the full bill leads — a tighter corset, smaller tasks, or acceptance of the costs — I am not deciding here.
Back to the guard from the beginning. It is itself a rule at a particular point in the chain: before execution, active in every session, blocking. And it sits there because the front encoding was not enough — that encoding was built, measured, and supplemented by the guard in the very same session. What I am posing here as a question, I had long since decided: a good three weeks earlier, and without noticing that I was deciding anything.
So the question is not: do you have too many rules? It is: at which point in the chain does yours stand — and what does it cost there? And the more uncomfortable half: could you draw up the bill at all?
Originally published on Medium.

