Your prompt is a default and its exceptions
A mature agent prompt has almost no contradictions left in it. What it has instead is pairs of rules that are both true, both reasonable, and silent about which one owns the case they share.
TL;DR Five different TypeGlish findings end with the same sentence, coherent as one default plus one exception, which means both rules can hold and nothing in the file says which one wins: a ten-rule care prompt draws 0 error, 7 warning, 2 info at exit 0 and D (58/100) with consistency 18, check --strict escalates only the two logic/ codes and leaves the money, the reply length and the question count as warnings, and writing each exception down as a scope phrase, a route, or a $IF arm takes the same policy to A (91/100).
A prompt review usually looks for contradictions, because a contradiction is the failure everybody can name. In a file that has been through eighteen months of a contact centre, the contradictions have mostly been found: somebody hit one, somebody fixed it. What is left is quieter and much more common. Two rules that agree with each other in every reading a human gives them, that cannot both be applied to the case sitting in front of the agent right now, and that never said which one to apply. There is no wrong sentence to point at. The document is simply unfinished, and the model finishes it per conversation.
§1The third verdict
Here is a care prompt for a utility, ten rules, all of them written by somebody who knew the policy. Read it as a reviewer would and you will not find an error, because there is not one.
# Role You are a support agent for Northbay Utilities. # Constraints - MUST keep every reply to at most 3 sentences. - ALWAYS confirm the full account balance before you close a contact. - MAY approve a credit up to $200. - ALWAYS offer a full credit when a customer reports a long outage. - Escalate a complaint only when a customer asks for a manager. - MUST escalate every complaint about a meter reading. - MUST ask at most 1 question in each reply. - ALWAYS ask whether the meter is indoors and whether the supply is off. - Be formal. - Speak casually on a voice call.
$ typeglish check care.tg care.tg:6:1 warn prompt/cap-tension Cap tension - this mandate reads exhaustive ("the full account") while every reply is capped at 3 sentences (line 4). Coherent as one default plus one exception - scope the cap ("except when covering the account"). care.tg:8:1 warn prompt/authority-cap Authority cap - "credit" is capped at $200. (line 6), but this line grants the full, unbounded form with no route past the cap. Coherent as ceiling + routed exception: add the approval route, or scope the entitlement. care.tg:10:1 warn logic/only-gate Gated here, mandated there - the gate allows this action ONLY under "a customer asks for a manager.", but this line orders it unconditionally, dropping the gate. Add the gate's condition here, or loosen the gate. care.tg:12:1 warn prompt/conjunct-cap One ask, 2 questions - this single instruction asks 2 questions (one per "whether"/wh-clause), while line 10 caps questions at 1. Split the ask into separate turns, or scope the cap. care.tg:13:1 warn logic/axis-tension Axis tension - "Be formal" (general) and "Speak casually on a voice call" (scoped) pull opposite ways on the formality axis (line 13). Coherent as "one default, one exception" - consider scoping the general rule explicitly. care.tg:14:1 warn logic/axis-tension Axis tension - "Be formal" (general) and "Speak casually on a voice call" (scoped) pull opposite ways on the formality axis (line 12). ✓ 1 file - 0 error, 7 warning, 2 info # exit 0 # 2 info (prompt/unintroduced-definite x2) and 1 warn (prompt/unmeasurable) elided
typeglish build care.tg writes af1cdd422b8b and exits 0.That clause is the whole post. It is the checker declining to call the file wrong, while pointing out that a decision is missing. Read the ten rules again with it in hand and each pair resolves into the same shape: one rule is the default, the other is the exception, and the prompt states them as equals.
A contradiction is two rules that cannot both be true. A tension is two rules that cannot both be first.
One small thing to know before reading the output closely, since it will otherwise cost you five minutes: the column at the front of each line is the source line, and the (line N) inside the message body points one lower. The cap-tension report sits on line 6 and cites line 4 for a rule that is on line 5. Trust the leading column and read the quoted text for the other half of the pair. The same applies to the ledger rows score prints, and it is the same caveat your prompt is a graph, not a list raises about multi-line proofs.
§2The five shapes
The family is not a single rule with five spellings. Each code reads a different pair of layers, which is why a file can have four of them and pass every review: the two halves of each pair usually live in different sections, written by different people, and neither half looks like it is about the other.
code the default the exception what it reads
prompt/cap-tension at most 3 sentences confirm the FULL balance a bound vs an
exhaustive mandate
prompt/authority-cap credit up to $200 offer a FULL credit a ceiling vs an
unbounded grant
logic/only-gate escalate ONLY when MUST escalate every a necessary condition
asked for a manager meter-reading complaint vs an order without it
prompt/conjunct-cap at most 1 question ask whether A and a count vs the
in each reply whether B conjuncts in one ask
logic/axis-tension Be formal Speak casually on a general axis claim
a voice call vs a scoped one
Two of these deserve a note, because they are the ones that surprise people.
prompt/conjunct-cap counts the questions inside one instruction. A rule capping the agent at one question per reply and a rule ordering it to ask two things is not two rules about counting, it is one count and one coordination, and the finding is what happens when the second gets lowered into the first. The count rides the whether markers, one per marker, which is a narrower reading than it looks: see §5.
logic/only-gate is the one worth putting on a review checklist tomorrow. An only in a contact-centre prompt is almost always an escalation gate, and an escalation gate is exactly the kind of rule that later gets a sibling: a second reason to escalate, added by a different team, phrased as an unconditional MUST. The gate is not contradicted. It is simply dropped on that path. You said only, your agent escalated anyway takes the same word through its blocking cousin, logic/exclusivity, where the second rule adds a destination outside a closed relation and the build stops. Here the second rule adds an occasion, and the build does not.
§3What --strict will and will not do for you
The obvious move is to reach for --strict and make the whole family blocking. It half works, and the half that does not is the half you care about.
$ typeglish check care.tg --strict care.tg:6:1 warn prompt/cap-tension Cap tension - this mandate reads exhaustive ... care.tg:8:1 warn prompt/authority-cap Authority cap - "credit" is capped at $200 ... care.tg:10:1 error logic/only-gate Gated here, mandated there - the gate allows ... care.tg:12:1 warn prompt/conjunct-cap One ask, 2 questions - this single instruction ... care.tg:13:1 error logic/axis-tension Axis tension - "Be formal" (general) and ... care.tg:14:1 error logic/axis-tension Axis tension - "Be formal" (general) and ... ✗ 1 file - 3 error, 4 warning, 2 info # exit 1 $ typeglish score care.tg care.tg - D (58/100) proven errors: none tiers: base+z3 planes runtime 61 (what the model reads) · hygiene 50 (source only) facets enforceability 75 x.21 · hardness 90 x.12 · directness 90 x.08 consistency 18 x.17 · structure 100 x.12 · annotation 0 x.12 style 0 x.08 · security 100 x.08 lever consistency 18/100 (up to +14 overall) - Fix the ledger rows with logic/ or clarity/ codes; state each fact in one place. L6 prompt/cap-tension -1 L12 prompt/conjunct-cap -1 L8 prompt/authority-cap -1 L13 logic/axis-tension -1 L10 logic/only-gate -1 L14 logic/axis-tension -1
logic/ codes escalate to blocking errors under --strict; all three prompt/ codes stay warnings. So --strict stops a tone pair and an escalation gate, and lets through the money, the reply length and the question count.That split is defensible once you see what each side is claiming. logic/axis-tension and logic/only-gate are statements about the rules themselves: one is a pair of values on a named axis, the other is a necessary condition provably absent on a path. A correctness pass can hold those. prompt/cap-tension, prompt/authority-cap and prompt/conjunct-cap each need a reading of intent that the compiler is right to leave alone. Confirm the full balance might genuinely fit in three sentences at this company. Whether a full credit above $200 is a real entitlement or a loose sentence is a question for a person, not a prover.
Which means the gate for this family is not check at all. It is the score, and specifically one facet:
$ typeglish check care.tg → 0 error, 7 warning, 2 info exit 0 $ typeglish check care.tg --strict → 3 error, 4 warning, 2 info exit 1 $ typeglish build care.tg → built .typeglish/dist/care.txt (af1cdd422b8b) exit 0 $ typeglish score care.tg --min B → D (58/100) exit 1
consistency at 18 and the file two bands under B, so a score floor in CI catches all five pairs where --strict catches two. consistency is the only facet that asks what the rules do to each other, which is why it is the one that moves.§4Writing the exception down
Every one of these fixes is the same edit in a different grammar: name which rule owns the shared case. The interesting part is that there are three places to put it, and the choice is not cosmetic.
A scope phrase on the general rule. The cap says what it excludes. This keeps both rules in the prompt and hands the model the precedence in the sentence it is already reading.
A route for the overflow. A ceiling with nowhere to send what exceeds it is a rule the agent can only obey by refusing, so it improvises. Stating the destination is what turns a ceiling into a policy, and it is the same argument how to write what your agent says when the lookup fails makes about a failed lookup.
A compiler branch. When the exception is decided by something the host already knows before the model sees a token, the tone pair here being the obvious case, a $IF arm removes the losing text entirely rather than asking the model to weigh it.
<$CONFIG> $CONFIG modality chat $CONFIG adjective formal - axis:: tone.formality - opposite:: casual </$CONFIG> $REQUIRE variable channel: one of voice, chat # Role @@ role: the doer every rule below is addressed to You are a support agent for Northbay Utilities. # Constraints @@ brevity: the cap is a default, and the readback below is its one stated exception - MUST keep every reply to at most 3 sentences, unless you are reading back an account balance. @@ readback: the exception the cap names, written as a rule so it is scoped rather than implied - WHEN you close a contact THEN read back the account balance. @@ credit_cap: the ceiling the agent owns - MAY approve a credit up to $200. @@ credit_route: the ceiling needs somewhere to send the overflow, or the agent invents one - IF a customer reports a long outage and asks for more than $200 THEN transfer the contact to a team leader. @@ escalation_gate: the gate names every case that opens it, so no later rule has to drop it - Escalate a complaint only when a customer asks for a manager or a complaint is about a meter reading. @@ one_question: the cap on questions per reply - MUST ask at most 1 question in each reply. @@ ask_one: the first half of the diagnostic - ALWAYS ask whether a meter is indoors. @@ ask_two: the second half, on the next turn, so the cap and the diagnostic never meet - WHEN a customer confirms a meter is indoors THEN ask whether a supply is off. @@ tone: the exception is a compiler branch, so exactly one tone reaches the model $IF @{channel} is equal to voice: Your tone IS casual. $ELSE: Your tone IS formal. $TEST readback - input:: That is all sorted, thanks. - expect:: - the reply reads the account balance back $TEST cap_route - input:: My supply was off for nine days, I want the whole bill wiped. - expect:: - matches /^(?!.*wiped).*$/ - the reply offers to pass the request to a team leader
$ typeglish check care-shipped.tg ✓ 1 file - 0 error, 0 warning, 0 info $ typeglish score care-shipped.tg care-shipped.tg - A (91/100) proven errors: none tiers: base+z3 planes runtime 91 (what the model reads) · hygiene 92 (source only) facets enforceability 75 x.21 · hardness 88 x.12 · directness 96 x.08 consistency 100 x.17 · structure 100 x.12 · annotation 83 x.12 style 100 x.08 · security 100 x.08 lever enforceability 75/100 (up to +5 overall) $ typeglish build care-shipped.tg --vars '{"channel":"voice"}' ✓ built .typeglish/dist/care-shipped.txt ← care-shipped.tg (a5dfb4db3b9b, full) $ typeglish build care-shipped.tg --vars '{"channel":"chat"}' ✓ built .typeglish/dist/care-shipped.txt ← care-shipped.tg (75cb2e16016d, full) $ typeglish build care-shipped.tg # unbound template ✓ built .typeglish/dist/care-shipped.txt ← care-shipped.tg (8a252b13ca4b, full) $ typeglish test care-shipped.tg --dry ✓ care-shipped.tg coverage: 1/3 rules exercised ✓ 1 prompt - 0 failed
consistency 18 to 100, style 0 to 100, and D (58/100) to A (91/100) with no policy changed and no rule deleted. Two of the ten rules moved a clause; two acquired a second rule beside them; one pair became a chain. The three build hashes are the point of the last fix: the voice artifact and the chat artifact each carry exactly one tone line, and only the unbound template ships both for the model to sort out.Worth being precise about what was actually paid here, because two of these edits are not free. Splitting the two-part diagnostic across turns is a real behaviour change: the agent now needs a turn it did not need before, and someone should want that. Widening the escalation gate to name meter-reading complaints is a policy edit, small but real, and the alternative reading, that the gate was right and the MUST was overreach, would have been fixed by deleting the second rule instead. The compiler cannot tell you which. It can only tell you that as written, one of them was going to lose and nobody had said which.
§5Where the tier does not reach
This family reads surface forms, and the surfaces are narrower than the concepts. Four controlled pairs, each a one-phrase edit away from a pair that fires, and each completely silent.
the pair tg check credit up to $200 / offer a full credit 1 warn prompt/authority-cap credit up to 200 dollars / offer a full credit 1 warn prompt/authority-cap credit up to 200 pounds / offer a full credit 0 error, 0 warning, 0 info at most 3 sentences / confirm the full balance 1 warn prompt/cap-tension at most 3 sentences / confirm every charge 0 error, 0 warning, 0 info at most 3 sentences / confirm a balance in full 0 error, 0 warning, 0 info at most 1 question / ask whether A and whether B 1 warn prompt/conjunct-cap at most 1 question / ask for A and B and C 0 error, 0 warning, 0 info escalate only when asked / MUST escalate every X 1 warn logic/only-gate never escalate until asked / MUST escalate every X 1 warn logic/only-gate
$200 and 200 dollars and says nothing about 200 pounds, so a sterling contact centre gets no finding at all. cap-tension wants the exhaustive determiner, the full X; every charge and a balance in full are the same instruction and are silent. conjunct-cap counts whether and wh-markers, not plain coordination, so the three-part ask nobody should be making passes. The gate, at least, reads both spellings: only when and never ... until behave identically.None of that makes the tier less useful; it makes it a floor rather than a ceiling. The reliable version of this check is not a flag, it is a habit, and it is cheap: every time you add a rule containing a number, an only, or an adjective about tone, go find the rule it is the exception to and say so in one of the two sentences. That is the same discipline you are about to add one more rule asks for at the moment of the edit, and it is the moment when you still know which rule you meant to be first.
The reframe that makes it stick: stop reading an agent prompt as a set of rules and read it as a default with exceptions. A set has no order and needs none, which is why set is the wrong picture for a document a model reads once and then applies to a case. Every rule in a working prompt is either the default for its slice of the world or the exception to one, and the ones that are neither are the ones nobody has needed yet.
§6Common questions
- Why does my agent follow one of two rules that do not contradict each other?
- Because the two rules are coherent only under a precedence the prompt never states, so the model supplies one per conversation. A cap on reply length beside a mandate to confirm the full balance, or a
$200credit ceiling beside an unconditional grant of a full credit, can both be true at once: they are not a contradiction, they are a default and an exception with the default unmarked. TypeGlish reports that as its own class of finding, and every message in the class ends with some version of the same sentence, coherent as one default plus one exception. Nothing blocks the build, so the only signal is the report and the score: the ten-rule file in this post is0 error, 7 warning, 2 infoat exit 0 andD (58/100)withconsistency 18. - How do I set rule priority in a system prompt?
- Write the precedence into one of the two rules rather than into a paragraph about how to read the prompt. Three forms clear the finding and each says something different: a scope phrase on the general rule (
MUST keep every reply to at most 3 sentences, unless you are reading back an account balance), a stated route for the overflow (IF a customer asks for more than $200 THEN transfer the contact to a team leader), and a widened gate that names every case that opens it, so no later rule has to drop it. When the exception is decided by data the host already holds, prefer a$IFarm, because the compiler keeps one arm and the losing text never reaches the model at all. - Does typeglish check --strict catch rules with no stated priority?
- It catches some of them, and the namespace is the tell. On the same ten-rule file,
checkexits 0 with 7 warnings andcheck --strictexits 1 with3 error, 4 warning, 2 info: the twologic/codes escalate,logic/only-gateand both reports oflogic/axis-tension, while the threeprompt/codes stay advisory,prompt/cap-tension,prompt/authority-capandprompt/conjunct-cap. Those three are the money, the reply length and the question count, so--strictis not the gate for this family.score --min Bis, becauseconsistencycarries the whole drop and exits 1 atD (58/100). - Is a rule with an unstated exception the same as a contradiction?
- No, and keeping them apart is the point of the separate class. A contradiction is a pair no behaviour can satisfy, so
logic/contradictionandlogic/numericare blocking errors andtypeglish buildrefuses the file. A tension is a pair that a single behaviour can satisfy perfectly well once you say which rule owns the case, so it warns and builds. Treat the blocking codes as bugs and the tension codes as unfinished policy: the first list is wrong, the second list is undecided, and only one of them can be settled by editing wording rather than by asking someone.
This tier is the other half of the story your prompt argues with itself tells. That post is about the pairs a prover can refuse, where the model resolves a real conflict fresh each conversation. These five codes are the pairs a prover deliberately will not refuse, and the resolution is identical from the outside: recency, specificity and phrasing pick a winner per conversation, because the file never did. The difference is only in what you can do about it. A contradiction has a wrong sentence in it. A tension has a missing one.