← Tidelines/Deep dives

Two rules, no words in common

Compliance wrote one line. QA wrote the other, in a different section, four months later. They contradict each other flatly, and there is no search you can run over the file that will ever show you both at once.

by TypeGlish team7 min read#deep-dives
Same slot. Opposite poles.

TL;DR TypeGlish keys a rule to the action it names rather than the words you typed, and a handful of English verbs fold onto one action at opposite poles: MUST withhold the internal reason code and MUST display the internal reason code are one slot claimed twice, a blocking logic/contradiction, F (71/100) with consistency 0. Nothing about those two lines is greppable, because their only shared word is the object.

Every prompt engineer has a story about the rule that turned out to already exist. You add a line, the agent starts doing something else, and a week later you find the line you were fighting: sensible, well-scoped, written by somebody who did nothing wrong, sitting in a section you had no reason to open. The reason these are so hard to find by hand is not that prompts are long. It is that a contradiction in English does not have to look like one. Two rules can be exact opposites and share no vocabulary at all.

§1Two lines, two sections, one slot

Here is the shape, minus the four hundred lines it would normally be buried in. A refunds section says the agent should tell the customer why a refund failed. A compliance section, added later by somebody who had just read an audit finding, says the internal code never leaves the building. Both lines are correct. Both lines are the policy their author was asked to write.

support.tg✗ won't build · F (71/100)
# Role
You are a support agent for Acme, a SaaS analytics company.

# Refunds
- MUST keep every response to at most 3 sentences.
- MUST display the internal reason code.

# Compliance
- MUST withhold the internal reason code.
Two sections apart, and the only string they share is the internal reason code. Search the file for display and you find one line. Search for withhold and you find the other. There is no query that returns both, which is exactly why this survives review.
tg check - output
support.tg:6:1  error  logic/contradiction  Logical conflict — "display the internal reason
  code." vs "withhold the internal reason code." is both required and forbidden. Keep one,
  or scope the two rules so they cannot both apply (IF <condition> THEN ...). (conflicts with line 7)
support.tg:9:1  error  logic/contradiction  Logical conflict — "display the internal reason
  code." vs "withhold the internal reason code." ... (conflicts with line 5)

 1 file — 2 error, 0 warning, 0 info
The phrase to sit with is both required and forbidden. Not similar, not possibly overlapping. The checker resolved both lines to one action on one object and found an obligation and a prohibition on it.
tg score - before
support.tg — F (71/100)  proven errors — grade capped at F  tiers: base+z3
  planes  runtime 78 (what the model reads) · hygiene 50 (source only)
  facets  enforceability 100 x.21 · hardness 100 x.12 · directness 100 x.08 · consistency 0 x.17
          · structure 100 x.12 (hygiene) · annotation 0 x.12 (hygiene) · style 100 x.08 · security 100 x.08
  lever   consistency 0/100 (up to +17 overall) — Fix the ledger rows with logic/ or clarity/
          codes; state each fact in one place.
enforceability 100 and hardness 100. Every rule in this file is measurable and every rule is binding. The file is still an F, because being individually excellent is not the same as being jointly possible.

§2The checker reads the action, not the word

The mechanism is a verb ontology. TypeGlish parses the head of a directive against a curated catalog of actions grouped into families, and the emission family, the one about what the agent puts out and what it holds back, is a single polarity system. Its negative-pole verbs fold onto their positive head. That fold is why the two lines above land in one slot: withhold resolves to the negation of the same action display asserts, so the pair is MUST X against MUST NOT X, which is the oldest contradiction there is.

You can see the fold in the diagnostic wording if you look closely. Run the checker on a pair of rules that are both prohibitions and it reports a conflict in vocabulary that appears nowhere in your file.

handoff.tg✗ won't build
# Role
You are a support agent for Acme, a SaaS analytics company.

# Handoff notes
- NEVER omit the ticket reference.
- NEVER include the ticket reference.
The first line is ops: a handoff note without the ticket reference is useless to the human receiving it. The second is privacy: at Acme the reference embeds the customer's email. Two NEVERs. No reviewer reads two prohibitions as a conflict.
tg check - output
handoff.tg:5:1  error  logic/contradiction  Logical conflict — "omit the ticket reference."
  vs "include the ticket reference." is both required and forbidden. Keep one, or scope the
  two rules so they cannot both apply (IF <condition> THEN ...). (conflicts with line 5)
handoff.tg:6:1  error  logic/contradiction  Logical conflict — "omit the ticket reference."
  vs "include the ticket reference." ... (conflicts with line 4)

 1 file — 2 error, 0 warning, 0 info
