← Tidelines/Best practices

Rewrite: your bad example is a good example

Every prompt has an examples section, and it is the part nobody reviews, because it reads like documentation. Compile it and look at what came out: the two responses you told the agent never to give are in the prompt, and they no longer have quote marks around them.

by TypeGlish team7 min read#best-practices
Do not say this. Says it.

TL;DR A counter-example written in prose puts the banned wording in the prompt: build it and the forbidden price is in the artifact with its quote marks stripped, and three separate examples have reflowed into one paragraph. Move the same content into $EXAMPLE blocks and the - bad:: field is held out of the compiled prompt entirely, taking a support prompt from B (83/100) to A (92/100) with the two banned strings absent from the artifact.

The rules in a prompt get argued over. The examples get pasted in once, during the week the agent launched, and then inherited. Partly that is because examples feel like the safe part: no modals, no bounds, nothing to contradict. Mostly it is because an examples section is written in a form that looks finished, and the version the model reads is not the version you are looking at.

§1Before: three sensible rules and a paragraph

A first-line agent for a mobile network. Three constraints anybody would sign off, then the examples section that grew around them.

tone-before.tg - the inherited version✓ compiles
# Role
You are a support agent for Cobalt Mobile, a mobile network.

# Constraints
- NEVER state a specific price.
- MUST keep every reply to at most 3 sentences.
- ALWAYS offer a callback when a fault is unresolved.

# Examples
For example, if a customer asks what a plan costs, say "I can put you through to our sales team, who have current pricing." Never say "The Unlimited plan is 30 pounds a month", because prices change weekly.
Another example: if the signal is down, reply "I am sorry about that. There is a mast fault in your area. Our engineers are on site now. I will text you the moment it clears. Is there anything else I can help you with today?"
Do not reply like this: "Have you tried turning it off and on again?", which customers find dismissive.
Three examples: one good response, one banned price, one banned brush-off. Note the second one is five sentences long, under a rule that caps replies at three.
tg check tone-before.tg - output
tone-before.tg:10:134  info   grammar/spacing  Space before "," — remove it.
tone-before.tg:11:1    info   prompt/unintroduced-definite  "the signal" retrieves something
  this document never introduces - a model must guess which signal is meant. Introduce it on
  another line ("You manage a signal.") or name it outright.

 1 file — 0 error, 0 warning, 2 info
Two info findings, both cosmetic, neither about the examples. A prose block under # Examples is exempt from rule analysis by design, so this is the checker keeping a promise rather than missing something.

B (83/100), enforceability 85, consistency 100. Nothing here says stop. Now build it, which is the only way to see the file the model is actually given.

tg build tone-before.tg - the artifact
 built .typeglish/dist/tone-before.txt ← tone-before.tg (8302d10c809e, full)

# Role
You are a support agent for Cobalt Mobile, a mobile network.

# Constraints
- NEVER state a specific price.
- MUST keep every reply to at most 3 sentences.
- ALWAYS offer a callback when a fault is unresolved.

# Examples
For example, if a customer asks what a plan costs, say I can put you through to our sales team, who have current pricing. Never say The Unlimited plan is 30 pounds a month, because prices change weekly. Another example: if the signal is down, reply I am sorry about that. There is a mast fault in your area. Our engineers are on site now. I will text you the moment it clears. Is there anything else I can help you with today? Do not reply like this: Have you tried turning it off and on again?, which customers find dismissive.
Three source lines, one artifact line. Every quote mark is gone, and The Unlimited plan is 30 pounds a month is now flat prose inside a prompt whose first rule is NEVER state a specific price.

Two separate things happened on the way through, and both of them removed structure you were relying on.

  • The quotes stripped. A literal's quote marks are a compiler instruction, not content: they mark the span as text and then come off. Every boundary between what to say and the saying of it was carried by those marks.
  • The lines joined. A prose block reflows, so the three examples you wrote on three lines arrive as one paragraph. The blank line you would have used to separate the good one from the bad ones is not there.
A demonstration outranks the label on it. You wrote the label. The model got the demonstration.

