Chapter 4 of 8 · 14 min read
Ranking constraints by violation cost
On this page
"Critical constraints go last" is memorable, is supported by a replicated sixteen-point result, and has no answer for a prompt with four critical constraints. Four things cannot all be last. The version of the convention that survives contact with four stakeholders is a final block containing four rules, which is a different intervention with a different result, and this chapter is about measuring that difference and about the three moves that come before any layout decision at all.
The vocabulary worth fixing first is the tail block: the short span placed after the retrieved material and immediately before the request, which chapter 1 introduced as the position where the least material stands between a rule and the first output token. The measurements below use the harness from chapter 2 with the per-rule scoring from chapter 3, on the loan explainer's prompt, whose constraints have four owners — compliance owns an affordability statement, security owns a redaction rule forbidding any customer identifier from a retrieved document appearing in the answer, design owns a 200-word panel, and support owns a rule that the answer must never quote an interest rate absent from the retrieved product sheet, because a wrong rate is a mis-selling incident.
Four final lines are a middle of their own
Each condition placed a different number of rules in the tail block, with the full rule set left in the system prompt in every case.
| Rules in the tail block | Tail size | Redaction | Rate accuracy | Statement | Length |
|---|---|---|---|---|---|
| None (production baseline) | — | 71.5% | 88.1% | 97.1% | 60.1% |
| Redaction only | 42 tokens | 87.5% | 88.4% | 96.8% | 60.4% |
| Redaction + rate accuracy | 96 tokens | 86.9% | 94.2% | 96.9% | 59.8% |
| All four | 310 tokens | 79.4% | 90.6% | 91.2% | 74.3% |
Two rules in the tail behaved additively: each moved rule gained roughly what it gained alone, and the unmoved rules stayed within the 1.6-point run-to-run spread. Four rules did not. Every rule in the four-rule tail landed below what it achieved when it was one of two, and the statement rule — which had been fine at 97% while it sat in the middle — dropped five points, because the tail block now demands brevity and completeness in adjacent sentences and the model has to resolve that itself. The tail works because very little competes with it at generation time. Filling it with 310 tokens of competing obligations reproduces, in miniature, the structure the tail was introduced to escape.
The columns are worth reading as differences rather than as levels, because the differences are what the experiment measured. Redaction starts at 71.5% in the baseline and reaches 87.5% as the tail's only occupant, which is the sixteen points chapter 2 replicated. Sharing the tail with one other rule leaves it at 86.9%, so the gain is a little over fifteen points and the second rule cost it about half a point — which is well inside the 1.6-point run-to-run spread and therefore not a measured cost at all. Sharing with three others leaves it at 79.4%, a gain of under eight points — roughly half of what the same relocation bought when it was alone. Rate accuracy tells the same story from a different starting point: it picks up six points when it joins a two-rule tail and only two and a half in the four-rule one.
The mechanism behind those halvings is chapter 1's, applied to the tail's own interior. A rule's compliance depends on how much material stands between it and the first generated token, and on how much of that material is competing for the same influence. With one rule in the tail, the answer is the question and nothing else. With four, the first rule in the block has three more rules and the question between it and generation, and those three are not neutral filler — they are instructions, in the same register, making claims of the same kind. A block long enough to hold four obligations has a beginning, an end and a middle, and the rules in that middle are back in the position the tail was invented to get them out of.
So the tail has a budget, and the budget is small — two rules on this prompt, plausibly three on a shorter one, and the number is a measurement rather than a constant. Which means the interesting work is not deciding what to put in the tail. It is reducing the set of things that are competing for it.
De-conflict first, because a conflict cannot be placed
Chapter 3's arithmetic check ran on this prompt and found one conflict: 180 words of mandated statement against a 200-word ceiling, on the 34% of cases where the statement applies. No layout resolves that, and every layout choice made while it exists is a choice about which team's rule gets violated, made by an engineer, without anyone naming it as such.
That is the general shape. Before ranking or placing anything, enumerate the constraints, compute the floors and ceilings, and take every genuine conflict to whoever owns the requirement. What comes back is usually a narrower version of one of the constraints rather than a deletion, and it is always cheaper than the alternative, which is discovering the conflict later as a compliance finding.
Rank by what a violation costs, then decide where each one lives
With conflicts removed, the surviving constraints get sorted by a question nobody usually asks explicitly: what happens when this is violated once? The answers differ by orders of magnitude, and the ranking they produce rarely matches the order in which the rules were added to the prompt. One term in the dispositions below needs pinning down first: a check fails closed when firing it withholds the response entirely, so the request errs towards returning nothing rather than towards letting a suspect answer through.
| Constraint | Cost of one violation | Mechanically checkable? | Disposition |
|---|---|---|---|
| No customer identifier in output | Reportable data incident | Yes — exact match against the identifiers in the retrieved documents | Code, fails closed; prompt copy in tail |
| No interest rate absent from the product sheet | Mis-selling incident, remediation cost | Yes — every numeric rate in the output must appear in the sheet | Code, fails closed; prompt copy in tail |
| Affordability statement present when required | Regulatory finding at audit | Yes — exact string match on the short form | Code, appended deterministically |
| Answer ≤ 200 words | Text clipped in the panel | Yes — word count | Code, truncation is unacceptable; regenerate once, else degrade the panel |
| Reply in the customer's language | Poor experience, ticket | Only with a classifier | Prompt only |
| Tone matches the brand guide | Nothing measurable | No | Prompt only |
Two things fall out of the table. The first is that the constraints whose violation is most expensive are, in this system and in most systems, the ones that are easiest to check mechanically, because expensive requirements tend to be written down precisely by the people who own them.
That correlation looks like luck and is not, and the chain behind it is short enough to check link by link. A violation is expensive when somebody outside the team can impose a cost for it — a regulator, a customer, an auditor. An outside party can only impose a cost for something it can demonstrate happened, so the requirements that carry external penalties are the ones written down in advance in terms that can be demonstrated: this exact string must appear, this identifier must not, this rate must match the sheet. A requirement stated that precisely is already a predicate; turning it into code is transcription rather than design. Run the chain in the other direction and the two unmeasurable rows in the table stop looking like an oversight. "Tone matches the brand guide" has no external adjudicator, which is why nobody wrote it precisely, which is why it has no predicate, which is why one violation of it costs nothing anyone can name.
The second thing the table shows is that after the checkable ones move into code, the tail block has two occupants rather than four, and the two it has are the two whose prompt-side compliance most directly reduces how often the code path has to fire.
Topic 1.12 made the case for putting a categorical requirement in code rather than in a prompt, and built the validator-with-repair path for a required fixed string. Two constraints here do not fit that shape, and the difference is worth being precise about. A missing disclaimer can be appended, because the requirement is that a string be present and your code can put it there. A leaked identifier cannot be un-leaked by appending anything: the requirement is that a string be absent, and the only compliant action on a response that contains one is to not return that response.
The asymmetry is not a property of these two rules, it is a property of what repair costs in each direction. Appending a required string is a function of two things the caller already holds — the response and the string — and it changes nothing else about the answer. Removing a leaked identifier is not a deletion: the sentence around it has to be rewritten so it still reads, and the only tool available for rewriting a sentence is another generation, which has its own failure rate on the same constraint. A repair that has to re-enter the mechanism that produced the defect is not a repair. That is the test to apply to any constraint before deciding which column it goes in — can the fix be computed from material the caller already has, or does fixing it require asking again.
class Blocked(Exception):
pass
def guard(resp: str, case) -> str:
"""Fails closed: on an unfixable violation, nothing is returned to the user."""
leaked = [i for i in case.identifiers if appears(i, resp)]
unsourced = [r for r in rates_in(resp) if r not in case.product_sheet_rates]
if leaked or unsourced:
metrics.increment("guard.blocked", tags={"kind": "pii" if leaked else "rate"})
audit.record(case.id, resp, leaked, unsourced) # kept for review, not shown
raise Blocked
if case.needs_statement and SHORT_FORM not in normalize(resp):
metrics.increment("guard.appended")
resp = resp.rstrip() + "\n\n" + SHORT_FORM # absence is repairable
return respFailing closed has a price and the price has to be sized before the branch ships. On this system the guard blocks about 0.9% of responses after the prompt-side rules are in place, and a blocked response costs one regeneration — a second call, roughly 1.4 seconds at p50, the median of the latency distribution — with a fallback to a canned "let me connect you to an agent" message if the regeneration also fails, which happens on about 0.05% of requests. Those two numbers are the whole argument: 0.9% of traffic paying a second call is affordable, and if the guard were firing on 20% of responses the correct response would be to stop shipping and fix the prompt, because a guard that fires constantly is not a safety net, it is a broken product with a safety net attached.
The prompt copy in the right-hand column of the disposition table is the part most likely to be deleted by whoever inherits this system, on the reasonable argument that a guarantee enforced in code does not need to be requested in a prompt as well. The reason to keep it is arithmetic rather than caution. The guard fires exactly when the prompt-side rule failed on a request that rule governed, so its fire rate is whatever the tail block left behind. Take the tail copy out and redaction falls back from its tail rate to its baseline rate, which the first table in this chapter puts sixteen points apart on the population the rule covers. A guard firing on 0.9% of responses would then be firing on a number many times larger, and a second call on 0.9% of traffic — affordable, as above — becomes a second call on a large fraction of it, which is not. The code path is what makes the guarantee categorical. The prompt copy is what keeps the guarantee cheap. They are not alternatives, and that is the sentence to put in the comment above both of them.
The convention worth writing
"Critical constraints go last" is a placement heuristic promoted to a policy, and it fails on the three cases this chapter has walked through: it has no answer when four constraints are critical, it invites a tail block that stops discriminating, and it says nothing about what to do when a constraint is critical and also cannot be satisfied alongside another one. The version that survives those cases is longer, which is the cost of it being usable.
Rank every constraint by the cost of one violation, and resolve conflicts with the requirement's owner before choosing any layout. A constraint whose violation is unacceptable does not live in the prompt: it gets a deterministic check in code that repairs where repair is possible and fails closed where it is not, with a counter on every branch. The prompt keeps a copy so the code path stays rare. At most two constraints occupy the block immediately before the request; needing a third is a consolidation problem, not a placement problem. Any change to instruction order ships with replicated per-rule compliance for every rule in the prompt, not only the one that moved.
The last sentence is what makes the rest enforceable, and it is also the answer to a reviewer asking what a safety-motivated placement change has to carry before it merges. A diff that moves a safety constraint and reports a single improved number is not reviewable: it could be chapter 1's clean win, chapter 2's unreplicated noise, or chapter 3's rule swap, and the three are indistinguishable from the outside. The reviewable version reports the moved rule's before and after with a spread from repeated runs, every other rule's before and after, the guard's expected fire rate under the new layout, and the counters that will make the residual rate visible in production rather than at the next audit.
Where this runs out
Two of the six constraints in the table have no mechanical check, and pretending otherwise is the most common way this framework gets misapplied. "Matches the brand guide" has no predicate; a judge model could be built for it, and the judge would have an error rate of its own that has to be measured and maintained, which converts a statistical control into a statistical control with an extra moving part. For constraints of that kind the honest position is that you have a rate, not a guarantee, and the lever that actually moves the risk is narrowing what the system is allowed to do rather than improving how it is asked.
The order to work in, then, is de-conflict, rank, relocate, and only then place. Each step reduces the number of things competing for the one position that carries the most weight, and the last step is the only one that is about prompting at all. A team that starts at the last step is choosing which stakeholder loses, in a meeting where nobody says so.
The other limit is that everything decided so far has been decided on reliability alone. Every layout in this chapter's table was evaluated by per-rule compliance, and the four-rule tail was rejected because it discriminated poorly — but none of these layouts is free. The static block that a shared prefix makes cheap, the tenant-specific block that has to go somewhere, the 310 tokens of tail: each of those choices lands on a bill and on a latency budget as well as on a compliance number, through a mechanism most teams believe they understand backwards. The next chapter is about a reviewer blocking a late-placement change on the grounds that it breaks prompt caching, and about why the mechanism says the opposite.
Instruction placement and recency — read the current version at https://daviddominguez.dev/learn/en/instruction-placement-and-recency/ranking-constraints-by-violation-cost