Files
chemenu/docs/why-gates-are-code.md
T
torben 828521861d
CI / verify (push) Successful in 53s
Release / release (push) Successful in 36s
stack: MCP submit-Tool mit Upload Review Gate und Quarantäne-Schreibpfad (schliesst #32)
Files changed:
- .gitignore
- AGENTS.md
- CHANGES.md
- INSTALL-MCP.md
- README.md
- VERSION
- docs/why-gates-are-code.md
- instructions/gates.md
- instructions/ingest-queue.md
- instructions/mcp-read-server.md
- instructions/wiki-ingest/SKILL.md
- raw/CONTRACT.md
- tools/CONTRACT.md
- tools/chemenu/cli.py
- tools/chemenu/commands/docs_verify.py
- tools/chemenu/commands/doctor.py
- tools/chemenu/commands/upload_cmd.py
- tools/chemenu/config.py
- tools/chemenu/mcp/server.py
- tools/chemenu/tests/test_doctor.py
- tools/chemenu/tests/test_mcp_server.py
- tools/chemenu/tests/test_upload.py
- tools/chemenu/tests/test_upload_cmd.py
- tools/chemenu/upload.py
2026-09-11 09:51:37 +02:00

76 lines
5.0 KiB
Markdown

# Why gates are code
Chemenu has four hard limits - the Mass-Update Gate, the Publish-Remote Gate, the Upload Review
Gate, and the Iteration Budget Gate - and all four 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 four 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 four different mechanisms, not one
The four gates ask four 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 Upload Review Gate asks the Mass-Update Gate's own question - *is this change right?* - at
the opposite end of its size range: one file from a stranger instead of a changeset from the
session's own work. That similarity is exactly why it reuses the same shape (a `--confirm` token
digesting the thing being approved) rather than inventing a fourth one: the two gates differ in
*who* produced the change and *how much* of it there is, not in what kind of question either one
is answering, so nothing about the mechanism needed to change - only the boundary it sits behind
did, since the submission lives in a quarantine the ordinary pipeline never reads at all rather
than in the working tree `publish` is about to commit.
The Iteration Budget Gate asks a fourth 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.