§2Why a counter-example is expensive

The failure mode is not that a model cannot read the word never. It is that the two things in that sentence are not equally weighted. Never say X is one clause of instruction; X itself is a fully formed, fluent, in-domain support reply sitting in the context window, at the exact place the prompt says examples live. On the hard turns, the ones where a customer pushes twice for a number, that is the wrong asymmetry to have built.

Which is why $EXAMPLE splits the block in two and treats the halves differently. - good:: is content and gets emitted. - bad:: is control plane: it stays in the file for the next engineer, documents the failure mode, and never reaches the prompt. Same source, two audiences, which is the shape your prompt has a second reader describes for annotations, applied to demonstrations.

The block is checked as structure, so it cannot rot quietly. The field set is closed at three names:

tg check on a misnamed field - output
ex.tg:4:1  error  structure/bad-example  only "- input::", "- good::", and "- bad::" fields
  belong in $EXAMPLE pricing — "- response::" is neither.

 1 file — 1 error, 0 warning, 0 info
Blocking, and it names both the allowed set and the offender. Worth knowing because - response:: is exactly what somebody reaches for, and in a prose block that typo would simply have been more prose.

§3The honest limit: nobody checks your example against your rules

Before the rewrite, one thing the checker does not do, because it changes what you should write. Go back to that five-sentence outage reply under the three-sentence rule. Put it in a proper $EXAMPLE block, where the checker can see it as structured data rather than prose, and ask:

a good example that breaks the rule above it - output
$ typeglish check ex-long.tg
 1 file — 0 error, 0 warning, 0 info
The rule says at most 3 sentences. The demonstration is five. Clean, in both forms. An example is a demonstration, not an assertion, and the checker never reconciles one against a rule.

That is the same boundary the asserts-versus-judge bake-off found from the test side: the compiler will prove things about your rules and will not grade your expectations against them. So the instrument for this reply obeys that rule is a $TEST assert, which is deterministic and runs with no API key. Every example you keep should have one, because an example that quietly contradicts a rule is worse than no example: it is a demonstration beating a bound.

§4After: the same content, sorted by audience

Nothing below is new material. The good responses are the good responses, the banned ones are the banned ones, and the only real edit is that the outage reply got cut to the three sentences the rule always demanded.

tone-after.tg - rewritten✓ A (92/100)
# Role
You are a support agent for Cobalt Mobile, a mobile network.

# Constraints
@@ no_prices: tariffs change weekly, so a quoted price is a promise we cannot keep
- NEVER state a specific price.
@@ brevity: a reply over 3 sentences stops being read on a phone screen
- MUST keep every reply to at most 3 sentences.
@@ callback: an unresolved fault with no callback is the top driver of repeat contacts
- ALWAYS offer a callback when a fault is unresolved.

$EXAMPLE pricing
  - input:: How much is the Unlimited plan?
  - good:: I can put you through to our sales team, who have current pricing for that.
  - bad:: The Unlimited plan is 30 pounds a month.

$EXAMPLE outage
  - input:: My signal has been down all morning.
  - good:: There is a mast fault in your area and our engineers are on site. I will text you the moment it clears. Would you like a callback if it runs past today?
  - bad:: Have you tried turning it off and on again?

$TEST pricing_reply
  - input:: How much is the Unlimited plan?
  - expect::
    - at most 3 sentences
    - contains "sales team"
0 error, 0 warning, 0 info. The the signal finding went too, because the vague noun moved into an - input:: field where it is a customer's words rather than the document's.
tg build tone-after.tg - the artifact
 built .typeglish/dist/tone-after.txt ← tone-after.tg (021fe20de1ac, full)

# Role
You are a support agent for Cobalt Mobile, a mobile network.

# Constraints
- NEVER state a specific price.
- MUST keep every reply to at most 3 sentences.
- ALWAYS offer a callback when a fault is unresolved.

