Skip to content

15 min read

Instruction set saturation

On this page

Every formatting rule in a prompt is cheap on its own. The first few are straightforwardly positive: a line about thousands separators fixes three cases and breaks nothing, a line about multi-line addresses fixes four. Somewhere between the tenth and the twentieth that stops being true, and the signal is specific — a new rule fixes the reported case and regresses two that passed the previous week. The manager's reading of that is one fixed customer against two internal test cases, which is not unreasonable and is arithmetic on the wrong quantities.

Saturation is the state where rules interact at the edges and compete for adherence, so appending is negative-sum. It is a property of the set rather than of the new rule, which means no amount of care in writing rule twenty-three recovers it, and it is measurable rather than a matter of feel. This chapter is about the two mechanisms that produce it, the cheap discriminator between them, and what a fix looks like when the prompt is no longer the place to put one.

What a saturating instruction set looks like

The useful number is never in the current ticket. It is in the history of the previous ten. Pulling each formatting rule's commit, its ticket, the case it was added to fix, and the regression cases that were touched in the same week produces a ledger that most teams have never assembled — here, for a document-extraction prompt that had grown twenty-two formatting rules over about fourteen months.

RuleTicketCases fixedCases regressedNet
14thousands separator in totals30+3
15strip trailing whitespace from vendor names20+2
16multi-line addresses joined with commas40+4
17negative amounts on credit notes21+1
18VAT rate as decimal not percent31+2
19ignore watermark text21+1
20line-item descriptions truncated at 120 chars110
21currency code inferred from symbol220
22do not normalise vendor legal suffixes12−1
23European date order12−1

The last column is one subtraction per row, and the whole argument is in its sign. Cases fixed minus cases regressed: rule 17 fixed two and cost one, so it nets one; rule 20 fixed one and cost one, so it nets nothing; rule 23 fixed one and cost two, so it nets minus one. Read down the column and the sequence runs clearly positive, then smaller, then flat, then negative. The reason the ledger has to be assembled at all is that no single row shows any of that. Every row is a defensible ticket outcome taken on its own — even the negative ones read as a paying customer fixed against two internal test cases — and only the sequence says the mechanism producing them has changed underneath.

The trend is the argument. Rules 14 to 16 were straightforwardly positive, rules 17 to 19 cost something, and from rule 20 onwards the prompt has been trading roughly one regression per fix. Rule 22 was already negative and shipped anyway, which is why nobody noticed that rule 23 was not an anomaly.

Adherence dilution can be measured directly. Take a single formatting rule, hold it out, and measure adherence to it in isolation and then embedded in progressively larger rule sets, using the same fifty documents each time. On this prompt, rule 16 — multi-line addresses joined with commas — was obeyed on 98 percent of applicable documents when the prompt carried six formatting rules, 94 percent at fourteen rules, and 87 percent at twenty-two. Nothing about rule 16 changed and no other rule mentions addresses. The rule simply gets a smaller share of the model's adherence as the instruction set grows, in the same way that a queue with more producers gives each one less throughput. On top of that, every rule added multiplies the number of rule pairs that can collide, which is the rule-35 arithmetic from the previous chapter, and the collisions are what turn a 13-point adherence drop into specific, reproducible regressions.

The queue in that paragraph is an analogy, and the place it breaks is worth naming, because that is where a wrong prediction would come from. A queue has a fixed capacity being divided, so the split is exact and adding a producer takes a computable amount from each of the others. Nothing measured here establishes that adherence is a conserved quantity being divided. What was measured is a rate falling as the set grows, and that is equally consistent with rules interfering with one another, with longer instruction blocks being harder to follow in general, and with something specific about the particular siblings that were added. The analogy is a way of remembering the direction of the effect. It is not a model anyone can predict the next number from, which is precisely why the ledger has to exist.

Collisions and dilution

