← Tidelines/Teardowns

Teardown: the apology rules three desks wrote

Every contact centre has this fight. CX wants the agent to say sorry, compliance wants it to admit nothing, and QA wants it to stop repeating itself. Here is what twelve lines of that argument look like to a compiler, and which half of it a compiler can actually referee.

by TypeGlish team9 min read#teardowns
Sorry. Not our fault.

TL;DR An apology section written by three desks is 2 error, 1 warning, 2 info at F (55/100) with the build refused, and both blocking errors are one rule restated at a weaker strength, while You MUST NOT admit fault. sitting nine lines under You MUST always apologize for a delay. is 0 error, 0 warning, 0 info, because apologize, say sorry and admit are three different keys and a prover can only referee two rules that share one.

The apology rules are never written in one sitting. A CX lead writes the first one because the transcripts read cold. Compliance adds a line after a claim goes badly. A QA analyst adds a third the quarter somebody counts how many times the bot says sorry in a four-turn chat. Each edit is small, each is correct on the day, and none of the three authors opens the file to read the other two.

The subject here is a shape rather than a company: a regional energy retailer's chat agent, the kind of midmarket deployment where one prompt handles outages, billing and moves. Twelve content lines, three sections, three desks.

§1Twelve lines, three desks

Here is the file as it stands. Read it the way a reviewer would: nothing in it is stupid, and every line has a person behind it who could defend it.

apology-block.tg - the prompt as inherited✗ 2 error, 1 warning
# Role
You are a chat support agent for Whitcombe Energy.
You manage a reply.
You manage a delay.

# Tone
- You MUST always apologize for a delay.
- Try to sound genuinely sorry.
- You MUST be empathetic.

# Compliance
- You MUST NOT admit fault.
- You MUST NOT say sorry for an outage we have not confirmed.

# Quality
- You sometimes apologize for a delay.
- You MUST apologize at most 1 time in a reply.
Tone is the CX lead, Compliance is the reviewer who saw a transcript go to a regulator, Quality is the analyst with the sorry-count spreadsheet. Three sections, one subject, and the file never says so.
tg check apology-block.tg - output
$ npx typeglish check apology-block.tg
apology-block.tg:7:1   error  logic/quantifier-strength  Conflicts with line 13. One rule, two strengths — "apologize for a delay." (required on every occasion) already entails "apologize for a delay" (required on some occasions); a model may read the weaker as "not always". Keep the stronger line.
apology-block.tg:8:3   info   prompt/hedging  Hedging — "Try to" turns this instruction into a suggestion the model may skip. Delete the hedge, or commit to a modal (MUST / NEVER / SHOULD).
apology-block.tg:8:1   info   prompt/unregistered-doer  A bare generic doer never enters the world model - instruction to the agent, or background about users? Use the imperative if the agent acts, or a definite party ("The user should ...") to register the doer.
apology-block.tg:9:1   warn   prompt/unmeasurable  Unmeasurable rule — no observable action or bound, so nothing can check compliance. Name a concrete action ("cite the source", "at most 3 sentences") or move it to a prose block.
apology-block.tg:16:1  error  logic/quantifier-strength  Conflicts with line 6. One rule, two strengths — "apologize for a delay." (required on every occasion) already entails "apologize for a delay" (required on some occasions); a model may read the weaker as "not always". Keep the stronger line.

 1 file — 2 error, 1 warning, 2 info
