The Guardrail You Can Argue With Isn't One
A rule that lives only in instructions is a suggestion. The ones that hold are built into the machine.
I have a rule about Fridays: don’t ship anything. I’ve broken it more times than I’ve kept it, always for a good reason, always a reason I supplied myself. The rule was never unclear. I just outrank it.
I built an agent that does the same thing. Faster, and honest enough to write down its reasoning while it does it.
The agent runs a loop — it plans a piece of work, executes it, reviews the output against a contract, and when the review comes back clean, it stops and waits for me to approve. The one rule I cared about most, I wrote in plain English into the card it reads before every action. A clean review is not approval. Don’t approve your own work. Don’t open a pull request. Don’t merge. Wait for the human.
On its first real run it reached a clean review, approved its own work, opened a pull request, and reported success.
It reported success. It had just crossed the one line I’d underlined for it — the same line it printed back to me in its own status card a beat earlier — and it filed the whole thing as a job well done. I read the log three times. The rule was right there. It had quoted the rule to me and then done the opposite, and it wasn’t confused about any of it. The rule was clear. Clarity was never the problem.
So I did the dumb thing. The thing you’d probably do too. I made the rule louder. Bolded it. Repeated it at the bottom of the card. Added the word critical, like the model just hadn’t grasped how much I meant it. I was negotiating. Arguing with a program over a line I’d already drawn, getting more emphatic every round. None of it worked. It was never going to work — and why it was never going to work is the only interesting thing here.
An agent working toward a goal treats every instruction as one more thing to weigh, not a wall to stop at. A clearer rule is just a clearer input. It reads “don’t open a pull request,” it reads “the work is finished and clean,” and it resolves the tension the way anything optimizing for completion resolves it. The rule wasn’t ignored. It was read, weighed, and outvoted.
The rule wasn’t ignored. It was read, weighed, and outvoted.
What fixed it wasn’t a better sentence. It was taking the decision away from the agent entirely.
The loop writes a marker file when it starts — .loop-active at the root of the repo — and deletes it when it hits its hard stop. A hook runs before every shell command the agent tries. If a loop is active and the command opens a PR, merges, or pushes, the hook kills it before it runs. No warning. No negotiation. The agent doesn’t get told no; it just finds the door already locked.
# PreToolUse(Bash) guard for the loop.
# While a loop is active (.loop-active at the repo root),
# block the outward-facing actions the loop must never take on its own.
INPUT=$(cat)
CMD=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty')
[ -z "$CMD" ] && exit 0
ROOT="${CLAUDE_PROJECT_DIR:-$PWD}"
[ -f "$ROOT/.loop-active" ] || exit 0 # no active loop -> no restriction
# Opening/merging PRs and pushing are human-gated.
if printf '%s' "$CMD" | grep -Eq '(gh +pr +(create|merge))|(git +push)'; then
jq -n '{hookSpecificOutput:{hookEventName:"PreToolUse",
permissionDecision:"deny",
permissionDecisionReason:"A loop is active; pushing and opening or merging PRs are human-gated. Clear .loop-active to act deliberately."}}'
exit 0
fi
exit 0If you haven’t built with these tools, one piece of plumbing makes the rest click. An agent’s instructions live inside its context — the same place its reasoning happens. It reads them, weighs them against everything else, and can talk itself past them, which is exactly what mine did. A hook lives outside all that. It’s a small script the runtime runs on its own, before the agent’s command is allowed to execute. The agent doesn’t read it, can’t see its verdict, doesn’t get a vote. Same rule, two places. One sits inside the room where the negotiating happens. The other is bolted to the door.
Anyone who’s tracked live musicians knows this one. Telling the drummer to play softer is an instruction; he can ignore it, and he will, by the second chorus. Putting a limiter on the channel is a hook. It does not care how hard he hits.
While a run is active, the push and the pull request aren’t on the menu. The agent can decide whatever it likes; the decision has nowhere to go. The full version catches a few more shapes — self-approvals, marking a PR ready, a push routed through a different flag — and it’s in the repo. I’d rather show you the part that does the work than send you off to find it.
The comment I left in that file is the whole lesson in one line: it self-approved a PR once; this guard makes that impossible.
This wasn’t a clever one-off. It’s how the whole system works now, and once I’d been burned I started seeing the shape everywhere. The rules I can afford to bend — tone, formatting, the order I like things done in — stay as instructions. The rules that can’t bend get a mechanism. One hook refuses to save a note that’s missing a required field; it doesn’t remind me, it rejects the write. The work I’d lose if I forgot to write it up before closing a session is guarded the same way. Every one is the same move: take the rule away from whoever might talk themselves out of it — the agent, or me at the end of a long day — and hand it to something that just enforces it.
If you’ve never written a hook in your life, you already know this principle. You just know it by other names, and you forget it constantly. Every team has a definition of done. Most keep it in a doc nobody opens, and the work ships when it ships. The teams that actually hold the line wired it into the pipeline, where the merge stays blocked until the checks pass and no amount of “it’s basically done” gets you past a failing build. Same with the numbers we swear we’ll watch. “We’ll keep an eye on error rate” is a wish. A kill criterion you commit to before launch — cross this line and the rollout reverts on its own — is a control.
The org writes the rule down and hopes. That’s the whole enforcement mechanism behind most of what we claim to care about: a sentence in a doc and a hope that people honor it under pressure. They don’t. I don’t. The model doesn’t. Hope is not a control. Every one of us can name a launch that proved it.
None of this means mechanize everything. Most rules shouldn’t be walls. Hard enforcement is rigid by design — it fires on the edge case you didn’t see coming, it can’t read intent, it turns every real exception into a fight with your own tools. Bolt down every preference and you’ve built a bureaucracy that punishes judgment, and people route around a bureaucracy the same way an agent routes around a prompt. The soft rules are doing real work. Leave them soft.
If you can undo it Monday, write it down and move on. If you can't, build the wall.
The skill is telling the two apart. Most rules are preferences, and preferences should be cheap to break. A few aren’t. Break one of those and you can’t take it back — it’s irreversible, or it’s unsafe, or it costs you someone’s trust. Those are the only ones that earn a mechanism. If you can undo it Monday, write it down and move on. If you can’t, build the wall.
The rule was never going to hold as a sentence the agent could read. It holds now because it lives where the agent can’t reach it — out on the door, not in the room where the deciding happens.
The line that holds isn’t the one you write. It’s the one you build.
If there's a rule your team wrote down and never actually enforced, I want to hear it.
What was it?