The two mechanisms produce different symptoms, and telling them apart decides where you look next. A collision is deterministic given the input: a purchase order picks the footer date on fifty runs out of fifty, because two rules both fire on that document and one of them wins consistently at the temperature the extractor runs at. Dilution presents instead as cases that pass on some samples and fail on others, at a rate drifting downwards as the prompt grows rather than snapping to zero, which is what rule 16's address joining did — three suite cases flapping between green and red for about two months before anybody filed them.

Why one is stable and the other is not comes from where each sits relative to the sampler. A collision means two rules both fire on the input and push toward different outputs, and one of them wins by a margin wide enough that the sampling at the temperature the extractor runs at never crosses it — so fifty runs give fifty identical answers, and the answer is wrong on all fifty. Dilution creates no competitor. It lowers how strongly one rule is being followed, which moves the margin between the right output and the next candidate closer to zero, and a margin near zero is exactly the condition under which the sampler decides the outcome. That is why the symptom is a rate rather than a verdict, and why the rate is informative: it is the size of the remaining margin, read out through the sampler.

The cheap discriminator is to re-run the failing case thirty times. A stable failure sends you looking for the rule it is arguing with, which is the bisection procedure from earlier in this topic. A rate between ten and forty percent sends you somewhere different: measure adherence to that one rule in isolation, then in the full prompt, and if the gap is large the problem is the size of the set rather than any particular sibling.

Moving a constraint out of the prompt

Classify a saturated prompt's rules by what kind of thing they actually are, and most of them turn out not to be instructions at all.

MechanismRules it absorbsHow it is enforcedWhat it fails at
Post-processing in code14 — dates, thousands separators, currency codes, casing, whitespace, truncation, VAT decimalsDeterministic, unit-testable, no model involvementAnything requiring the model to decide which value is the right one
Schema constraints — types, enums, pattern, maxLength3 — currency must be an ISO-4217 code (the three-letter currency codes, EUR, USD), amounts numeric, description lengthThe decoder cannot emit a violating tokenCannot express relationships between fields
Few-shot examples instead of prose rules3 — legal-suffix handling, watermark text, credit-note signsDemonstrated rather than stated; does not enter the precedence contestCosts more tokens than the rule it replaces
Router to per-document-type promptsSplits the remainderEach prompt carries only the rules its document type needsA misrouted document gets the wrong rule set

The rewrite took a week and left five prose rules in the extraction prompt, all of them about judgement the model genuinely has to exercise — which of three candidate totals is the invoice total, whether a line is a line item or a subtotal, whether a page is part of this document or the next one. Everything deterministic moved into normalise.py, everything expressible as a type moved into the schema, and three rules became examples. Re-measuring rule 16's adherence in the five-rule prompt put it back at 97 percent, and the regression suite gained fourteen new unit tests that run in 40 milliseconds instead of forty seconds of model calls.

The general principle underneath is that a prompt rule is the most expensive and least reliable place to put a constraint. It costs tokens on every request, it is enforced probabilistically, it competes with every other rule, and it can only be tested by sampling. A line of Python costs nothing per request and is enforced absolutely. Before writing rule n+1, the question worth asking is whether the constraint is deterministic given information the system already has, because if it is, it does not belong in the prompt.

Flowchart: New constraint from a ticket → Deterministic given data the system already holds?; Deterministic given data the system already holds? → yes; yes → Post-processor in code; Deterministic given data the system already holds? → no; no → Expressible as a type, enum or pattern?; Expressible as a type, enum or pattern? → yes; yes → Schema constraint; Expressible as a type, enum or pattern? → no; no → Easier to show than to state?; Easier to show than to state? → yes; yes → Few-shot example; Easier to show than to state? → no; no → Applies to one document type only?; Applies to one document type only? → yes; yes → Route to that type's prompt; Applies to one document type only? → no; no → Prose rule; count how many now touch this field.

The last box is not a failure of the procedure, since five rules in this prompt ended there and belong there. What the path buys is that a rule arriving by it has been refused by four cheaper mechanisms first, so the prose section grows only where nothing else can hold the constraint.

Long prompts and overlapping scope