Read the quoted phrases against the file. include the ticket reference appears once in handoff.tg, in a line that forbids it, and the checker is reporting it as required. It is not quoting your text. It is quoting the folded key, where NEVER omit has already become an obligation to include.

Which is the whole trick, stated plainly: NEVER omit X is a double negative, and a double negative is an obligation. The compiler does that arithmetic before it looks for conflicts, so it sees MUST include X beside MUST NOT include X while you are still seeing two lines that both begin with the word never.

Your prompt does not contradict itself in words. It contradicts itself in meanings, and words are the only thing you can search.

§3Where the fold stops

Now the honest half, which matters more than the impressive half, because it is the part that decides whether you can trust a green check.

The fold is not blanket. It is keyed, verb by verb, and only where the polarity is provably exact. We enumerated it: every negative-pole verb in the emission family against every positive-pole verb, all 153 pairs, each as a two-rule prompt on one object, run through the real checker. Seventeen pairs conflict. The other hundred and thirty-six compile clean. The seventeen resolve into three merged keys.

fig. 1 - the three merged keys in the emission family
withhold · suppress · hidenegative pole
folds onto
output · emit · print · display · showone key
omitnegative pole
folds onto
includeone key
blocknegative pole
folds onto
sendone key
skip · exclude · redact · censorno positive partner
no fold
nothing provableboth rules compile
Measured against typeglish 0.6.0. Any pair drawn from one row is a provable conflict. Any pair drawn across two rows is not, no matter how obviously opposite it reads to you.

So this file, which says redact the code and display the code, is fine as far as the compiler is concerned.

redact.tg✓ 0 error, 0 warning, 0 info
# Role
You are a support agent for Acme, a SaaS analytics company.

# Refunds
- MUST redact the internal reason code.
- MUST display the internal reason code.
tg score - redact.tg
redact.tg — B (87/100)  proven errors: none  tiers: base+z3
  planes  runtime 100 (what the model reads) · hygiene 50 (source only)
  facets  enforceability 100 x.21 · hardness 100 x.12 · directness 100 x.08 · consistency 100 x.17
          · structure 100 x.12 (hygiene) · annotation 0 x.12 (hygiene) · style 100 x.08 · security 100 x.08
consistency 100, runtime 100, and the only thing costing it a grade is missing @@ notes. A prompt that tells the agent to redact a value and display the same value gets a clean bill of health on the facet named consistency.

This is not a gap to be annoyed about. It is a decision, and it is the right one. redact is not the negation of display: you can redact part of a string and display the rest, which is what redaction usually means. Merging those keys would let the checker manufacture conflicts out of rules that are jointly satisfiable, and a prover that cries wolf gets switched off. Every fold in that diagram is a place where the language committed to these two verbs cannot both hold, and the four unfolded verbs are places where it declined to guess.

What that buys you is a practical authoring rule, and it is the actionable takeaway of this whole post: when you mean an absolute either-or, write both halves on the same key. If the code must never reach a customer, say withhold or say NEVER display, not redact. You are not decorating; you are choosing whether a future contradiction is a red build or a support ticket. The same logic runs underneath your prompt argues with itself: a contradiction the compiler can prove is a decision you make once, and a contradiction it cannot prove is a decision the model makes fresh every conversation.

§4Guarding one side is not guarding

The diagnostic offers two exits: keep one rule, or scope them so they cannot both apply. The second is the one people reach for, because usually both policies are real. It is also the one people get wrong, and the failure is worth seeing because it looks so much like a fix.

The natural move is to guard the rule you are less sure about. Internal staff can see the code; customers cannot. So put an IF on the display line and leave compliance alone.

scoped.tg - the fix that isn't✗ still won't build
# Role
You are a support agent for Acme, a SaaS analytics company.

# Refunds
- IF the customer is an internal Acme employee THEN display the internal reason code.
- MUST withhold the internal reason code.
Two errors, unchanged, and the score actually drops to F (66/100) because the guarded rule reads as less enforceable. Scoping half a conflict does not resolve it.

The reason is simple once stated. A bare MUST has no condition on it, so it applies to every case, including the case you just carved out. For an Acme employee both rules now fire, and the conflict is exactly where it was. The diagnostic says scope the two rules and it means the plural: a conflict is resolved when the guards make the two rules disjoint, not when one of them acquires a guard.

scoped.tg - both sides, complementary guards✓ 0 error, 0 warning, 0 info
# Role
You are a support agent for Acme, a SaaS analytics company.

