← Tidelines/Deep dives

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.

by TypeGlish team11 min read#deep-dives
Coherent. Undecided.

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.

care.tg - ten rules, no contradiction✓ builds
# 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.
tg check care.tg - output
$ 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
Five distinct codes, five rule pairs, and the same clause in three of the messages: coherent as one default plus one exception. Nothing here is a contradiction, so nothing blocks: 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.

one family, five readings
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
Read the middle two columns as a sentence: this, except that. Every row is a policy a support lead would sign off on in a meeting, and none of them is written down as a policy, because except never got typed.

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.

tg check --strict and tg score - the same file✗ D (58/100)
$ 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
The namespace is the tell. Both 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:

the three gates on one file
$ 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
Seven advisory findings put 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.

care-shipped.tg - the same policy, with the precedence written down✓ A (91/100)
<$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
tg check, score and build - the shipped file
$ 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.

controlled pairs - the same policy, reworded
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
Three real gaps and one reassurance. The authority cap reads $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 $200 credit 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 is 0 error, 7 warning, 2 info at exit 0 and D (58/100) with consistency 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 $IF arm, 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, check exits 0 with 7 warnings and check --strict exits 1 with 3 error, 4 warning, 2 info: the two logic/ codes escalate, logic/only-gate and both reports of logic/axis-tension, while the three prompt/ codes stay advisory, prompt/cap-tension, prompt/authority-cap and prompt/conjunct-cap. Those three are the money, the reply length and the question count, so --strict is not the gate for this family. score --min B is, because consistency carries the whole drop and exits 1 at D (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/contradiction and logic/numeric are blocking errors and typeglish build refuses 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.
Field note

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.

∿ washed up Aug 28, 2026 ∿