<example>
User: How much is the Unlimited plan?
Assistant: I can put you through to our sales team, who have current pricing for that.
</example>
<example>
User: My signal has been down all morning.
Assistant: There is a mast fault in your area and our engineers are on site. I will text you the moment it clears. Would you like a callback if it runs past today?
</example>
The scaffolding compiled into a delimited User and Assistant pair the model can pattern-match. Both banned responses are absent: grep for either string in the artifact returns nothing. The $TEST block is gone too, because a test is control plane and never ships.

B (83/100) to A (92/100), and the interesting part is where the points came from. runtime 94 to runtime 97 and enforceability 85 to 90, which is modest, because the rules barely changed. The rest is hygiene, 50 to 75, from the annotations. The grade is not the argument here. The argument is the two strings that are no longer in the file the model reads.

tg test tone-after.tg --dry - output
 tone-after.tg  coverage: 0/3 rules exercised
  · pricing_reply — "How much is the Unlimited plan?" (not run)
       at most 3 sentences
       contains "sales team"

 1 prompt — 0 failed
Both expectations validate as deterministic asserts, offline, with no model call. coverage: 0/3 is honest and worth reading: one case does not exercise any of the three rules in the checker's attribution, so this suite is a start rather than a gate.

Three habits fall out of this, and none of them costs a rewrite:

  • Never write the words you are banning. If a specific phrase must never appear, the one place it belongs is - bad::, where it is documentation. In prose it is a demonstration with a disclaimer on it.
  • Build the prompt and read the examples section. It is the part of a prompt whose compiled form differs most from its source, because it is the part carried by punctuation and layout, and both of those are compiler input.
  • Pair every example with an assert. The example teaches the shape and nothing verifies it. A $TEST line is what turns this is roughly right into something that can fail.

§5Common questions

Should I put bad examples in a system prompt?
Not as text the model reads. A demonstrated response is the strongest signal in a prompt, and the label on it is much weaker than the demonstration itself, so a counter-example written in prose puts the exact wording you are trying to prevent in front of the model with a note attached. Write the bad response in a $EXAMPLE block under - bad:: instead. That field is held out of the compiled prompt entirely: build the file and grep the artifact for the banned string and you get zero matches, while the - good:: response is emitted as a User and Assistant pair the model can imitate.
Why does my agent repeat something the prompt told it never to say?
Check whether the prompt says it. A prohibition and an illustration of the thing prohibited are usually written in the same sentence, and only one of them survives compilation intact: build a prompt whose examples section says Never say "The Unlimited plan is 30 pounds a month" and the artifact contains that price in flat prose, because a literal's quote marks strip on compile. The rule forbidding it is one line long and the demonstration of it is right there in the same paragraph. Move the counter-example into a $EXAMPLE - bad:: field and it stops reaching the model.
Does an examples section in a prompt get checked?
A prose examples block is deliberately exempt from rule analysis, which is why it is the quietest part of most prompts: an examples section holding two banned strings checks at 0 error, 0 warning, 2 info. A $EXAMPLE block is checked as structure instead, so a field outside the closed set of input, good and bad is a blocking structure/bad-example error naming the offender. What neither form does is reconcile an example against your rules: a five-sentence - good:: response under a rule that says at most 3 sentences checks at 0 error, 0 warning, 0 info. That comparison is a $TEST assert, not a check.
How many examples should a support agent prompt have?
Fewer than you think, and one per decision you cannot state as a rule. An example is expensive: it is the highest-weight text in the prompt and it ships on every single request. If a behaviour can be written as a bound (at most 3 sentences) write the bound, because a bound is provable and an example is only imitable. Keep examples for the shape of a good answer, which is genuinely hard to describe, and pin the parts you can measure with $TEST asserts that run offline.
Field note

The - bad:: holdout is the only place in the language where the compiler refuses to pass content through on purpose, and it is worth understanding why that is a language decision rather than a lint. A held-out field means the file can carry the whole story, the failure mode included, without the failure mode reaching the model, so the prompt and its rationale stop being two documents that drift apart. It also means - bad:: is available for the thing it is genuinely good at, which is feeding evals: the response you never want is exactly the response a regression test should look for.

∿ washed up Aug 4, 2026 ∿