Bake-off: name the condition once vs. write it three times
Every tiered support prompt has one condition in it three or four times. Naming it looks like housekeeping. It is the only part of the file that decides what happens the week the tiers change.
TL;DR A condition spelled out in three chains and the same condition behind one $DEFINE both score A (99/100) and build to byte-identical artifacts once the variable is bound, so the choice is entirely about the edit that has not happened yet: promoting a tier is one line in the named file and three in the other, and the copy you miss is still 0 error. Fold the $DEFINE into the <$CONFIG> block or it costs you two points you cannot annotate back.
Halyard Software sells three support tiers and a free one. Gold and platinum get a fifteen-minute call back, a named account manager, and weekend cover. Bronze and silver get the queue. That is one business rule, is this a priority account, and it lands in a prompt three times. Here are both ways to write it, and the checker as referee.
§1One condition, three copies
Route A writes the condition where it applies, every time. This is what almost every prompt in production looks like, because it is what you get by writing the rules one at a time.
<$CONFIG> $REQUIRE variable tier: one of bronze, silver, gold, platinum </$CONFIG> # Role @@ role: one named agent so every later "you" is this agent You are Rowan, a support agent for Halyard Software. # Constraints @@ callback: the priority tiers buy a fast call back and nothing else buys it $IF @{tier} is one of gold, platinum: - MUST offer a call back within 15 minutes. $ELSE: - MUST offer a call back within 4 hours. @@ manager: a named human is the thing the priority contract actually sells $IF @{tier} is one of gold, platinum: - MUST name their account manager. $ELSE: - MUST name their support queue. @@ weekend: Saturday cover is the third line item on the priority contract $IF @{tier} is one of gold, platinum: - MUST offer weekend cover. $ELSE: - NEVER offer weekend cover.
@@ notes, one condition typed out three times. Nothing here is wrong and nothing here is unusual.Route B gives the condition a name. $DEFINE binds a named state that chains and guards read, so the three chains stop mentioning tiers at all.
<$CONFIG> $REQUIRE variable tier: one of bronze, silver, gold, platinum $DEFINE priority AS @{tier} is one of gold, platinum </$CONFIG> # Role @@ role: one named agent so every later "you" is this agent You are Rowan, a support agent for Halyard Software. # Constraints @@ callback: the priority tiers buy a fast call back and nothing else buys it $IF @{priority}: - MUST offer a call back within 15 minutes. $ELSE: - MUST offer a call back within 4 hours. @@ manager: a named human is the thing the priority contract actually sells $IF @{priority}: - MUST name their account manager. $ELSE: - MUST name their support queue. @@ weekend: Saturday cover is the third line item on the priority contract $IF @{priority}: - MUST offer weekend cover. $ELSE: - NEVER offer weekend cover.
gold now appears exactly once in the file, which is the whole pitch.The referee has nothing to say about either of them.
$ npx typeglish check repeat.tg named.tg
✓ 2 files — 0 error, 0 warning, 0 info
$ npx typeglish score repeat.tg
repeat.tg — A (99/100) proven errors: none tiers: base+z3
planes runtime 99 (what the model reads) · hygiene 100 (source only)
facets enforceability 100 x.21 · hardness 100 x.12 · directness 92 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 100 x.12 (hygiene) · style 100 x.08 · security 100 x.08
rules 6 of 6 rule-shaped lines read
$ npx typeglish score named.tg
named.tg — A (99/100) proven errors: none tiers: base+z3
planes runtime 99 (what the model reads) · hygiene 100 (source only)
facets enforceability 100 x.21 · hardness 100 x.12 · directness 92 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 100 x.12 (hygiene) · style 100 x.08 · security 100 x.08
rules 6 of 6 rule-shaped lines read
§2Where the $DEFINE sits decides your grade
There is one trap on the way in, and it is worth two points. Put the $DEFINE at the top level, above the Role block, the way you would put a constant at the top of a source file, and the file is clean but the score is not.
$ npx typeglish score loose.tg loose.tg — A (97/100) proven errors: none tiers: base+z3 planes runtime 99 (what the model reads) · hygiene 90 (source only) facets enforceability 100 x.21 · hardness 100 x.12 · directness 93 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 80 x.12 (hygiene) · style 100 x.08 · security 100 x.08 lever annotation 80/100 (up to +2 overall) — 1 statement without a @@ note (L5) — put a "@@ why" line directly above each rules 6 of 6 rule-shaped lines read $ npx typeglish check loose.tg loose.tg:5:1 error structure/unattached-annotation A `@@` annotation documents the statement DIRECTLY below it — here the next line is a compiler command. Move the note to the line immediately above its statement. ✗ 1 file — 1 error, 0 warning, 0 info
@@ note on line 5. Line 5 is the $DEFINE, and a note directly above a compiler command is a blocking error. The two points are unreachable from where the declaration is standing.The fix is placement, not wording. A $DEFINE inside the <$CONFIG> fold is scaffolding rather than a statement, so nothing asks it for a reason: that is the version in §1, and it is the two points back. It also puts the definition next to the $REQUIRE it depends on, which is where a reader looks for it anyway.
§3The artifacts are identical, until nobody binds the variable
What actually reaches the model settles most arguments about prompt style, so build both files once per tier and compare the bytes.
$ for t in bronze silver gold platinum; do
> npx typeglish build repeat.tg named.tg --vars "{\"tier\":\"$t\"}" --out-dir dist/$t
> done
✓ built dist/bronze/repeat.txt ← repeat.tg (998be51c41ae, full)
✓ built dist/bronze/named.txt ← named.tg (998be51c41ae, full)
✓ built dist/silver/repeat.txt ← repeat.tg (998be51c41ae, full)
✓ built dist/silver/named.txt ← named.tg (998be51c41ae, full)
✓ built dist/gold/repeat.txt ← repeat.tg (fad953ddb3d5, full)
✓ built dist/gold/named.txt ← named.tg (fad953ddb3d5, full)
✓ built dist/platinum/repeat.txt ← repeat.tg (fad953ddb3d5, full)
✓ built dist/platinum/named.txt ← named.tg (fad953ddb3d5, full)
$ cat dist/platinum/named.txt
# Role
You are Rowan, a support agent for Halyard Software.
# Constraints
- MUST offer a call back within 15 minutes.
- MUST name their account manager.
- MUST offer weekend cover.
That equality has one hole in it, and it is the only real argument against naming the condition. Build without a value bound and you get the template build, where the conditional survives as prose for the host to resolve later.
$ npx typeglish build repeat.tg named.tg --out-dir dist/tpl ✓ built dist/tpl/repeat.txt ← repeat.tg (77ce5eb45e2f, full) ✓ built dist/tpl/named.txt ← named.tg (a1e40a9eac14, full) $ head -6 dist/tpl/repeat.txt # Role You are Rowan, a support agent for Halyard Software. # Constraints If tier is gold or platinum: - MUST offer a call back within 15 minutes. $ head -6 dist/tpl/named.txt # Role You are Rowan, a support agent for Halyard Software. # Constraints If priority: - MUST offer a call back within 15 minutes.
This is the same seam as your system prompt has a compile time: a $DEFINE is resolved by the compiler, so it is worth exactly as much as your build is bound. Bind the inputs at build time and the name costs nothing. Ship a template and the name is a hole.
§4Tuesday: silver joins the priority tiers
Sales has been discounting platinum to hold renewals, so the answer is to move the call back and the account manager down a tier. Silver becomes a priority account. In named.tg that is one line. In repeat.tg it is the same line three times, and three is exactly the number where somebody updates two.
<$CONFIG> $REQUIRE variable tier: one of bronze, silver, gold, platinum </$CONFIG> # Role @@ role: one named agent so every later "you" is this agent You are Rowan, a support agent for Halyard Software. # Constraints @@ callback: the priority tiers buy a fast call back and nothing else buys it $IF @{tier} is one of silver, gold, platinum: - MUST offer a call back within 15 minutes. $ELSE: - MUST offer a call back within 4 hours. @@ manager: a named human is the thing the priority contract actually sells $IF @{tier} is one of gold, platinum: - MUST name their account manager. $ELSE: - MUST name their support queue. @@ weekend: Saturday cover is the third line item on the priority contract $IF @{tier} is one of gold, platinum: - MUST offer weekend cover. $ELSE: - NEVER offer weekend cover.
The check is silent, the grade does not move, and the artifact table is where the mistake is standing in plain sight.
$ npx typeglish check repeat-drift.tg named-new.tg ✓ 2 files — 0 error, 0 warning, 0 info $ npx typeglish score repeat-drift.tg repeat-drift.tg — A (99/100) proven errors: none tiers: base+z3 $ for t in bronze silver gold platinum; do > npx typeglish build repeat-drift.tg named-new.tg --vars "{\"tier\":\"$t\"}" --out-dir d2/$t > done ✓ built d2/bronze/repeat-drift.txt ← repeat-drift.tg (998be51c41ae, full) ✓ built d2/bronze/named-new.txt ← named-new.tg (998be51c41ae, full) ✓ built d2/silver/repeat-drift.txt ← repeat-drift.tg (2cf1bb1fae8f, full) ✓ built d2/silver/named-new.txt ← named-new.tg (fad953ddb3d5, full) ✓ built d2/gold/repeat-drift.txt ← repeat-drift.tg (fad953ddb3d5, full) ✓ built d2/gold/named-new.txt ← named-new.tg (fad953ddb3d5, full) ✓ built d2/platinum/repeat-drift.txt ← repeat-drift.tg (fad953ddb3d5, full) ✓ built d2/platinum/named-new.txt ← named-new.tg (fad953ddb3d5, full) $ cat d2/silver/repeat-drift.txt # Role You are Rowan, a support agent for Halyard Software. # Constraints - MUST offer a call back within 15 minutes. - MUST name their support queue. - NEVER offer weekend cover.
2cf1bb1fae8f is a prompt nobody designed and nobody approved. The silver customer is called back in fifteen minutes, handed the queue instead of their account manager, and told on Saturday that weekend cover is not part of their plan. The named file produced two hashes for four tiers, the way it did before the edit.Count the policies. Then count the hashes. A tiered prompt should not have more of the second than the first.
Nothing in the checker can find this one for you, and it is worth being precise about why. Both spellings are legal, both are internally consistent, and a prompt that treats silver as priority for call backs and standard for account managers is not a contradiction, it is a policy. Wrong is not a property of the file. It is a property of the gap between the file and the contract, and the only artifact that can hold the contract is your build matrix. That is the same reason a typed input beats free text for tier-gated rules: what the compiler proves is always the shape of the thing, never the business decision inside it.
§5What naming does not cost you
The usual objection to a named condition is that the compiler loses sight of what it is made of. It does not. Take the $ELSE arms out of both files, so neither chain covers the whole domain, and both are refused in the same way.
$ npx typeglish check repeat-noelse.tg named-noelse.tg repeat-noelse.tg:11:1 error structure/non-exhaustive-switch @{tier} can be bronze, silver, but no arm handles them — add an arm or an ELSE. repeat-noelse.tg:14:1 error structure/non-exhaustive-switch @{tier} can be bronze, silver, but no arm handles them — add an arm or an ELSE. repeat-noelse.tg:17:1 error structure/non-exhaustive-switch @{tier} can be bronze, silver, but no arm handles them — add an arm or an ELSE. named-noelse.tg:12:1 error structure/non-exhaustive-switch This chain isn't exhaustive — no arm handles @{tier}=bronze. Add an arm or an ELSE. named-noelse.tg:15:1 error structure/non-exhaustive-switch This chain isn't exhaustive — no arm handles @{tier}=bronze. Add an arm or an ELSE. named-noelse.tg:18:1 error structure/non-exhaustive-switch This chain isn't exhaustive — no arm handles @{tier}=bronze. Add an arm or an ELSE. ✗ 2 files — 6 error, 0 warning, 0 info
@{tier}=bronze. The compiler resolves the name back through the $DEFINE to the domain the value came from, which is the difference between a name and a comment.So the ledger is short. Naming the condition is free on grade, free on artifact, free on proof, and it buys you a policy change that cannot be applied to two thirds of a file. It costs you one thing: a template build that says If priority to a model that has never heard the word. Whether that matters is a deployment question, and it has an answer: bind your inputs at build time. If you are choosing between spellings for a chain over a closed domain in the first place, the catch-all arm against one arm per member is the other half of this decision.
- Name it at the second use. One occurrence is a condition. Two is a policy concept with no name yet, and it will reach three without anybody deciding to let it.
- Put the
$DEFINEin the<$CONFIG>fold. Beside the$REQUIREit reads, where it is scaffolding rather than an unannotatable statement. - Name the concept, not the members.
prioritysurvives the tiers changing.gold_or_platinumis the same three copies with extra steps. - Build once per member and count the hashes. Two policies, two artifacts. A third hash is a rule that came loose from the policy it belongs to, and it is the only way to see it.
§6Common questions
- Should I use $DEFINE for a condition my prompt uses more than once?
- Yes, from the second use onward. Three chains spelling out
@{tier} is one of gold, platinumand the same three chains reading$IF @{priority}both scoreA (99/100)with0 error, 0 warning, 0 info, and once the variable is bound they build to the same artifact, byte for byte:998be51c41aefor a bronze conversation andfad953ddb3d5for a platinum one, from either file. The difference is the edit. Promoting silver into the priority set is one line in the named file and three identical lines in the other, and the copy you miss is still clean. - Does naming a condition with $DEFINE stop TypeGlish from checking it?
- No. The compiler resolves the name back to the domain it came from. Strip the
$ELSEarms out of both files and each one is 3 blockingstructure/non-exhaustive-switcherrors: the spelled-out version says @{tier} can be bronze, silver, but no arm handles them, and the named version says This chain is not exhaustive - no arm handles @{tier}=bronze. Same proof, and the named file names the missing member even though the chain never mentions tier. - Why did my prompt score drop when I added a $DEFINE?
- Because a
$DEFINEat the top level counts as a statement for the annotation facet, and you cannot annotate it: a@@note directly above a compiler command is a blockingstructure/unattached-annotation. Left bare it isA (97/100)withannotation 80and the lever line naming L5. Move the$DEFINEinside the<$CONFIG>fold, next to the$REQUIREit depends on, and the same file isA (99/100)withannotation 100. - How do I catch a rule that quietly applies to the wrong customer tier?
- Build once per member of the domain and count the distinct hashes. A prompt with two tiers of policy should produce two artifacts. Build the drifted file for bronze, silver, gold and platinum and it produces three:
998be51c41ae,2cf1bb1fae8fandfad953ddb3d5, and the odd one out is the tier somebody half-promoted. The file itself checks clean atA (99/100), so the hash table is the only place that mismatch is visible.
The thing that makes this bake-off worth running is how badly the first round misleads you. Two files, same grade, same facets, same artifacts, and a review that compares them side by side will call it a wash and wave through whichever one arrived first. Every difference that matters is in an edit nobody has made yet, which is also true of most prompt-style arguments and is why so few of them get settled. The habit worth taking is the build matrix: a loop over the members of every domain your prompt branches on, the hashes collected, the count compared against the number of policies your business actually has. It is four lines of shell, it runs offline, and it is the only check in this post that would have caught the Tuesday edit. Say it once, or say it's optional makes the same argument about a rule stated twice; a condition stated three times is the version that waits three months to hurt.