# Why gates are code Chemenu has three hard limits - the Mass-Update Gate, the Publish-Remote Gate, and the Iteration Budget Gate - and all three live inside `tools/wikitool`, not in a paragraph of instructions an agent reads and follows. The rules themselves, and what to do when one trips, are in [AGENTS.md ยง Gates](../AGENTS.md#gates) and [instructions/gates.md](../instructions/gates.md). This page is only about the design choice underneath them: why code, and why these three mechanisms in particular. ## A suggestion an agent can talk itself past An instruction like "don't publish too much at once" or "don't loop forever" lives in the same place as every other piece of guidance a session is holding - alongside the task, the user's last message, and whatever context made the moment feel urgent. Under pressure, or with a plausible-sounding reason ("this batch is different, it's mechanical"), that guidance can be reasoned around without anyone deciding to break a rule. Nothing enforces it; it just competes for attention with everything else in the context window, and sometimes loses. A check compiled into the tool doesn't have that problem, because it isn't part of the conversation at all. It runs before the command dispatches, regardless of how convincing the case for skipping it seemed a moment earlier. The difference isn't that code is smarter than a well-written instruction - it's that code doesn't get talked into anything. ## Why three different mechanisms, not one The three gates ask three different questions, and each one's shape follows from what kind of question it is. The Mass-Update Gate asks *is this change too large to publish unreviewed* - a judgment that varies changeset by changeset, so it clears with a `--confirm` token tied to the specific output the user just read. Approval is scoped to that one publish. The Publish-Remote Gate asks something underneath that: *is this even the right repository*. That's not a per-push judgment, it's a standing property of the checkout - true or false for every publish that checkout will ever attempt, not just this one. A confirm token would let an agent clear it once and then treat the answer as settled, which is exactly backwards for a question whose answer shouldn't move at all mid-session. The only way past it is the user editing `.wikitool-remotes.json` directly, outside the gate's own flow. The Iteration Budget Gate asks a third kind of question - not "is this instance correct" but "has this session stopped making progress." That's read from the shape of the call history itself (call count, repeated identical calls), not from anything about the content of any one call. ## Numbers that come from measurement, not intuition The iteration ceiling didn't start where it sits now. It used to run 15-25, borrowed from a general rule of thumb, until four real ingest runs measured 24, 26, 29 and 30 calls apiece - every one of them an ordinary workflow doing nothing wrong, and every one of them at or past where the old ceiling would have refused it. A limit that the normal case keeps tripping stops functioning as a limit; it becomes background noise a session learns to route `--override-budget` around as a matter of course, and the whole point of a hard-coded check is that it isn't supposed to feel routine. That's the deeper reason these numbers live in a tool rather than in prose: prose is read once and remembered loosely, but a threshold enforced every call is tested by every call, and a threshold that fails its own test gets noticed and re-measured rather than quietly ignored. The suite's coverage floor is the same argument run forwards instead of backwards. The ceiling above was wrong first and measured afterwards; the floor was withheld on purpose until the number existed - measured, then watched across 38 runs while the code grew by a quarter, and only then written down as 85 against an observed 87.0%. The two points of daylight are the same consideration as the ceiling's headroom: a limit the ordinary case keeps tripping stops being a limit. A coverage floor set at the measured number goes red on the next thin command wrapper, which is not a regression, and a threshold that goes red for a non-reason gets lowered rather than earned - the failure mode above, reached from the other direction.