← Tidelines/Deep dives

Not approving is not declining

Most guardrails in a support prompt are written as prohibitions, and for one family of verbs that is enough, because forbidding the thing is instructing its opposite. For the family that actually handles customers, it is not. Here is where the line falls and how to tell which side you are writing on.

by TypeGlish team8 min read#deep-dives
Silence is not an answer.

TL;DR A refunds section written entirely as MUST NOT approve rules is 0 error, 0 warning, 0 info at B (86/100) with enforceability 93 and consistency 100 and still specifies no reply at all, because the emission verbs fold a prohibition onto its positive head (MUST NOT display plus MUST withhold is 2 clarity/duplicate warnings, one rule twice) while the response verbs never fold (MUST NOT approve plus MUST decline is 0 error, two rules), so a refusal has to be written as its own obligation or it does not exist.

Read the guardrail section of any support prompt and count the polarity. Almost all of it is never, do not, must not. That is a sensible instinct: the expensive failures are things the agent did, so the rules name the things it must not do. The instinct is also load-bearing in a way nobody checks, because it quietly assumes that forbidding an action tells the agent what to do instead.

For some verbs that assumption is exactly right, and the compiler will prove it to you. For the verbs that decide how a customer is answered, it is exactly wrong, and the compiler will prove that too, by staying silent in a way that turns out to be informative.

§1Four rules, no reply

Here is a refunds section of the kind a midmarket CX team ships in week two. Every rule in it came from a real incident. It is short, it is specific, and it has bounds.

refunds-forbid.tg - the guardrails, as written✓ B (86/100)
# Role
You are a refunds agent for Marlow Kitchens.
You manage a refund request.

# Constraints
- You MUST NOT approve a refund over 100 dollars.
- You MUST NOT approve a refund on a clearance item.
- You MUST NOT approve a second refund on one order.
- You MUST hand off to a human agent when the customer asks for one.
Three ceilings and an escape hatch. Now read it as the customer: what happens when somebody asks for 240 dollars back on a clearance blender?
tg check, score and build - refunds-forbid.tg
$ npx typeglish check refunds-forbid.tg
 1 file — 0 error, 0 warning, 0 info

$ npx typeglish score refunds-forbid.tg
refunds-forbid.tg — B (86/100)  proven errors: none  tiers: base+z3
  planes  runtime 98 (what the model reads) · hygiene 50 (source only)
  facets  enforceability 93 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
  lever   annotation 0/100 (up to +12 overall) — Put a "@@ why" note directly above each statement ("@@ name: why" also names it).

$ npx typeglish build refunds-forbid.tg
 built .typeglish/dist/refunds-forbid.txt ← refunds-forbid.tg (066d0e388abf, full)
# 321 bytes, shipped as written
Nothing to fix, nothing to argue with, and enforceability 93 because every rule names a concrete action with a bound. The artifact goes to production containing not one instruction about what the agent says to the customer who asked.

What actually happens in that conversation is decided by the model, per conversation. Sometimes it declines and explains the cap. Sometimes it declines and does not say why, which reads as a brush-off. Sometimes it hedges into a paragraph that never contains the word no, and the customer waits for an answer that is never coming, then asks again, then asks for a person. All three are compliant with every rule in the file.

§2The family that folds, and the family that does not

The reason this is a design principle rather than an oversight sits in the verb ontology. Directive heads parse against a curated catalog of verb families, and the families do not all behave the same way. Two lines, in two families, with the same grammatical shape.

emission-pair.tg - a prohibition and its positive twin✗ 2 warning
# Role
You are a refunds agent for Marlow Kitchens.
You manage a reason code.

# Constraints
- You MUST NOT display the reason code.
- You MUST withhold the reason code.
response-pair.tg - the same shape, one family over✓ 0 error
# Role
You are a refunds agent for Marlow Kitchens.
You manage a refund request.

# Constraints
- You MUST NOT approve the refund request.
- You MUST decline the refund request.
tg check - both files, one invocation
$ npx typeglish check emission-pair.tg response-pair.tg
emission-pair.tg:6:1  warn   clarity/duplicate  Duplicate rule — "display the reason code." is forbidden in 2 places (also line 6). State it once and reference it.
emission-pair.tg:7:1  warn   clarity/duplicate  Duplicate rule — "display the reason code." is forbidden in 2 places (also line 5). State it once and reference it.

 2 files — 0 error, 2 warning, 0 info
Same two-line shape, same subject, same modals. In the emission file the compiler collapsed both lines to one key and told you one of them is redundant. In the response file it found two rules and had nothing to say.

