Rewrite: a box costs 34 pounds, and nothing knows it
Five sentences of background at the top of a subscription support prompt. Two of them are in the compiler's world model and three are furniture, and the sentences do not look any different from each other.
TL;DR Only a copula binds: Our goodwill credit limit is 20 pounds and Our support hours are 8am to 8pm claim slots the prover can reach, while A box costs 34 pounds and The skip window closes 5 days before a delivery bind nothing and can be contradicted for free. State each of those pairs twice with different numbers and the copula versions are 6 error and 2 error; the verb versions are 0 error, 0 warning, 0 info. Sort the block by destination instead: rules get a bound, runtime facts get a $REQUIRE variable, and only what the agent must state correctly stays a fact. B (82/100) to A (98/100).
Nobody argues about the Context section. It goes in first, it gets written in ten minutes by whoever set the project up, and it is never reviewed again, because it does not tell the agent to do anything. It is background: the things a new starter would be told on day one, written down so the model has them too. That is a real job and the section deserves to exist. What nobody notices is that the compiler is already reading every line of it and sorting the sentences into two piles, and that the sorting rule is a point of English grammar rather than anything to do with which facts matter.
§1The before
Wren answers subscription chat for Larkspur, a meal-kit company. Three rules, and above them the five things the CX lead thought the model should know.
# Role You are Wren, a customer support agent for Larkspur, a meal-kit subscription service. # Context Larkspur delivers on Tuesdays and Fridays. Our support hours are 8am to 8pm. The skip window closes 5 days before a delivery. A box costs 34 pounds. Our goodwill credit limit is 20 pounds. # Constraints - You MUST NOT credit an account more than 20 pounds in a quarter. - You MUST NOT let a customer skip a box inside the skip window. - WHEN a customer asks to cancel THEN you MUST offer a pause before you cancel.
$ typeglish check larkspur.tg larkspur.tg:5:1 info prompt/unregistered-doer A bare generic doer never enters the world model - instruction to the agent, or background about users? Use the imperative if the agent acts, or a definite party ("The user should ...") to register the doer. ✓ 1 file — 0 error, 0 warning, 1 info $ typeglish score larkspur.tg larkspur.tg — B (82/100) proven errors: none tiers: base+z3 planes runtime 93 (what the model reads) · hygiene 50 (source only) facets enforceability 80 x.21 · hardness 100 x.12 · directness 97 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 0 x.12 (hygiene) · style 92 x.08 · security 100 x.08
§2Which sentences are in the world model
There is a cheap test for whether a line binds anything: state it twice with two different values and see whether the compiler objects. If the fact is in a slot, a second value for that slot is a conflict. If it is prose, the two sentences never meet. Five files, one per Context line, each with the line duplicated and the number changed.
$ tail -2 p1.tg Larkspur delivers on Tuesdays. Larkspur delivers on Wednesdays. $ typeglish check p1.tg ✓ 1 file — 0 error, 0 warning, 2 info $ tail -2 p2.tg Our support hours are 8am to 8pm. Our support hours are 9am to 5pm. $ typeglish check p2.tg ✗ 1 file — 2 error, 0 warning, 1 info $ tail -2 p3.tg The skip window closes 5 days before a delivery. The skip window closes 2 days before a delivery. $ typeglish check p3.tg ✓ 1 file — 0 error, 0 warning, 0 info $ tail -2 p4.tg A box costs 34 pounds. A box costs 39 pounds. $ typeglish check p4.tg ✓ 1 file — 0 error, 0 warning, 0 info $ tail -2 p5.tg Our goodwill credit limit is 20 pounds. Our goodwill credit limit is 30 pounds. $ typeglish check p5.tg ✗ 1 file — 6 error, 0 warning, 0 info
The dividing line is the verb. IS, ARE and AM are the one copula operator, and a copula binds a name to a value, so Our support hours are 8am to 8pm and Our goodwill credit limit is 20 pounds are declarations. Every other verb makes an ordinary sentence: costs, closes and delivers are just words the model reads. The facts are equally true, equally important and equally visible to a human reviewer. Only one of the two groups can be contradicted by a later line and have anybody find out.
$ typeglish check p6.tg p6.tg:5:1 error logic/contradiction Conflicts with line 5. Contradiction — single-valued "box.price" is assigned 2 different values ("34 pounds", "39 pounds"). Keep one, or hold several with a list ("... is 34 pounds and 39 pounds"). p6.tg:5:1 error logic/measure Money conflict on box — "exactly GBP 34" and "exactly GBP 39" cannot both hold. (with line 5) p6.tg:5:1 error logic/measure Money conflict on box.price — "exactly GBP 34" and "exactly GBP 39" cannot both hold. (with line 5) p6.tg:6:1 error logic/contradiction Conflicts with line 4. Contradiction — single-valued "box.price" is assigned 2 different values ("34 pounds", "39 pounds"). p6.tg:6:1 error logic/measure Money conflict on box — "exactly GBP 34" and "exactly GBP 39" cannot both hold. (with line 4) p6.tg:6:1 error logic/measure Money conflict on box.price — "exactly GBP 34" and "exactly GBP 39" cannot both hold. (with line 4) ✗ 1 file — 6 error, 0 warning, 0 info
p4.tg with one word changed: A box costs 34 pounds became The box price is 34 pounds. Same fact, same number, same section. The possessive reading binds box.price, and three layers report on it independently.Two facts only meet if both of them are declarations. A fact and a rule never meet at all.
§3The edit that gets through
The failure this section causes in production is not a duplicated fact. It is a single edit made in the right place and not the other one. Finance raises the quarterly goodwill limit from 20 pounds to 30, the ticket says update the agent prompt, and somebody opens the file and changes the rule.
$ typeglish check ed1.tg # + "A box costs 39 pounds." ✓ 1 file — 0 error, 0 warning, 1 info $ typeglish check ed2.tg # + "Our goodwill credit limit is 30 pounds." ✗ 1 file — 6 error, 0 warning, 1 info $ typeglish check ed3.tg # the RULE changed to 30, the Context left at 20 ✓ 1 file — 0 error, 0 warning, 1 info
The middle case is the interesting one, because it is the file that turns red, and no reviewer would ever have written it: nobody states the same background fact twice in a row with two different numbers. The realistic mistake is the third, and there is no diagnostic for it, because a rule bound and a declared value are not two claims on one slot. The rule says do not exceed 30 and the fact says the limit is 20, and those are consistent in the same way that do not drive over 50 is consistent with the speed limit is 30. Somebody has to notice.
Which is the argument for the rewrite. The fix is not to add a check. It is to arrange the file so the number exists in exactly one place, because two copies of a figure will eventually disagree and one copy cannot. That is the same structural move as lifting a shared paragraph into an $IMPORT, done at the scale of a single value.
§4The rewrite: sort by destination
Go through the Context block line by line and ask what each sentence is actually for. Three answers come back, and only one of them is background.
- It constrains what the agent does. Then it is a rule, and it belongs in Constraints with a bound. The skip window and the goodwill limit both fail this way in the before file: they read as background and they are policy with the modal filed off.
The skip window closes 5 days before a deliverybecomeswithin 5 days of a deliveryinside the rule that uses it, and the separate fact disappears. - It changes per conversation. Then it is an input, not a fact.
Larkspur delivers on Tuesdays and Fridaysis true of the company and false of any individual subscription, which is why it drew theprompt/unregistered-doer: it is a claim about a third party that the agent is somehow meant to act on. It becomes$REQUIRE variable delivery_day: one of tuesday, fridayand a rule that says it out loud. - The agent has to state it correctly. Then it is genuinely a fact, and it gets the copula so that it binds. The box price and the support hours stay, rewritten as declarations.
<$CONFIG> $REQUIRE variable delivery_day: one of tuesday, friday </$CONFIG> # Role @@ role: subscription support for Larkspur meal kits You are Wren, a customer support agent for Larkspur, a meal-kit subscription service. # Context @@ pricing owns this line and it is the only place the figure is written The box price IS 34 pounds. @@ the roster, not a promise to a customer Our support hours ARE 8am to 8pm. # Constraints @@ FIN-88: above this a supervisor decides - You MUST NOT credit an account more than 20 pounds in a quarter. @@ the packing list locks 5 days out - You MUST NOT let a customer skip a box within 5 days of a delivery. @@ CX-140: a pause saves three cancellations in ten - WHEN a customer asks to cancel THEN you MUST offer a pause before you cancel. @@ the delivery day is per subscription and arrives with the conversation - WHEN a customer asks when a box arrives THEN you MUST say @{delivery_day}. $TEST box_price - input:: How much is a box? - expect:: - contains "34" $TEST cancel_intent - input:: I want to cancel my subscription today. - expect:: - The reply offers a pause before it processes a cancellation.
$ typeglish check wren.tg ✓ 1 file — 0 error, 0 warning, 0 info $ typeglish score wren.tg wren.tg — A (98/100) proven errors: none tiers: base+z3 planes runtime 98 (what the model reads) · hygiene 100 (source only) facets enforceability 93 x.21 · hardness 100 x.12 · directness 95 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 100 x.12 (hygiene) · style 100 x.08 · security 100 x.08 $ typeglish test wren.tg --dry ✓ wren.tg coverage: 1/4 rules exercised ✓ 1 prompt — 0 failed $ typeglish build wren.tg ✓ built .typeglish/dist/wren.txt ← wren.tg (e07489ff4d4c, full)
The coverage number deserves a footnote, because it is the one place the two files can be compared on a denominator. Bolt the same two cases onto the before file and it reports coverage: 1/3 rules exercised; the rewrite reports coverage: 1/4. Five Context lines contributed nothing to either total, in either version, because a fact is not a rule and there is nothing about it to exercise. A Context block can therefore grow to a page without moving any number in the toolchain, which is worth knowing the next time a small-looking denominator makes a prompt feel well covered.
§5What the rewrite bought, and what it did not
The payoff is the next edit rather than today's file. Append a second box price or a second set of support hours to the rewritten prompt and both refuse to build, where the before file absorbed the price edit in silence.
$ typeglish check f.tg # + "The box price IS 39 pounds." f.tg:24:1 error logic/contradiction Conflicts with line 7. Contradiction — single-valued "box.price" is assigned 2 different values ("34 pounds", "39 pounds"). ✗ 1 file — 6 error, 0 warning, 0 info $ typeglish check f.tg # + "Our support hours ARE 9am to 5pm." f.tg:13:1 error logic/time Conflicts with line 14. Time conflict — "8am to 8pm" and "9am to 5pm" give "our support hours · be" two different windows; one schedule per slot per scope. f.tg:13:1 info prompt/naive-time This prompt states clock times but never fixes a timezone - the model will guess one at runtime. ✗ 1 file — 2 error, 0 warning, 1 info
Two honest limits ship with the rewrite. The first is that the skip window did not gain a proof by becoming a rule: appending You MUST NOT let a customer skip a box within 2 days of a delivery is 0 error, 0 warning, 0 info, because a duration hanging off a compound action does not lower into the time layer the way call a customer back within 2 hours does. It is better written as a rule than as background either way, since it is now in the denominator and carries a bound a reader can see, but do not expect the second one to fight the first.
The second is the one the whole section turns on: nothing forces the box price in the declaration to match the box price in the world. The compiler will prove that a file states one price, and will refuse a file that states two. Whether the number is right is still a job for a person, and the reason to put it on one line with a @@ note naming its owner is so that person is identifiable. Related reading if the same figure lives in several agents rather than several lines: the paragraph that lives in three prompts.
§6Common questions
- Should company facts go in the Context section of a system prompt?
- Only the ones that are genuinely facts. Most of what ends up in a Context block is either a rule with the modal filed off (a cut-off, a credit limit) or an input that varies per conversation (a delivery day), and both of those are worse off as background prose. Sort the block by destination: a line that constrains behaviour becomes a rule with a bound, a line that varies at runtime becomes a
$REQUIRE variable, and only a line the agent must state correctly stays as a fact. Doing that to a five-line Context block takes a meal-kit prompt from B (82/100) withenforceability 80to A (98/100) withenforceability 93. - Does typeglish check the facts in my Context block?
- It checks the ones written with a copula and ignores the rest, and the difference is invisible when you read the section.
Our support hours are 8am to 8pmandOur goodwill credit limit is 20 poundsbind slots, so stating either twice with different values is a blocking error: 2 for the times vialogic/timeand 6 for the money vialogic/contradictionpluslogic/measure.A box costs 34 poundsandThe skip window closes 5 days before a deliveryuse ordinary verbs, bind nothing, and are0 error, 0 warning, 0 infohowever many times you contradict them. The fix is one word:The box price is 34 poundsis the same fact and it bindsbox.price. - Why did my prompt not catch a number I changed in only one place?
- Because a number in a rule and a number in prose never meet. Raising a credit limit from 20 pounds to 30 in the Constraints while leaving 20 in the Context is
0 error, 0 warning, 1 infoand it builds, since a rule bound and a background sentence are not two values for one slot. Two declarations of the same slot are, which is the argument for keeping each figure on exactly one line and having the rules refer to it by name rather than restating it. Then the desynchronised edit is not caught, it is impossible. - What is the difference between a declaration and a fact in a prompt?
- A declaration renders and binds; a fact only renders. Both reach the model identically, so the model cannot tell them apart and neither can a reviewer, but the compiler can: a declaration puts its value in a slot where a later value can collide with it, and where an
@{pointer}can read it back.IS,AREandAMare the one copula, so the grammar of the sentence decides which you wrote. One caveat worth knowing: an unquoted multi-word definite binds nothing, soThe skip window is 5 daysneeds to beThe %skip window% is 5 daysif you want the name. - Do background facts count towards test coverage?
- No, and that is the right answer. Running
typeglish test --dryover the before file reportscoverage: 1/3 rules exercisedand the rewritten file reportscoverage: 1/4, with the five Context lines contributing nothing to either denominator, because a fact is not a rule and nothing about it can be exercised. It also means a Context block can grow indefinitely without the coverage number moving, which is worth remembering the next time a clean 1/3 makes a prompt look small.
The habit worth taking from this is a five-second read rather than a refactor. Open any prompt you own, scroll to the background section, and read only the verbs. Every line whose main verb is is, are or am is in the compiler's world model and can be defended; every other line is text the model reads and nothing else. That distinction is doing real work in your file already, and it was decided by whoever typed the sentence, on a section nobody reviews, in the ten minutes before the first rule was written. The version of this that keeps us up at night is not the box price. It is the line in some prompt somewhere that reads our refund limit is 100 dollars, which binds, sitting three sections above a rule that caps refunds at 250, which does not collide with it, in a file whose check comes back green and whose score is a B. Both numbers are in the artifact. The model picks one.