Teardown: the do-not-say list from the brand team
Eight lines of tone of voice, pasted under a heading. They arrive as eight hard rules, they print every forbidden word into the prompt, and the one that argues with your apology rule draws nothing at all.
TL;DR A do-not-say list pasted into an agent prompt becomes eight rules at NEVER and ALWAYS force that outnumber your actual policy, ships every forbidden word to the model in one reflowed paragraph, and cannot collide with the rules it contradicts, because a ban on a word and a rule about an act are different keys. Keep the acts as rules and move the vocabulary into an $EXAMPLE and a matches assert.
Halden Living sells furniture online and runs a chat agent called Rue. The comms lead sends round a tone-of-voice page: eight lines, the accumulated wisdom of a year of listening to replies that landed badly. Somebody pastes it under a new heading at the bottom of the prompt, where it looks like guidance. The file checks clean. It has been there for two months, and in that time nobody has read it as policy, which is exactly what it became on the day it landed.
§1The block, as pasted
# Role You are Rue, the support agent for Halden Living, an online furniture retailer. # Constraints - MUST keep every reply to at most 4 sentences. - WHEN a customer reports a damaged item THEN MUST apologize once. - WHEN a customer asks for a refund THEN MUST transfer them to the returns desk. # Tone of voice Never say sorry. Do not use the word policy. Avoid unfortunately. Never say no problem. Do not say only. Never tell a customer to calm down. Always say happy to help. Never use jargon.
$ npx typeglish review voice.tg voice.tg — B (84/100) → A (98) projected if every fix below lands check: 0 error, 0 warning, 2 info FIXES ranked by projected gain +12 put a "@@ why" note above L2 L5 L6 L7 L10 L11 L12 L13 +4 more — annotation 0 → 100 +1 make L11 L17 provable — one verb the checker knows, one object, or a bound +1 fix the 2 prompt/unintroduced-definite findings (L7 L11) RULES 11 of 11 rule-shaped lines read · 2 not provable L11 ~ unprovable Do not use the word policy. the checker knows "use" but cannot prove this shape: "use" is a light verb with nothing bound to it → name the tool or object it acts on (call @[tool], make a note) L17 ~ unprovable Never use jargon. the checker knows "use" but cannot prove this shape: "use" is a light verb with nothing bound to it → name the tool or object it acts on (call @[tool], make a note) TESTS no $TEST cases — add one beside a rule; `test --dry` validates it offline
11 of 11 rule-shaped lines read: three of those rules are the returns policy, and eight of them came off a slide about how to sound.§2Eight preferences, arriving as rules
The tone page was written in the imperative, the way any style guide is written, and the imperative is how you write a rule. Never and Always are not emphatic English here. They are the two hard corners of the deontic scale: the same force as NEVER share a card number, the same force as the transfer rule three lines up. Nothing marks the block as advisory, because nothing in the file can. A heading is not a scope.
So the deployed policy of this desk is eleven rules, and eight of them are about vocabulary. Rue is bound to the same degree on no problem as on where a refund goes. If a future edit ever has to lose an argument between the two, there is nothing in the file that says which one gives way.
Nobody decided that the word unfortunately outranks a refund. The paste decided it.
§3The ban that cannot meet the rule it contradicts
Line 6 says WHEN a customer reports a damaged item THEN MUST apologize once. Line 10 says Never say sorry. To everybody at Halden those are one instruction pointing two ways, and it is the single most consequential thing in the file: the agent is required to apologize and forbidden from the word almost every apology is made of.
The checker reports consistency 100. It is not being lax. The prover compares claims about actions, and say sorry is not the action apologize - two entries in the verb catalog, two keys, never joined. This is the shape taken apart in advice is a verb, arriving from the other direction: there, a prohibition was too narrow to cover the act somebody meant; here, a prohibition names a word and the obligation names the act, so the two never even look at each other.
The way to see what the line is worth is to write the ban as the act it is really about, and change nothing else.
# Role You are Rue, the support agent for Halden Living, an online furniture retailer. # Constraints - WHEN a customer reports a damaged item THEN MUST apologize once. - NEVER apologize.
$ npx typeglish check probe.tg probe.tg:5:1 error logic/contradiction Conflicts with line 6. Counted rule — "apologize once." vs "apologize." is bound to exactly 1 but exactly 0. The counts cannot both hold. ↳ line 6: - NEVER apologize. probe.tg:6:1 error logic/contradiction Conflicts with line 5. Counted rule — "apologize once." vs "apologize." is bound to exactly 1 but exactly 0. The counts cannot both hold. ↳ line 5: - WHEN a customer reports a damaged item THEN MUST apologize once. ✗ 1 file — 2 error, 0 warning, 0 info $ npx typeglish score probe.tg probe.tg — F (70/100) proven errors — grade capped at F tiers: base+z3
§4Every forbidden word, delivered to the model
Now build it and read what Rue is actually sent.
$ npx typeglish build voice.tg
✓ built .typeglish/dist/voice.txt ← voice.tg (5b8c546e1383, full)
$ cat .typeglish/dist/voice.txt
# Role
You are Rue, the support agent for Halden Living, an online furniture retailer.
# Constraints
- MUST keep every reply to at most 4 sentences.
- WHEN a customer reports a damaged item THEN MUST apologize once.
- WHEN a customer asks for a refund THEN MUST transfer them to the returns desk.
# Tone of voice
Never say sorry. Do not use the word policy. Avoid unfortunately. Never say no problem. Do not say only. Never tell a customer to calm down. Always say happy to help. Never use jargon.
That is the quiet cost of a vocabulary ban. The prompt is the only document in the pipeline the model reads in full, every turn, and a do-not-say list is a list of the words you do not want, written out, in the place where the words you do want are supposed to be. It is a prohibition that has to be understood as a negation to be obeyed, which is a lot of work to ask for a phrasing preference.
Two of the eight lines do not even survive parsing as rules with a verb the checker can use. Do not use the word policy and Never use jargon come back as unprovable: use is a light verb with nothing bound to it, so the sentence reads as a rule and proves nothing. And the checker files an info on the phrase the word policy, because the in subject position names exactly one such thing and nothing in the file introduces a policy. It is not wrong to flag it. The line really is a definite reference to an object the document never declares.
§5The rebuild: sort acts from strings
Every line on a tone page is one of three things, and the sorting is the whole job.
# line what it is where it goes
Never say sorry. an act decide it: the apology rule wins
Do not use the word policy. a string $EXAMPLE bad:: and a matches assert
Avoid unfortunately. a string a matches assert
Never say no problem. a string a matches assert
Do not say only. a string a matches assert
Never tell a customer to calm down. an act, unwritten a de-escalation rule with a trigger
Always say happy to help. a string $EXAMPLE good::
Never use jargon. a bound in hiding at most 4 sentences, already there
The act. Never say sorry and MUST apologize once were always one decision taken twice, in opposite directions, by two teams who never met. Somebody has to settle it. Halden settles it for the policy: the desk apologizes once for a damaged delivery, and the demonstration shows the house wording for it. The ban comes out of the file, because a rule nobody intends to follow is worse than no rule.
The strings. Those become an $EXAMPLE pair and a deterministic assert. The good:: response is compiled into the artifact as a User and Assistant exchange the model reads; bad:: is held out of the prompt entirely, so the phrasing you are trying to kill is demonstrated to the eval suite and never to the model, the difference measured in your bad example is a good example. The assert is the part that actually gates: matches reads the reply, which is the only place the banned word could ever appear.
The one that is a rule nobody wrote. Never tell a customer to calm down is not about a phrase. It is a desk that has watched an agent mishandle an angry customer and has no rule for what to do instead. It stays out of this rebuild on purpose, because it needs a trigger and an action of its own, which is the whole of building a de-escalation agent.
# Role @@ role: one sentence names the speaker, so every later "you" is the agent You are Rue, the support agent for Halden Living, an online furniture retailer. # Constraints @@ brevity: four sentences is this desk's limit on a chat reply - MUST keep every reply to at most 4 sentences. @@ damage: one apology, then the fix - WHEN a customer reports a damaged item THEN MUST apologize once. @@ refunds: a refund is a returns handler's call, not this desk's - WHEN a customer asks for a refund THEN MUST transfer them to a returns handler. @@ cause: a reason for a late delivery is a guess, and the customer hears it as a promise - NEVER state a cause for a late delivery. // the wording preferences, as a demonstration instead of eight rules $EXAMPLE damaged_arrival - input:: The bookcase turned up with a split panel. - good:: I am sorry about the split panel. I can send a replacement panel this week, or arrange a collection if you would rather send the bookcase back. - bad:: Unfortunately our policy only allows a replacement. // the same preferences, as asserts that run on the reply $TEST damaged_arrival - input:: The bookcase turned up with a split panel. - expect:: - apologizes once - matches /^(?!.*[Uu]nfortunately)(?!.*[Nn]o problem).*$/ - at most 4 sentences
bad:: line carries three of the banned words at once, and no customer will ever see it.$ npx typeglish check voice.tg ✓ 1 file — 0 error, 0 warning, 0 info $ npx typeglish test voice.tg --dry ✓ voice.tg coverage: 2/4 rules exercised uncovered (no case shares the rule's wording): L11 transfer them to a returns handler. L13 state a cause for a late delivery. · damaged_arrival — "The bookcase turned up with a split panel." (not run) ✓ matches /^(?!.*[Uu]nfortunately)(?!.*[Nn]o problem).*$/ ✓ at most 4 sentences · rubric — 1 prose expectation, judged on a live run (needs ANTHROPIC_API_KEY) ✓ 1 prompt — 0 failed $ cat .typeglish/dist/voice.txt # Role You are Rue, the support agent for Halden Living, an online furniture retailer. # Constraints - MUST keep every reply to at most 4 sentences. - WHEN a customer reports a damaged item THEN MUST apologize once. - WHEN a customer asks for a refund THEN MUST transfer them to a returns handler. - NEVER state a cause for a late delivery. <example> User: The bookcase turned up with a split panel. Assistant: I am sorry about the split panel. I can send a replacement panel this week, or arrange a collection if you would rather send the bookcase back. </example>
§6Common questions
- How do I stop my AI agent from saying a word the brand has banned?
- Not by writing the word in the prompt. A line like
Avoid unfortunatelyis the only place that word appears in the deployed artifact, and it asks the model to notice an absence, which nothing can check. Put the phrasing you do want in an$EXAMPLEgood::response, put the phrasing you do not want inbad::, which is held out of the artifact, and assert the string in a$TESTwithmatches /^(?!.*[Uu]nfortunately).*$/. The assert reads the reply, which is where the word would actually appear. - Why does my agent still apologize when the prompt says never say sorry?
- Because a ban on a word and a rule about an act are different keys, and nothing in the file makes them meet.
NEVER say sorrysitting underWHEN a customer reports a damaged item THEN MUST apologize oncechecks clean atconsistency 100: the prover compares claims about actions, and say sorry is not the action apologize. Rewrite the ban with the act's own verb,NEVER apologize, and the same pair is two blockinglogic/contradictionerrors with the build refused. The conflict was always there; only one spelling of it is reachable. - Should brand tone-of-voice rules go in the system prompt?
- The acts yes, the vocabulary no. Pasted under a heading, eight tone lines are read as eight rules at
NEVERandALWAYSforce, the two hard corners of the scale, so a ban on the phrase no problem carries exactly the weight of the rule that routes refunds, and there are nearly three times as many of them. Keep the lines that name an act the agent performs, and move the lines that name a string into the example and the test suite. - Does TypeGlish check a do-not-say list?
- It reads one. The eight pasted lines all parse as rule-shaped statements and count toward the rule total, which is why the file grades B rather than being ignored. What it cannot do is prove anything about a word:
Do not use the word policyandNever use jargoncome back as unprovable, becauseuseis a light verb with nothing bound to it, and the only findings on the block are twoprompt/unintroduced-definitenotes. A green check on a vocabulary ban means the line parsed, not that it holds.
One line in that list is worth a second look for a reason that has nothing to do with brand: Do not say only. In TypeGlish, only is a quantifier, one of the words the prover reads to close a relation to a single target, and an only rule that another line steps outside of is a provable error. So a word can be banned in the replies and load-bearing in the rules at the same time, and the two uses never touch. That is the whole teardown in miniature. A tone page is about the sentences a customer receives; a prompt is about the acts an agent performs; and pasting the first into the second turns preferences into policy without anybody signing off on it. Go and look at the bottom of your own file. If there is a block down there that arrived from outside the team, count how many of your rules it now outnumbers.