That is the whole principle, and it is deliberate. The emission family is one polarity system: withhold, suppress, hide, omit, redact, exclude are negative-pole verbs that fold onto their positive heads, so must not display and must withhold are the same rule stated twice. The response family, approve, reject, accept, decline, grant, refuse, is explicitly excluded from that fold, and the reason is one line of design rationale worth memorising:

Rejecting is an act, not an absence.

Withholding a reason code is what happens when you do not display it. There is no equivalent for a refusal. Not approving a refund is a state of the world; declining it is something the agent has to do, in words, to a person who is waiting. The compiler refuses to pretend otherwise, which is why it will not accept your prohibition as a substitute for an instruction.

It also means the redundancy warning is a service you only get on one side. Write the same policy twice in the emission family and the checker hands you clarity/duplicate. Write it twice in the response family and both lines survive, because to the compiler they were never the same thing. That asymmetry runs the other way too, and two rules can be exact opposites while sharing no vocabulary at all when the fold is what connects them.

§3The score cannot see the missing half

The natural next hope is that the grade catches what the check does not. It does not, and the way it fails is worth looking at directly, because it tells you what kind of defect this is.

tg score - the prohibition against the obligation
$ npx typeglish score p-forbid.tg
# - You MUST NOT approve a refund over 100 dollars.
p-forbid.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

$ npx typeglish score p-decline.tg
# - You MUST decline a refund over 100 dollars.
p-decline.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
Identical, facet for facet. Both sentences name a concrete action with a numeric bound and commit to a pole, so every instrument the scorer has reads them as equally good rules. They are equally good rules. Only one of them is an answer.

This is the same boundary that makes an unbounded retry outscore a bounded one, and it is a boundary rather than a bug. Enforceability asks whether a rule names a checkable action. Both do. Nothing in either sentence indicates that the first one leaves the customer-facing half of the decision unwritten, because the missing half is not in the sentence. It is in the space beside it.

Which gives the practical test, and it takes about four seconds per rule. Read your prohibition and ask what the transcript looks like if the agent obeys it and does nothing else. For must not display the reason code, the transcript is fine: the agent answers, and the code is not in it. For must not approve the refund, the transcript is a customer waiting.

§4A refusal owes the file three lines

Once you accept that the refusal has to be written, the shape of it falls out. Every refusal a support agent gives is three obligations, and prompts routinely ship one of them or none.

The refusal itself, so there is an answer. The reason, because a refusal with no reason reads as a fault and gets asked again in the next turn. And the exit, because a customer who has been told no and given nowhere to go asks for a person, which is the outcome the cap existed to avoid paying for.

The threshold belongs to the runtime rather than to the model. The order total is a number the host already has; asking the model to compare it is asking it to do arithmetic it may or may not do, when the compiler can do it once, at build time, and delete the arm that lost.

refunds-final.tg - the refusal as an obligation✓ A (100/100)
<$CONFIG>
  $IMPORT tool issue_refund
  $REQUIRE variable refund_amount: number
</$CONFIG>

# Role
@@ role: refunds desk, capped spend, a human is always available above the cap
You are a refunds agent for Marlow Kitchens.
@@ subject: introduces the request every rule below decides on
You manage a refund request.

# Constraints
@@ cap: 100 is the ceiling finance signed off, so above it a person owns the call
$IF @{refund_amount} is greater than 100:
  @@ refuse: the prohibition left the reply undefined, so the refusal is the obligation
  - You MUST decline the refund request.
  @@ reason: a refusal with no reason reads as a fault, and the caller asks again
  - You MUST say that refunds over 100 dollars need a human agent.
  @@ exit: every refusal hands the customer somewhere to go next
  - You MUST offer a transfer to a human agent.
$ELSE:
  @@ pay: under the cap the tool is the whole procedure
  - You MUST call @[issue_refund].

$TEST over_cap
  - input:: I want 240 dollars back on order 88120.
  - expect::
    - contains "100"
    - at most 3 sentences
0 error, 0 warning, 0 info at A (100/100), every facet at 100 except directness at 98. The three prohibitions are gone, and nothing was lost: an agent handed only the second arm has no instruction that would let it approve anything.
tg build - one source, two artifacts, neither ambiguous
$ npx typeglish build refunds-final.tg --vars '{"refund_amount":240}'
 built .typeglish/dist/refunds-final.txt ← refunds-final.tg (2492a2d59f91, full)

# Role
You are a refunds agent for Marlow Kitchens. You manage a refund request.

# Constraints
- You MUST decline the refund request.
- You MUST say that refunds over 100 dollars need a human agent.
- You MUST offer a transfer to a human agent.
# 246 bytes. Three obligations, no prohibitions, and no decision left in the room.

$ npx typeglish build refunds-final.tg --vars '{"refund_amount":40}'
 built .typeglish/dist/refunds-final.txt ← refunds-final.tg (5347a93fd349, full)