# exit 1
Five rows. Two of them are the same finding reported from both ends, so the file has four distinct defects, and the build stops.
tg score and tg build - the grade and the refusal
$ npx typeglish score apology-block.tg
apology-block.tg — F (55/100)  proven errors — grade capped at F  tiers: base+z3
   L6 logic/quantifier-strength: Conflicts with line 13. One rule, two strengths — "apologize for a delay." (requ
   L13 logic/quantifier-strength: Conflicts with line 6. One rule, two strengths — "apologize for a delay." (requ
  planes  runtime 56 (what the model reads) · hygiene 50 (source only)
  facets  enforceability 58 x.21 · hardness 76 x.12 · directness 93 x.08 · consistency 0 x.17 · structure 100 x.12 (hygiene) · annotation 0 x.12 (hygiene) · style 55 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.

$ npx typeglish build apology-block.tg
typeglish build: apology-block.tg refused — nothing written
# exit 1
consistency 0 on a file whose sections agree about almost everything. The facet is not measuring whether the policy is coherent; it is measuring whether the same fact is stated in one place, which is a much narrower and much more provable question.

§2Both errors are one rule, written twice

Take the blocking pair first, because it is the one thing here a compiler is genuinely certain about. Line 7 says You MUST always apologize for a delay. Line 16, nine lines and two sections later, says You sometimes apologize for a delay.

"apologize for a delay." (required on every occasion) already entails "apologize for a delay" (required on some occasions); a model may read the weaker as "not always".

The frequency words are a quantifier square over occasions. ALWAYS and NEVER are its all and none corners; sometimes, occasionally and at times claim some occasions; usually, mostly and typically claim most. One quantity per fact, so a second claim about the same action on the same axis is either contained by the first or contains it, and the looser one is dead weight.

What makes it worth stopping the build rather than filing a note is the clause at the end of the message: a model may read the weaker as "not always". This is not redundancy the way a duplicated comment is redundancy. A reader who is handed always X and then, later and in a different section, sometimes X, has been given a reason to think the first one was an overstatement. The QA analyst who wrote line 16 meant do not overdo it. What landed in the document was a licence to skip.

Notice also where the two lines came from. The Quality section was written by somebody solving a real problem, and it solved it by weakening a rule in a section they were not editing. That is the ordinary mechanism, and it is the same one that turns two individually clean prompts into a broken third when a re-org merges two desks.

§3Three verbs, three rules, no argument

Now the part everybody assumes a checker is for. The file contains what looks like the classic contact-centre contradiction: an unconditional instruction to apologize, and a compliance rule forbidding an admission. Ask the checker about it in isolation and here is what you get.

fault-only.tg - the compliance line alone✓ 0 error
# Role
You are a chat support agent for Whitcombe Energy.
You manage a reply.

# Compliance
- You MUST NOT admit fault.
two-keys.tg - the collision everybody worries about✓ 0 error
# Role
You are a chat support agent for Whitcombe Energy.
You manage a reply.

# Compliance
- You ALWAYS apologize for a delay.
- You MUST NOT say sorry for a delay.
one-key.tg - the same policy on one verb✗ 2 error
# Role
You are a chat support agent for Whitcombe Energy.
You manage a reply.

# Compliance
- You ALWAYS apologize for a delay.
- You MUST NOT apologize for a delay.
tg check - all three, one invocation
$ npx typeglish check fault-only.tg two-keys.tg one-key.tg
one-key.tg:6:1  error  logic/contradiction  Conflicts with line 6. Logical conflict — "apologize for a delay" is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...).
one-key.tg:7:1  error  logic/contradiction  Conflicts with line 5. Logical conflict — "apologize for a delay" is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...).

 3 files — 2 error, 0 warning, 0 info
# exit 1
Three files, one difference between the second and the third: a verb. say sorry and apologize are the same policy in English and two keys to the compiler, so the pair that a human reads as a flat contradiction is silent, and the pair on one verb is two blocking errors.

This is the verb ontology doing exactly what it says on the tin, and it is worth understanding rather than resenting. Directive heads parse against a curated catalog. apologize is a catalog verb in the communication family. say is also in that family, but it heads its own frame, and the family notes are explicit that say, tell and state are judged by the semantic tier and never merged, because they are the verbs whose meaning lives in the object. And admit is not in the ontology at all, which is why the compliance line sails through alone: there is nothing to key it to.

Compare that with the emission family, where the polarity system folds a negative-pole verb onto its positive head, so withhold and do not display are one key and the checker will tell you when you have written both. Emission folds; speech acts do not. Two rules can be exact opposites and share no vocabulary at all when the fold is on your side. Here it is not, and the same asymmetry decides whether a refusal ever gets written at all, which is the subject of not approving is not declining.

The practical rule this gives a reviewer is unglamorous and works. Pick one verb per behaviour and make the whole file use it. An apology policy that says apologize in Tone, say sorry in Compliance and acknowledge in Quality has three policies, and the only reason it looks like one is that you are reading it in English. It is the same trap that lets close a chat and end a conversation sit four words apart without a finding.

§4The count that did not count

Line 17 is the QA analyst's other edit, and it is the best line in the file: You MUST apologize at most 1 time in a reply. It carries a number, and a number is the thing another section can be caught disagreeing with. Nothing fired on it here, because nothing else in the file counts anything.

Give it something to argue with and the pile is instructive.

