← Tidelines/Deep dives

Your prompt is a graph, not a list

Every contradiction we have written about, including our own first post, teaches you to hunt for two rules that fight. The defect in this file needs three, and deleting any one of them makes it compile.

by TypeGlish team8 min read#deep-dives
Every pair agrees. The set does not.

TL;DR A prompt defect does not have to sit on a line or inside a pair of lines: three routing rules in three different sections of a contact-triage prompt, no two of which conflict, are 3 blocking logic/syllogism errors at F (72/100), and deleting any single one of them returns the file to 0 error, 0 warning, 0 info.

Here is the model most people carry: a system prompt is a list of rules, and a bad prompt is a list with a bad rule in it. Review is therefore a scan. You read down the file, you stop on anything that looks wrong, and when two lines look like they might disagree you hold them side by side and decide. That model is good enough for most of what goes wrong, which is why it survives. This post is about the class of defect it cannot see, and the class is not exotic. It is the ordinary result of three teams editing one file.

§1Three sentences, three sections, three teams

A contact-triage prompt for a broadband provider. Its job is small: read the contact, name the destination. Over eighteen months it accumulated a definitions section, because compliance wanted the word chargeback used precisely, and a routing section, because ops wanted disputes to reach a human with ledger access, and a constraints section, because billing kept getting chargebacks it could not act on.

Each of those three groups wrote one sentence. Each sentence is true of the business. Every one would survive a review, because every one is annotated, second person where it needs to be, and unambiguous on its own.

triage.tg - three sentences nobody would send back✗ blocked
# Role
@@ role: name the domain so "helpful" has a scope
You are a contact triage agent for Northwind Broadband.

# Definitions
@@ chargeback: compliance asked for this wording in 2024
- All chargebacks are disputes.

# Routing
@@ dispute_route: a dispute needs a human with ledger access
- All disputes are escalations.

# Constraints
@@ bank_owns_it: a chargeback is settled by the card network, not by us
- No chargeback is an escalation.
Read it top to bottom the way a reviewer does. There is nothing to stop on. The three claims are separated by two section headings and eight lines, and no two of them mention the same pair of things.

Put the file through the checker and it is three blocking errors.

tg check triage.tg - output
triage.tg:7:1   error  logic/syllogism  Contradictory chain - the rules on these lines force an
  overlap between "chargeback" and "escalation", which they also declare mutually exclusive.
  Relax one rule. (with lines 6, 8)

triage.tg:11:1  error  logic/syllogism  Contradictory chain - the rules on these lines force an
  overlap between "chargeback" and "escalation", which they also declare mutually exclusive.
  Relax one rule. (with lines 4, 8)

triage.tg:15:1  error  logic/syllogism  Contradictory chain - the rules on these lines force an
  overlap between "chargeback" and "escalation", which they also declare mutually exclusive.
  Relax one rule. (with lines 4, 6)

 1 file - 3 error, 0 warning, 0 info
Note what the message names: an overlap between chargeback and escalation. Neither word appears in the same sentence as the other anywhere in the file. The overlap is derived, not written.

The arithmetic is one line of it. Every chargeback is a dispute, every dispute is an escalation, so every chargeback is an escalation, and the third rule says none is. That inference is two steps long, which is exactly one step longer than a human review reliably carries.

tg score triage.tg - where the failure lands
triage.tg - F (72/100)  proven errors - grade capped at F  tiers: base+z3
  planes  runtime 63 (what the model reads) · hygiene 100 (source only)
  facets  enforceability 50 x.21 · hardness 100 x.12 · directness 90 x.08 · consistency 0 x.17
          structure 100 x.12 (hygiene) · annotation 100 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.
hardness 100, annotation 100, structure 100. Every facet that measures how well the rules are written is perfect, because they are well written. consistency 0 is carrying the whole grade, and consistency is the only facet that is a property of the set rather than of any member.

§2Every pair passes

This is the part worth proving rather than asserting, because the claim is easy to doubt. Take the three rules and check them two at a time. Three files, three pairs, the annotations kept, nothing else changed.

pairs_a.tg - the definition and the routing rule, alone✓ compiles
# Role
@@ role: name the domain so "helpful" has a scope
You are a contact triage agent for Northwind Broadband.

# Constraints
@@ chargeback: compliance asked for this wording in 2024
- All chargebacks are disputes.
@@ dispute_route: a dispute needs a human with ledger access
- All disputes are escalations.
pairs_b.tg holds the definition beside the exclusion, and pairs_c.tg holds the routing rule beside the exclusion. Same shape, same annotations.
tg check - all three pairs
$ typeglish check pairs_a.tg pairs_b.tg pairs_c.tg
 3 files - 0 error, 0 warning, 0 info
Nothing. Not a warning, not an info finding. There is no pair in this prompt that a pairwise review could have caught, because there is no faulty pair.