None of this makes long prompts wrong in general. Saturation is a property of overlapping scope rather than of token count. Twenty-two rules covering twenty-two disjoint fields, each mentioning its field and nothing else, can coexist comfortably, while eight rules that all constrain how dates are chosen will start interfering with each other well before the eighth. What makes a prompt saturate is how many rules can fire on the same input and constrain the same observable, so the useful audit question is not "how many rules do we have" but "how many rules apply to a single field".

Counting that takes twenty minutes: list the fields the output schema defines, walk the rules once, and mark every field each rule can constrain, counting a rule again for each field it touches.

Output fieldRules that can constrain itRegressions in 14 months
invoice_date5 — rules 9, 11, 13, 19, 234
total_amount6 — rules 10, 14, 17, 18, 20, 215
vendor_name3 — rules 12, 15, 222
line_items2 — rules 16, 201
currency2 — rules 18, 211
billing_address1 — rule 160
po_number1 — rule 70

Two fields carried eleven of the twenty-two rules between them and nine of the thirteen recorded regressions, while the fields governed by one or two rules each had produced almost nothing in over a year. The count says where restructuring pays first, because invoice_date and total_amount were exactly the two fields whose rules moved wholesale into normalise.py, and they are the reason the surviving five prose rules stopped interfering — no two of them constrain the same output field.

Both of those figures add up off the table, and the counting rule matters if you are building one for a prompt of your own. The two crowded fields carry five rules and six rules, which is the eleven; their regression counts are four and five, which is the nine, against thirteen down the whole column. A rule is entered once for every field it can constrain, so a rule touching two fields appears on two rows — and that double entry is the property being measured rather than a defect in the tally, because a rule constraining two fields really does participate in two contests. What the table ends up ranking is therefore neither rules nor fields but contested fields, which is why it predicts where the regressions landed when a plain rule count would not.

The other half of the argument is the one that changes anything, and it is about the cleanup ticket rather than about rule 23. The ticket that would justify shipping is the same ticket that produced rules 12 through 22 — each of those was also going to be tidied up later, and the ledger is what "later" produced. What has actually changed is the state of knowledge: the team now has a measurement showing the rules interact, so every subsequent formatting fix carries an unmeasured regression cost, and nobody has budgeted for measuring it. Asking for that budget is a concrete request with a number attached — the ledger took an afternoon to build and the adherence measurement took about 900 model calls — rather than an appeal to prompt hygiene.

Two practical limits sit on the diagnostic side. The ledger needs a ticket and a regression record per rule, and plenty of prompts have neither — rules arrive in commits called "prompt tweaks" with no linked case, and the regressions of fourteen months ago were fixed by whoever was on call and never counted. The substitute is to build the ledger forward rather than backward: from the next rule onwards, record the cases fixed and the cases regressed in the same pull request, and three or four entries are enough to show a trend. The field count needs no history at all, which is why it is the better first move on a prompt you have just inherited.

The other limit is that saturation and conflict ask for different responses even though they show up in the same suite. A colliding pair is repaired by removing or narrowing one of the two, and the prompt can stay the size it is. A saturated set is not repaired by any single removal, because the next rule will dilute adherence exactly as much as the last one did; what it asks for is that a whole category of constraint leaves the prompt. Treating a saturation symptom as a conflict produces an afternoon of bisection that correctly identifies a pair and buys three weeks.

Saturation is the point at which the cheapest available fix stops being the cheapest available fix, and it is invisible from inside any single ticket because every individual rule still looks like four minutes of work. The ledger is what makes it visible, the field count is what says where to start, and both are an afternoon.

Both of those diagnostics worked because somebody was measuring the right things. There is a worse position to be in, which is a prompt where the instructions have quietly stopped matching the system around them, the accuracy metric reads 0.91, and the metric is computed in a way that structurally cannot see the defect.

Instruction conflicts, over-constrained prompts, prompt bloat — read the current version at https://daviddominguez.dev/learn/en/instruction-conflicts-over-constrained-prompts-prompt-bloat/instruction-set-saturation

Back to top