# Refunds
- IF the requester is an Acme employee THEN display the internal reason code.
- UNLESS the requester is an Acme employee THEN withhold the internal reason code.
One predicate, both polarities, and the two rules can no longer both apply to one requester. UNLESS x is IF NOT x, so the pair is exhaustive as well as disjoint, which is what you want from a rule about who may see a value.

§5The rewrite, and the third option

Complementary guards are the right answer when both policies genuinely apply to the same thing. Often they don't, and the collision is a sign that two people used one noun for two different things. That is what happened here. Compliance meant the billing system's internal label. QA meant the customer should be told why. Those were never the same object, and the argument only existed because nobody wrote down the difference.

support.tg (after)✓ compiles · A (100/100)
# Role
@@ role: the addressee, so every rule below has a doer
You are a support agent for Acme, a SaaS analytics company.

# Refunds
@@ reason_code: one name for the thing both sections were arguing about
The %internal reason code% IS an internal billing label.
@@ withhold: compliance owns this line, and it is unconditional on purpose
- MUST withhold the %internal reason code%.
@@ decline_reason: QA owns this line; the customer is owed a reason, just not that one
- MUST display the plain-language decline reason.

$TEST declined_refund
  - input:: Why was my refund declined?
  - expect::
    - contains "reason"
The %...% name literal binds three words into one name, so internal reason code is a single token the checker tracks rather than an adjective pile it has to guess at. The declaration above it does double duty: it binds the name and it tells the model what the thing is, because in TypeGlish a declaration renders.
tg score & tg test - after
$ npx typeglish score support.tg
support.tg — A (100/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 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

$ npx typeglish test support.tg --dry
✓ support.tg  coverage: 2/2 rules exercised
  · declined_refund — "Why was my refund declined?" (not run)
      ✓ contains "reason"
✓ 1 prompt — 0 failed
F (71/100) to A (100/100). Nothing was deleted and no policy was overruled: both teams kept their rule, and the edit was to say out loud that they were talking about two different things. Consistency went 0 to 100 at weight x.17, and the @@ notes took hygiene 50 to 100.

Three exits, then, in the order you should try them. Are these two rules actually about two different objects? Name them differently. Are they about one object in two situations? Guard both sides on one predicate. Is one of them simply wrong? Delete it. What you must not do is add a third line clarifying which of the first two wins, which is the instinct, and which leaves the compiler exactly as unable to help you as it was before.

Field note

The verb ontology is a data structure you can read: npx typeglish reference prints all nine action families with every verb in each, and it says which ones are frame-only, meaning the checker parses them but never merges their keys. Worth ten minutes before your next prompt review, because the folds are the only places a contradiction becomes a build failure instead of a judgement call. The fold map in §3 came out of enumerating those pairs against 0.6.0 rather than reading the source, and every figure on this page is re-run against the real compiler by CI, so the codes, counts and grades are the checker's rather than ours. If a future release merges another key, this page fails the build.

FAQCommon questions

Why do my agent's rules conflict when I can't find the conflict?
Because you are searching for words and the conflict lives in the meaning. A checker keys a rule to the action it names, and a handful of English verbs fold onto one action: withhold, suppress and hide are the negative pole of the same key as output, emit, print, display and show. So MUST withhold the internal reason code and MUST display the internal reason code are one slot claimed twice at opposite poles, and no text search will ever put those two lines on the same screen because they have no word in common except the object.
Does NEVER omit mean the same thing as MUST include?
In TypeGlish, yes, and the checker will prove it. omit is the negative pole of include, so NEVER omit the ticket reference resolves to an obligation to include it. That is why NEVER omit the ticket reference and NEVER include the ticket reference are a blocking logic/contradiction even though both lines are prohibitions and neither contains the word MUST. Two prohibitions can absolutely contradict each other once one of them is a double negative.
Is it enough to put an IF guard on one of the two conflicting rules?
No, and this is the most common failed fix. If you guard the display rule with IF the requester is an Acme employee but leave the withhold rule unconditional, the unconditional rule still covers the guarded case, so both rules apply to an Acme employee and logic/contradiction still fires. The diagnostic says scope the two rules, plural, on purpose. Either guard both sides with complementary conditions, IF and UNLESS on the same predicate, or change one rule to be about a different object.
Will the checker catch every contradiction in my system prompt?
No, and the boundary is worth knowing exactly. The fold is per key, and in the emission family only three keys merge a positive and a negative pole. MUST redact the internal reason code beside MUST display the internal reason code compiles clean and scores B (87/100) with consistency 100, because redact is not the exact negation of display: you can redact part of something and still display it. A clean check means nothing was proven, not that nothing is wrong. Write both halves of a genuine either-or on the same key and the prover can reach it.
∿ washed up Jul 31, 2026 ∿