So the usual advice fails here, and it fails specifically. Your prompt argues with itself is about rules that provably conflict, and its examples are all two lines. Two rules, no words in common goes one level harder: the conflicting pair need not share any vocabulary, because a rule is keyed to the action it names rather than the words you typed. Both posts are about pairs. Both would have you looking for the wrong shape.

A reviewer reads pairs. A prover reads the closure. Those are different objects.

§3The defect is the set

Lengthen the chain and the property gets sharper. Ops later split their routing rule in two, because a dispute gets a second pair of eyes before it leaves the bot, and the second pair of eyes is a human. Four rules now, spanning three sections, and the distance from chargeback to escalation is three hops.

chain4.tg - one more hop, same file✗ blocked
# Role
@@ role: name the domain so "helpful" has a scope
You are a contact triage agent for Northwind Broadband.

# Definitions
@@ chargeback: compliance asked for this wording in 2024
- All chargebacks are disputes.

# Routing
@@ dispute_review: every dispute gets a second pair of eyes
- All disputes are reviews.
@@ review_route: a review is done by a human, so it leaves the bot
- All reviews are escalations.

# Constraints
@@ bank_owns_it: a chargeback is settled by the card network, not by us
- No chargeback is an escalation.
Four rules is six pairs and four triples. None of the six pairs is faulty. None of the four triples is faulty either, which is the next figure.
tg check chain4.tg - four rules, four reports
chain4.tg:7:1   error  logic/syllogism  ... (with lines 6, 7, 9)
chain4.tg:11:1  error  logic/syllogism  ... (with lines 4, 7, 9)
chain4.tg:13:1  error  logic/syllogism  ... (with lines 4, 6, 9)
chain4.tg:17:1  error  logic/syllogism  ... (with lines 4, 6, 7)

 1 file - 4 error, 0 warning, 0 info
One report per participating rule, each naming the other three. The finding is reported four times because there is no single place to report it once.

Now delete each rule in turn, along with its @@ note, and check the four survivors. This is leave-one-out, the same instrument we pointed at a good prompt one rule at a time, and here it settles what kind of object the defect is.

tg check - each rule removed in turn
$ typeglish check loo_7.tg loo_11.tg loo_13.tg loo_17.tg
 4 files - 0 error, 0 warning, 0 info
Four rules, four ways to remove one, four clean files. The set of four is unsatisfiable and every subset of three is fine, which is the textbook definition of a minimal unsatisfiable set and a very unhelpful thing to have in a code review.

That is the reframe, and it is worth stating flatly because it changes what you look for. Your prompt is not a list of rules that are individually right or wrong. It is a set of claims with edges between them, and a defect can be a property of a path. There is no line to blame. Ask which rule is broken and the honest answer is none of them, and you have four.

§4A finding with no single location

A defect with no location is awkward to report, and the reporting has a wrinkle you want to know about before you waste ten minutes on it. Each finding carries a range, and the range is a true file line: 7, 11 and 15 in the three-rule file, which is exactly where those rules sit. The numbers inside the message body are a different thing.

tg check triage.tg --json - one finding, trimmed
{
  "code": "logic/syllogism",
  "category": "logic",
  "importance": "critical",
  "severity": "error",
  "blocking": true,
  "range": { "line": 7, "column": 1, "endColumn": 32 },
  "message": "Contradictory chain ... (with lines 6, 8)"
}
range.line is 7 and the rule is on line 7. The message says with lines 6, 8, and the other two rules are on 11 and 15.

The companion numbers count positions in the file with blank lines and @@ annotations removed. In triage.tg that makes the first rule the fourth such line, the second the sixth and the third the eighth, which is where 4, 6 and 8 come from. Add a // comment and the numbering shifts by one, because a comment line keeps its position while an annotation does not. On a bare list of rules with no notes and no blank lines the two schemes coincide, which is presumably why it is easy to miss.

The score output prints both schemes in one report, and once you know that, it stops being confusing: the header rows above the facets say L4, L6, L8, and the ledger rows underneath say L7, L11, L15. Take the ranges, from --json or from the three lines of text output, and treat the in-message list as a count of how many rules the chain needed rather than as an address.

§5Which rule is wrong?

The message ends with Relax one rule, and it does not say which. That is not a gap. A prover can tell you that four claims cannot all hold; nothing in a compiler can tell you which of them your company does not actually believe. That decision is a policy question wearing a compiler error, and the useful thing about the error is that it forces the question into the open, in a pull request, in front of the three teams who each wrote one line.

There are three honest answers here and they do not cost the same. Weaken the middle rule to a majority claim, weaken it to an existential claim, or say what you actually meant and name the categories apart.

tg score - three relaxations, all of them legal
// - Most disputes are escalations.
fix_most.tg - B (87/100)  enforceability 50 · hardness 83 · consistency 100

// - Some disputes are escalations.
fix_some.tg - B (85/100)  enforceability 50 · hardness 70 · consistency 100