# Constraints
- You MUST call issue_refund.
# 126 bytes. The whole refusal policy is absent, because it does not apply.
The over-cap artifact is 246 bytes against the 321 the prohibition-only file shipped. It says more and is smaller, because a resolved conditional deletes text and three obligations replaced three ceilings plus a gap.

§5The gap the checker leaves open

One consequence of the no-fold rule deserves saying out loud, because it is the cost side of the design and you should know about it before you meet it in a review.

both-ways.tg and one-verb.tg - two files, one blocks
$ npx typeglish check both-ways.tg one-verb.tg
# both-ways.tg:  - You MUST approve the refund request.
#                - You MUST decline the refund request.
# one-verb.tg:   - You MUST decline the refund request.
#                - You MUST NOT decline the refund request.
one-verb.tg:6:1  error  logic/contradiction  Conflicts with line 6. Logical conflict — "decline the refund request" is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...).
one-verb.tg:7:1  error  logic/contradiction  Conflicts with line 5. Logical conflict — "decline the refund request" is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...).

 2 files — 2 error, 0 warning, 0 info
# exit 1
A file requiring the agent to both approve and decline the same request is 0 error. A file requiring it to decline and not decline is 2 blocking errors. Only the second is one key at two poles, so only the second is provable.

Both files are impossible to obey and only one of them stops the build. That is the honest price of not folding: a prover that treated approve and decline as one axis would catch the first file, and would also have to conclude that MUST NOT approve already means MUST decline, which is the false equivalence this whole post is about. The design picks the error it prefers to make, and it picks the one that leaves the gap visible instead of papering over it.

So the review habit that closes it is yours, not the tool's: one verb per decision, and the decision written as what the agent does. The same discipline is what stops an apology policy written by three desks from turning into three policies that cannot argue with each other, and it is the reason a prompt is easier to review after you have deleted the word not from it a few times.

§6Common questions

Why does my AI agent go quiet instead of saying no?
Because the rule you wrote forbade the approval and never required the refusal, so the only behaviour the prompt specified is an absence. A refunds section of three MUST NOT approve lines is 0 error, 0 warning, 0 info, clean under --strict, B (86/100) with enforceability 93 and consistency 100, and it builds to a 321-byte artifact in which no instruction tells the agent to reply anything at all. Add the obligation, not a sterner prohibition: MUST decline, MUST say why, MUST offer the next step.
Is MUST NOT approve the same as MUST decline in a system prompt?
Not to the checker, and not to a customer. Put both in one file and it is 0 error, 0 warning, 0 info: two rules, both load-bearing. Write the emission equivalent, MUST NOT display the reason code beside MUST withhold the reason code, and the same shape is 2 clarity/duplicate warnings reading display the reason code. is forbidden in 2 places, because the emission family folds a negative-pole verb onto its positive head and one of the two lines is redundant. Emission verbs fold; response verbs do not.
Does the TG score tell me a prohibition is incomplete?
No, and it should not pretend to. You MUST NOT approve a refund over 100 dollars. and You MUST decline a refund over 100 dollars. both score B (87/100) with enforceability 100, hardness 100, directness 100 and consistency 100, facet for facet identical. Both sentences name a concrete action with a bound, which is what enforceability measures. The difference between them is not in either sentence, it is in the reply the first one never specifies, so no facet computed from the text can see it.
How do I write a refusal rule an AI agent will actually follow?
Write three obligations rather than one prohibition: the refusal, the reason, and the exit. Move the threshold to a typed input so the compiler picks the branch, with $REQUIRE variable refund_amount: number and a $IF @{refund_amount} is greater than 100 chain, so the over-cap build is a 246-byte artifact whose only instructions are to decline, state that refunds over 100 dollars need a human agent, and offer a transfer, while the under-cap build is 126 bytes and says only to call the tool. That file is 0 error, 0 warning, 0 info at A (100/100).
Field note

The figure that changed how we read guardrail sections is the two-file check in §2, and specifically that it takes one invocation. Same shape, same modals, same object pattern, and the compiler treats one pair as a restatement and the other as two independent facts, with no flag anywhere to say why. The rule is not written on the sentence, it is written in the family the verb belongs to, and you can read the families out of typeglish reference in about a minute: emission carries the polarity fold and the note that a negative-pole verb folds onto its positive head, response carries the note that rejecting is an act rather than an absence. Since then the first pass on any Constraints section has been a polarity count. If a section is all prohibitions and the verbs are response verbs, the prompt has described a wall and has not described a door, and the transcripts will show you exactly what a model does when it hits a wall with no door: something different every time.

∿ washed up Sep 10, 2026 ∿