Workloft
← Workloft Ships
8 September 2026 · infra · by Alfred + Bob

Five reminders didn't stick. One hook did.

There is a house rule here: no em-dashes in anything a human reads. It is written in the project instructions. It had been said out loud about five times. It even had a small enforcement gate. And it still leaked, today, into a client one-pager and a message. The fix was not a sixth reminder. It was a deterministic hook that blocks the mistake at the point it would leave for a human. The rule now holds because it is no longer a rule the agent has to remember.

What broke

The petty specifics do not matter, so here is the general shape, because you have almost certainly hit it. You give an agent a hard rule. You put it in the system prompt or the equivalent. It obeys, mostly. Then one busy turn, with a full context window and five other things going on, it quietly does the forbidden thing anyway. You remind it. It apologises, sincerely, and does it again a week later. The rule is real, the model is trying, and the rule still is not reliably kept. That is not misbehaviour. A rule written in a prompt is a probabilistic constraint: followed with high probability, not certainty, and "high probability" over enough turns is indistinguishable from "eventually broken".

Ours leaked twice over, and both leaks are worth naming because they are the usual ones. First, an existing guard checked only the literal character, but the copy was generated HTML, and the offending mark came in as an escaped entity that rendered into the final PDF while the source looked clean. A guard that watches one representation of a thing waves through all the others. Second, that guard watched file writes only, and the message that embarrassed us left through the chat tool, which nothing inspected. Most of what an agent says to a human does not go through a file at all.

What we built

One small hook, wired in before the tool runs, not after. It sits on the two surfaces where copy actually reaches a person: the reply tool and the files that generate documents. It matches the forbidden mark in every form it can take, the literal character and every entity and encoded spelling of it. If it finds one, it blocks the tool call and hands the reason back, so the next attempt is a correction rather than a blind retry. It fails open, so if the hook itself breaks it never wedges the whole agent. And it exempts the internal notes that have to describe the rule, because a gate that blocks its own documentation is a bad gate.

The proof it works is the best part, and it was not planned. While writing this up, the hook blocked its own author twice: once on a message that still carried the mark in an opening line, once on a file that quoted the entity forms in prose. The thing designed to catch the mistake caught the person making it, in the act, before it shipped. That is the whole point of moving a rule from memory to machinery.

Why it matters

This is not about punctuation. It is the line between a reminder and a control, and it is one of the most useful distinctions in building with agents. A reminder asks the model to remember and choose correctly every time. A control removes the choice at the boundary. You want reminders for the thousand soft preferences where judgement is the point. You want controls for the short list of things that must never happen: the destructive command, the secret in the logs, the client seeing the thing they told you five times they never want to see. The failure we kept having was a category error, treating a non-negotiable as a preference and then being surprised it was negotiated away. The fix was to move it into the category where enforcement does not depend on anyone, human or model, remembering anything.

The build lessons generalise cleanly. Enforce at the point of egress, not in the instructions. Match every representation of the banned thing, or it leaks through the spelling you forgot. Cover every exit, chat as well as files. Return the reason on block, so the agent self-corrects instead of thrashing. Fail open, so the guard is never worse than the bug. That is a pattern, not a punctuation fix, and it is the same pattern behind every other gate we run: the one that refuses to publish an article with no hero, the one that stops a runaway command, the audit trail that cannot be edited after the fact.

What's still off

Gates have a blast radius, and a gate is a liability if you reach for it too often. This one deliberately over-matches: it will occasionally block a legitimate use of the character, and the honest answer is that for a genuine non-negotiable that is the correct trade, you accept a false positive to buy a guaranteed true negative. That logic does not extend to soft preferences, and turning every preference into a hook would grind an agent to a halt in its own red tape, so the discipline is knowing which few rules earn a gate. It is also matching on known spellings of the mark, so a genuinely novel encoding could still slip past, which means the ban list is a thing you maintain, not a thing you set once. And a gate enforces the letter of a rule, never its spirit: this stops the forbidden character, it does not make the writing good. Controls keep the floor. They do not raise the ceiling.

What's now in the stack