// - All disputes are reviews.
fix_renamed.tg - B (89/100)  enforceability 50 · hardness 100 · consistency 100
All three clear the errors and take consistency from 0 to 100. The two hedges pay for it in hardness, 83 and 70 against 100, because most and some are quantifier claims the agent can satisfy by doing almost nothing.

This is the trap in a chain finding, and it is worth naming: the cheapest edit is a hedge. Change one word to most, the three errors vanish, the build goes green, and you have shipped a routing policy that no longer says what happens to a dispute. The checker cannot object, because you did relax a rule and you were told to. The score is the only instrument that prices it, which is the same reason the add-a-rule checklist gates on --min 95 rather than --min B.

The version we would ship does not hedge and does not relax anything. It notices that the taxonomy was never the point. A triage prompt does not need to classify a contact into nested categories; it needs to name a destination. Categories chain, and destinations do not.

triage.tg - the same policy, written as destinations✓ compiles
# Role
@@ role: name the domain so "helpful" has a scope
You are a contact triage agent for Northwind Broadband.

# Definitions
@@ chargeback: the card network settles a chargeback, so nobody here can reverse one
- A chargeback IS a card-network reversal.
@@ dispute: a dispute is ours to decide, which is what makes it different
- A dispute IS a charge the customer asks us to review.

# Routing
@@ chargeback_route: the payments team holds the network case file
- WHEN the customer reports a chargeback THEN you MUST hand off to payments.
@@ dispute_route: a dispute needs a human with ledger access
- WHEN the customer asks us to review a charge THEN you MUST escalate to tier 2.
@@ one_route: two destinations in one reply sends the customer to neither
- You MUST name at most 1 destination in a reply.
0 error, 0 warning, 0 info at A (95/100), up from F (72/100), and no policy was changed on the way. Both definitions still render, so the model still learns the distinction compliance cared about. What is gone is the transitive scaffolding: nothing in this file says that one category contains another, so there is no chain left to close.

That is the transferable move, and it is smaller than it sounds. Every all X are Y in a prompt is an edge in a graph you are not drawing. Sometimes you need the edge, and then you want the checker holding it. Often you wrote a taxonomy when you meant a rule, and the fix is to write the rule.

§6Common questions

Why does my prompt contradict itself when no two rules conflict?
Because a contradiction does not have to be pairwise. If your prompt says all chargebacks are disputes, and all disputes are escalations, and no chargeback is an escalation, then every pair of those three sentences is consistent and the three together are not. A reviewer reads a prompt one rule at a time and at best one pair at a time, so a defect that needs three rules to appear is invisible to the reading. TypeGlish closes the chain instead: the same three lines are 3 blocking logic/syllogism errors and the file scores F (72/100).
What is logic/syllogism in TypeGlish?
It is the blocking error for a contradictory quantifier chain: a transitive path through your all and every claims that forces two categories to overlap while some other rule declares them mutually exclusive. It is reported once per participating rule, each report naming the others, because the defect has no single location. It is distinct from logic/contradiction, which is the pairwise case, and from logic/quantifier, which is a direct conflict between two quantified claims.
Which rule should I fix when the checker reports a contradictory chain?
Whichever one overstated your policy, and the checker will not tell you which that is, because it is not a compiler question. All three relaxations clear the error and they do not cost the same: naming the categories apart takes the file to B (89/100) with hardness 100, while hedging the middle rule to most disputes are escalations gives B (87/100) at hardness 83 and some disputes are escalations gives B (85/100) at hardness 70. The cheapest edit is a hedge, and the score is where you see what the hedge cost.
Do the line numbers in a logic/syllogism message point at my file?
The primary one does and the companions do not. Each finding’s range is a true file line, so a contradictory chain over three rules reports three findings at three real locations. The numbers inside the message body, the with lines 4, 6 part, count positions in the file with blank lines and @@ annotations removed, so in any real prompt they will not match your editor’s gutter. Read range.line from typeglish check --json and treat the message list as a count of how many rules the chain needed.
How do I review an agent prompt for defects that span several rules?
Stop trying, and change what you review. Reading for pairs does not scale: a 40-rule prompt has 780 pairs and 9,880 triples, and no reviewer holds that. Point a checker at the whole file in CI and spend the human read on the things a prover cannot judge, which is whether each rule states the policy you actually have. When a chain does fire, the useful move is leave-one-out: delete each named rule in turn, and the one whose removal you are unwilling to ship is the rule you actually disagree about.
Field note

The reason this class of finding exists at all is that the quantifier words are read as logic rather than as emphasis. all, every and each, some and any, no and none form a square of opposition the checker quantifies over, which is also why the arithmetic in your prompt can be wrong in a way that is provable rather than arguable. Two of those words in one file is a claim. Three of them in a line is a graph, and the graph is the thing your review was never reading.

∿ washed up Aug 7, 2026 ∿