← Tidelines/Best practices

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.

by TypeGlish team8 min read#best-practices
Five facts. Two slots.

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.

larkspur.tg - the prompt as written✓ exits 0
# 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.
Nothing here is wrong. Every fact is true, every rule is well formed, and the 20 in the Context and the 20 in the rule are the same 20 because somebody kept them in step by hand.
tg check, then tg score - output✗ B (82/100)
$ 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
One info, on the only Context line whose subject is the company. Everything else in the section passes without comment, which is not the same as passing.

§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.

tg check, each Context line stated twice - output✗ two of five
$ 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
Two of the five facts are in the world model. Three are text. Sorted by how much money is attached to them, the ordering is close to random.

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.

tg check p6.tg - the same fact, written with a copula
$ 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
This is 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.

tg check, three one-line edits to the before file - output✗ the wrong one is red
$ 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 third is the edit somebody actually makes. It ships a prompt that states a 20 pound limit as background and enforces a 30 pound one as policy, and it exits 0.

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 delivery becomes within 5 days of a delivery inside 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 Fridays is true of the company and false of any individual subscription, which is why it drew the prompt/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, friday and 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.
wren.tg - the same prompt, sorted✓ A (98/100)
<$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.
Five Context lines became two declarations, one typed input and two rules. Not one fact was changed and nothing was added except the notes.
tg check, score, test --dry and build - output✓ B (82/100) to A (98/100)
$ 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)
Enforceability 80 to 93 is the honest half of the jump: two background sentences turned into rules with bounds. Annotation 0 to 100 is the other half.

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.

tg check, one stale line appended to wren.tg - output✗ both refuse
$ 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
The timezone info only appears once there are two clock times to compare, which is a fair summary of this whole post: a single fact has nothing to be wrong against.

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) with enforceability 80 to A (98/100) with enforceability 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 8pm and Our goodwill credit limit is 20 pounds bind slots, so stating either twice with different values is a blocking error: 2 for the times via logic/time and 6 for the money via logic/contradiction plus logic/measure. A box costs 34 pounds and The skip window closes 5 days before a delivery use ordinary verbs, bind nothing, and are 0 error, 0 warning, 0 info however many times you contradict them. The fix is one word: The box price is 34 pounds is the same fact and it binds box.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 info and 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, ARE and AM are the one copula, so the grammar of the sentence decides which you wrote. One caveat worth knowing: an unquoted multi-word definite binds nothing, so The skip window is 5 days needs to be The %skip window% is 5 days if you want the name.
Do background facts count towards test coverage?
No, and that is the right answer. Running typeglish test --dry over the before file reports coverage: 1/3 rules exercised and the rewritten file reports coverage: 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.
Field note

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.

∿ washed up Aug 30, 2026 ∿