Chapter 6 of 7 · 9 min read
A hedge is a rule the model can honour and still break
On this page
Over five weeks the team reviewed 23 agent-authored migrations and sent six back for the same reason. Each one widened a column type and backfilled it in a single file — ALTER TABLE events ALTER COLUMN payload TYPE jsonb followed by an UPDATE events SET payload = payload::jsonb. On a table with 210 million rows the type change alone rewrites the heap under an ACCESS EXCLUSIVE lock, and the one that reached staging held it for 41 minutes with every writer blocked behind it.
The skill fires correctly on all of these; selection is not the problem. The rule is in the body, seventh in a list of nine, and it reads:
- Avoid combining type widening with backfills where possible.Eleven words, two defects
The hedge is the first. "Avoid" and "where possible" together describe a preference with an escape clause, and a model can satisfy the sentence completely while producing the migration that locked the table: it considered the split, judged that combining was simpler here, and combined. Nothing in the output contradicts the instruction. That is what makes a hedge different from a rule that is ignored — there is no violation to point at, and the reviewer who wants to reject the PR has to argue about whether it was possible rather than about whether it happened.
The second defect is the eight other items. Instructions in a body compete for the model's attention against each other and against the task the user asked for, and a rule sitting seventh in an undifferentiated list gets a share of that attention proportional to nothing in particular. The competition is real and measurable, and it is separate from the phrasing: the same sentence in a list of four behaves differently from the same sentence in a list of nine.
There is a third thing missing, which is the part that turns out to matter most. The rule forbids something and offers nothing in its place. The agent has been asked to widen a column and backfill it, and the body tells it not to do that in one migration without saying what the two-migration version looks like, so it is choosing between following the rule and completing the task it was given. The task wins, because the task is what was asked.
Numbers instead of an argument about wording
Wording arguments do not converge, so the team built a fixture of 30 migration tasks that all involve a type change with a backfill and replayed them against variants of the body.
| Body variant | Compliance | Change |
|---|---|---|
| Original hedge, item 7 of 9 | 61% | — |
Same words, item 1, bold, MUST NOT | 74% | +13 |
| Imperative, no hedge, no alternative, item 7 of 9 | 79% | +18 |
| Imperative with the two-migration procedure, item 7 of 9 | 88% | +27 |
| Imperative with procedure, list cut to 4 items | 94% | +33 |
Compliance here has a mechanical definition, which is what makes the column comparable across rows: a task passes when the agent produces two migration files and the file containing the ALTER ... TYPE contains no UPDATE. That is checked by a four-line grep over the output rather than by reading the plans, so a variant costs about six minutes of replay and no judgement calls. Defining the pass condition before running anything also settles the argument the hedge was creating, since "avoid where possible" has no grep that implements it — the first thing the fixture forces is a statement of what the rule actually forbids.
At 30 samples per variant the sampling error is roughly ±5 points, so the difference between 88% and 94% is at the edge of what this fixture can resolve and the difference between 61% and 88% is not. The two changes that carry the result are removing the hedge and supplying the compliant path:
Never widen a column type in the same migration as a backfill. Split it:
1. Migration A — the type change alone:
`ALTER TABLE events ALTER COLUMN payload TYPE jsonb USING payload::jsonb;`
Deploy and confirm it completed before writing Migration B.
2. Migration B — the backfill, in batches:
`UPDATE events SET payload = ... WHERE id > $last ORDER BY id LIMIT 5000;`
Commit each batch. Sleep 100 ms between batches. Record `$last` so the
job is resumable.Nine points of the twenty-seven come from the phrasing and the rest from the procedure, which is worth knowing when you are deciding where to spend an afternoon. A prohibition tells the model what not to produce and leaves it to invent the alternative under time pressure; a procedure gives it something to produce instead, and producing something is what it is trying to do.
Emphasis buys points and is not a mechanism
The move everyone makes first is to move the rule to the top of the list, set it in bold, and write MUST NOT. That row is in the table because it works — thirteen points is a real improvement and it costs about four minutes, which is exactly why the belief that emphasis is the answer is so durable.
It does not compose. Emphasis is relative, so its effect depends on being scarce, and the natural response to a second violated rule is to bold that one too. When this team bolded five of the nine items over the following two months, the target rule's compliance drifted back to 68%, inside noise of where it started. A body in which everything is marked important contains the same ranking as a body in which nothing is.
The more expensive cost is what the team now believes. A rule presented in bold capitals reads as enforced, and a rule that reads as enforced changes how the next violation is received: the reviewer who finds one treats it as a strange one-off, files no pattern, and does not ask whether the second line of defence exists. At 94% compliance and 23 migrations a quarter, the expected number of violations is 1.4 per quarter, and a team that has written that number down will have a review checklist item or a check on the artefact waiting for it. A team that believes it is at 100% has nothing waiting, and the 41-minute lock arrives as a surprise for the second time.
Improved, not total: what to do and what to refuse
Compliance at 88% is the beginning of a decision rather than the end of the work, and the twelve points missing from it are the residual, the share of runs in which a well-phrased rule is still not followed and which no rewording drives to zero. The next steps are to publish that residual as a number and a consequence — 1.4 expected violations per quarter, each one a potential multi-minute lock on a hot table — and then to decide whether that consequence is acceptable at that rate. If it is, the rule stays as an instruction, and the residual is handled by something outside the skill, usually a targeted line on the human review checklist for that one rule. If it is not acceptable at any rate, phrasing has run out of road, which is the subject of the next chapter.
Three responses are worth refusing explicitly, because each one is somebody's reasonable next suggestion.
The first is adding another line after each violation. "Remember that type changes and backfills must be separate" appended below the existing rule does not reinforce it; it makes ten items where there were nine, and the fixture says the marginal item costs compliance on everything already there. The second is escalating emphasis, for the reasons above. The third is describing the rule as enforced in any document a reviewer will read, which converts a known residual into an unexamined assumption.
Adding three rules has an exchange rate
The team's next request is to add three more items to the same list — an index-naming convention, a rule about NOT VALID constraints (the Postgres form that adds a constraint without checking the rows already in the table, leaving that check for a separate pass), and a note about statement timeouts. Each is defensible on its own, and the question they are being evaluated against is the wrong one, because the cost of an item does not fall on the item.
Replaying the same fixture with the list at four, nine and twelve items, holding the target rule's phrasing fixed, gives 94%, 88% and 84%. Three new rules cost roughly four points of compliance on every rule already present, including the one whose violation locks a 210-million-row table. That is the exchange rate, and once it is on the table the conversation stops being about whether the index-naming convention is a good idea.
The answer is that the list is a budget rather than an append-only log, so an addition needs a source of space: remove an item, move an item somewhere deterministic, or accept the drop and name which existing rule you are willing to see violated more often. Index naming is a strong candidate for removal in the sense that its violations are caught by a linter and cost a rename; the NOT VALID rule is a strong candidate for moving out of the body entirely.
Two steps that contradict each other mean rewrite
Dilution and hedging are both problems of degree, and they show up as a compliance number you can move. A contradiction is a different signal and it is the strongest indication that a body needs rewriting rather than extending.
This body has one. Step 2 says to add new columns as nullable and backfill them afterwards. Step 8, added four months later by someone fixing a different miss, says new columns must be NOT NULL with a default so that application code can rely on them. Both are correct advice for different situations, neither names its situation, and together they license either behaviour on every task. The compliance fixture reads fine, because whatever the agent produces satisfies the body — which is precisely why the metric cannot find this class of defect.
What a contradiction reveals is how the body was built. Nine items that grew one violation at a time have no author who has read them together, and a document assembled that way will keep producing contradictions as it grows, because each new item is written against the miss that prompted it and not against the other eight. The repair is to re-derive the list from the procedure it is supposed to describe, which in this case collapses steps 2 and 8 into a single conditional — nullable during the migration window, NOT NULL in a follow-up once the backfill has completed — and drops two items that were restating each other in different words.
The part phrasing cannot reach
Every number in this chapter is short of 100%, and the shortfall is not a defect to escalate. An instruction in a body is an input to a probabilistic process, so the honest description of a well-phrased rule in a short list is a rate, and the design question is whether the consequence of the residual is one the team can absorb.
For the index-naming convention it plainly is: a badly named index costs a rename in a follow-up PR. For a 41-minute exclusive lock on events during business hours it is not, at any rate, and no amount of rewriting produces a rule with a violation rate of zero. Rules in that second category have to leave the body.
Writing a skill — read the current version at https://daviddominguez.dev/learn/en/writing-a-skill/a-hedge-is-a-rule-the-model-can-honour-and-still-break