counts.tg - two desks, one action, two numbers✗ 6 error
# Role
You are a chat support agent for Whitcombe Energy.
You manage a reply.

# Compliance
- You MUST apologize at most 1 time in a reply.
- You MUST apologize at least 2 times in a reply.
tg check counts.tg - three codes, both directions
$ npx typeglish check counts.tg
counts.tg:6:1  error  logic/contradiction  Conflicts with line 6. Counted rule — "apologize at most 1 time in a reply." vs "apologize at least 2 times in a reply." is bound to at most 1 but at least 2 per reply. The counts cannot both hold.
counts.tg:6:1  error  logic/action-count  Conflicts with line 6. One action, two counts — "apologize at most 1 time" but "apologize at least 2 times". A directive carries ONE count for "apologize times in a reply" — state how many once.
counts.tg:6:1  error  logic/numeric  Conflicts with line 6. Numeric conflict — "at most 1 time" and "at least 2 times" can't both hold.
counts.tg:7:1  error  logic/contradiction  Conflicts with line 5. Counted rule — "apologize at most 1 time in a reply." vs "apologize at least 2 times in a reply." is bound to at most 1 but at least 2 per reply. The counts cannot both hold.
counts.tg:7:1  error  logic/action-count  Conflicts with line 5. One action, two counts — "apologize at most 1 time" but "apologize at least 2 times". A directive carries ONE count for "apologize times in a reply" — state how many once.
counts.tg:7:1  error  logic/numeric  Conflicts with line 5. Numeric conflict — "at most 1 time" and "at least 2 times" can't both hold.

 1 file — 6 error, 0 warning, 0 info
# three codes per line, reported from both ends
Two lines, six errors, three separate provers agreeing. Read the slot name in the logic/action-count row: "apologize times in a reply". The count is not attached to the sentence, it is attached to an action-and-scope pair, which is why the second author cannot hide a change by rewording.

Put the two halves of §3 and §4 beside each other and the shape of the whole teardown appears. The parts of an apology policy that a compiler can referee are the ones you wrote as a quantity: how often, how many, on which occasions. The parts it cannot are the ones you wrote as a choice of word. That is not a gap in the tool, it is a fact about where the meaning lives, and the useful response is to move more of the policy into the plane that can be checked.

§5The rewrite: one verb, one count, one literal

Four decisions turn the twelve lines into a spec. Pick one verb for the apology and delete the duplicate strength. Put the count on it, once. Make the compliance rule about a named thing the agent emits rather than about a state of mind. And let the runtime tell the prompt whether fault has been established, because the agent cannot know that and should not be asked to guess.

apology-final.tg - the shipped file✓ A (96/100)
<$CONFIG>
  $REQUIRE variable fault_confirmed: boolean
</$CONFIG>

# Role
@@ role: chat support for a regional energy retailer, outages and billing
You are a chat support agent for Whitcombe Energy.
@@ subject: the reply is the object every count below is bound to
You manage a reply.
@@ subject: the outage is what the compliance rule is about
You manage an outage.

# Persona
@@ voice: a prose block is exempt from rule analysis, which is where an unmeasurable line belongs
Be calm, and sound like a person rather than a policy. The customer can tell.

# Apology
@@ count: one apology per reply, because the second one reads as a script
- You MUST apologize at most 1 time in a reply.
@@ copy: compliance signed this sentence, so it ships as a literal rather than as a rule to paraphrase
- You MUST say "Sorry about the wait." at the start of the reply.

# Compliance
@@ cause: an apology is not an admission, so the admission is its own rule on an emission verb
$IF @{fault_confirmed}:
  - You MUST share a cause for the outage.
$ELSE:
  - You MUST NOT share a cause for the outage.

$TEST unconfirmed
  - input:: My power has been off for six hours. Is this your fault?
  - expect::
    - contains "Sorry"
    - at most 3 sentences
0 error, 0 warning, 0 info at A (96/100), consistency 100, annotation 78, and the unmeasurable warning is gone because be empathetic moved into a Persona prose block, which is exempt from rule analysis by design. The signed sentence is in straight double quotes, so nothing inside it parses as an operator.

The compliance line is the edit worth arguing over, so here is the argument. MUST NOT admit fault asks the model not to hold an opinion. MUST NOT share a cause for the outage asks it not to emit a specific thing, and share is an emission verb with a polarity fold behind it, so the day somebody in the outage-comms team writes MUST share a cause for the outage in another section, the build stops instead of shipping. The rule got narrower and the file got safer, which is usually the trade.

tg score, build and test - the shipped file
$ npx typeglish score apology-final.tg
apology-final.tg — A (96/100)  proven errors: none  tiers: base+z3
  planes  runtime 98 (what the model reads) · hygiene 89 (source only)
  facets  enforceability 93 x.21 · hardness 100 x.12 · directness 98 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 78 x.12 (hygiene) · style 100 x.08 · security 100 x.08

$ npx typeglish build apology-final.tg --vars '{"fault_confirmed":false}'
 built .typeglish/dist/apology-final.txt ← apology-final.tg (7f8334e54393, full)

# Role
You are a chat support agent for Whitcombe Energy. You manage a reply. You manage an outage.

# Persona
Be calm, and sound like a person rather than a policy. The customer can tell.

# Apology
- You MUST apologize at most 1 time in a reply.
- You MUST say Sorry about the wait. at the start of the reply.

# Compliance
- You MUST NOT share a cause for the outage.
# 371 bytes. The confirmed build is 5a9c2d582acb at 367, and neither artifact contains the other arm.

$ npx typeglish test apology-final.tg --dry
 apology-final.tg  coverage: 0/4 rules exercised
  · unconfirmed — "My power has been off for six hours. Is this your fault?" (not run)
       contains "Sorry"
       at most 3 sentences
The quotes strip on compile, so the model receives the signed sentence and not the quotation marks. coverage: 0/4 is honest: one offline case validates its asserts without touching any of the four rules, and an apology spec is mostly speech acts, which are the hardest thing in a prompt to cover with a deterministic assert.

§6Common questions

Should my AI support agent apologize to customers?
Yes, and the useful question is not whether but how many times and for what. Write the apology as a counted obligation on one verb rather than as an instruction to sound sorry: You MUST apologize at most 1 time in a reply. is 0 error, 0 warning, 0 info and gives the file a bound that a second author can be caught disagreeing with. You MUST be empathetic. is a prompt/unmeasurable warning, because there is no observable action or bound in it, and the checker's advice on that finding is to name a concrete action or move the line to a prose block.
How do I stop my AI agent from apologizing over and over?
Put a count on the action rather than a tone note beside it, and put the count in one place. A directive carries one count per slot, so You MUST apologize at most 1 time in a reply. beside You MUST apologize at least 2 times in a reply. is 6 blocking errors from two lines, 2 logic/contradiction, 2 logic/action-count and 2 logic/numeric, with the action-count message naming the slot as apologize times in a reply. The pile is the point: once the number exists, a second desk cannot quietly change it.
Can a prompt checker catch never admit fault against always apologize?
No, and no checker honestly can. A file containing only You MUST NOT admit fault. is 0 error, 0 warning, 0 info, and so is a file pairing You ALWAYS apologize for a delay. with You MUST NOT say sorry for a delay. Those are different actions to the compiler: apologize is a catalog verb in the communication family, say heads its own frame, and admit is not in the verb ontology at all. Write both halves on one verb and the same policy is 2 blocking logic/contradiction errors reading apologize for a delay is both required and forbidden. Agreeing on the verb is the reviewer's job; proving the agreement is the compiler's.
Where should compliance-approved wording go in a system prompt?
Inside straight double quotes, in the rule that emits it. A literal is not parsed as an operator or a declaration, so a signed sentence cannot be read as a rule and cannot collide with one, and the quotes strip on compile so the model receives the sentence exactly as approved. You MUST say "Sorry about the wait." at the start of the reply. checks clean and builds to an artifact whose line reads You MUST say Sorry about the wait. at the start of the reply.
Field note

We went into this teardown expecting the legal collision to be the headline and it is not, which is the honest result and the more useful one. A compiler referees keys, and a key is an action plus its object; three desks writing three verbs for one behaviour produce three keys and therefore three policies that cannot argue with each other. Nothing in the tool can fix that, because the tool would have to decide that saying sorry is the same act as apologizing, and the moment it decides that it starts deciding whether declining is the same act as not approving, which it is not. So the leverage is upstream and it is cheap: agree the verb before you write the rule. The blocking finding in §2 is the small reward for having done it once already, because always apologize and sometimes apologize only conflicted at all thanks to two authors, six months apart, happening to reach for the same word.

∿ washed up Sep 10, 2026 ∿