← Tidelines/Teardowns

Teardown: the system prompt an AI wrote for you

Ask a model for an agent system prompt and it gives you something fluent, tidy, and instantly recognisable. Run it through a checker and you get twelve warnings, none of which is the bug. The bug is underneath, and the typography is what was hiding it.

by TypeGlish team7 min read#teardowns
Twelve warnings. None was the bug.

TL;DR A machine-drafted agent prompt draws its first twelve findings on its own typography (em-dash, emoji, decorative bullets, a math symbol) and those warnings are not cosmetic: they keep the checker from reaching the logic. Sweep the character plane, commit the hedges to real modals, and the actual defect surfaces as logic/numeric - two length rules that cannot both hold, present since the first draft. Rebuilt properly, with the exception carved out of the cap, it goes from D (50/100) to A (92/100).

Updated for typeglish 0.10.0

The first version of this post rebuilt the file with a flat cap, MUST keep every reply to at most 3 sentences, beside WHEN you explain the return policy THEN give at least 5 sentences of detail, and called the pair scoped. That was wrong. A flat rule applies to every reply, the policy explanation included, so the two bounds still met there. typeglish 0.9.0 let it through because a bare imperative after THEN, with no modal, was not read as a rule at all; 0.10.0 reads it, proves WHEN and UNLESS guards, and reports the conflict the file always had. The rebuild in §5 now carves the exception out of the cap with UNLESS, and every figure on this page has been re-run against 0.10.0. One more change shows in those figures: 0.10.0 files the character-plane codes as warnings, not errors, so the twelve errors of the first version are twelve warnings here, and the grades have moved with them.

Generated prompts have a house style. Once you have seen a few you can spot one from across the room: the em-dash, the "highly capable and empathetic" preamble, the bullet list with a star emoji, the hedge in every third clause. The interesting thing is not that the style is recognisable. It is that the style is load-bearing in the worst way: every one of those tics costs the checker a tier of analysis, so the fluent draft is also the one whose real defects you cannot see. Below is a returns-agent prompt in that house style. If you have ever asked a model to draft one, you will recognise every line.

§1The prompt, as it arrives

returns.tg - as generated⚠ 12 warnings · D (50/100)
# Role
You are a highly capable, empathetic AI assistant for Acme — a leading provider of home coffee equipment. Your goal is to delight every customer!

# Guidelines
• Always strive to be as helpful as possible 🌟
• Keep responses ≤ 3 sentences where possible
• When explaining the return policy, provide at least 5 sentences of detail so the customer feels fully informed
• If the customer’s request seems complex, you should probably escalate to a human
Eight lines. Every mark in it is a default a generating model reaches for, not an accident: the spliced dash, the decorative bullet, the star, the math symbol, the curly apostrophe, and a hedge on every rule.

Before reading the diagnostics, try the exercise a reviewer would do: find the behavioural defect. It is in there, in plain English, and almost nobody finds it on the first pass. We will come back to that.

§2Twelve warnings, and not one of them is the bug

tg check - output (one line per distinct message)
returns.tg:2:59  warn   structure/em-dash  A typographic dash hides its break from the ASCII parsers, and models mirror the tic (the canonical AI-tell). Its plain form is the spaced hyphen: " - ".
returns.tg:5:46  warn   structure/special-character  Emoji is outside the language: the instruction plane is checkable English, and models mirror prompt decoration into output. Remove it, or demonstrate it inside <examples>.
returns.tg:5:1  warn   structure/special-character  Not a list bullet the parser reads: use "- ".
returns.tg:5:1  warn   structure/missing-period  Unterminated statement — end it with a period (or ! ?; a lead-in may end with ":"). Statement boundaries are a compile contract.
returns.tg:5:10  info   prompt/hedging  Hedging — "strive to" turns this instruction into a suggestion the model may skip. Delete the hedge, or commit to a modal (MUST / NEVER / SHOULD).
returns.tg:6:18  warn   structure/special-character  The numeric tier parses bounds written in English: '≤ 3' lowers with no bound while 'at most 3' is Z3-provable. Write it out.
returns.tg:7:1  info   prompt/unintroduced-definite  "the return policy" retrieves something this document never introduces - a model must guess which policy is meant. Mention a policy on an earlier line, or anchor it here ("their policy", "the policy you ...").
returns.tg:8:18  warn   structure/curly-quote  A curly apostrophe is a lookalike: the parsers read ASCII ', so possession and contractions written with it are invisible to the consistency checker. Straighten it.

✓ 1 file — 0 error, 12 warning, 4 info
Trimmed to one line per distinct message: each of the four bullets also carries its own Not a list bullet and Unterminated statement warning, which is how eight lines produce twelve. Every single warning is about characters and line shape. Not one is about what the agent will do.

Now the part that should bother you. Run the score and look at the consistency facet.

tg score - as generated (ledger rows trimmed)
returns.tg — D (50/100)  proven errors: none  tiers: base+z3
  planes  runtime 73 (what the model reads) · hygiene 0 (source only)
  facets  hardness 32 x.16 · directness 85 x.11 · consistency 100 x.21 · structure 0 x.16 (hygiene) · annotation 0 x.16 (hygiene) · style 40 x.11 · security 100 x.11
  lever   structure 0/100 (up to +16 overall) — 12 findings below (L2 L5 L6 L7 L8) — Fix the ledger rows with structure/ codes; most carry a one-click fix.
consistency 100. The checker is telling you, honestly, that it found no conflict between any two rules in this file. It is also wrong, and it is wrong because of the structure score sitting at zero next to it. Notice what else is missing from the facets line: no enforceability facet, and no rules table below it, because not one line in this file is a rule the checker could read.

This is the mechanism worth taking away from the whole teardown. The checker works in tiers, and the early ones gate the later ones. A line the rule reader cannot resolve into a rule never reaches the solver, so a conflict inside that line is not missed, it is unreachable. None of the four bullets here is a rule to the checker: • is not a list marker its parser reads, so the rule reader never looks at them, and the one bound in the file is a glyph the numeric tier cannot lower. There is nothing for the prover to prove.

The typography is not the defect. It is the reason you cannot see the defect.

§3Pass one: sweep the character plane

So we do the boring pass first, and only the boring pass: plain hyphen for the dash, - for the bullets, straight apostrophe, the emoji gone, and ≤ 3 written out as at most 3. Not one word of policy changes.

returns.tg - ASCII swept, nothing else touched⚠ advisory · D (55/100)
# Role
You are a highly capable, empathetic AI assistant for Acme, a leading provider of home coffee equipment. Your goal is to delight every customer!

# Guidelines
- Always strive to be as helpful as possible.
- Keep responses at most 3 sentences where possible.
- When explaining the return policy, provide at least 5 sentences of detail so the customer feels fully informed.
- If the customer's request seems complex, you should probably escalate to a human.
Twelve warnings to none. The overall grade moves only five points, from 50 to 55, and the runtime plane, the part of the score that reads what the model reads, goes down, from 73 to 62. That is the correct outcome: the file is no longer failing on hygiene, so the score stops being dominated by it and starts judging the rules. There were never any good rules.
tg check - after the sweep
returns.tg:5:10  info   prompt/hedging  Hedging — "strive to" turns this instruction into a suggestion the model may skip. Delete the hedge, or commit to a modal (MUST / NEVER / SHOULD).
returns.tg:6:38  info   prompt/hedging  Hedging — "where possible" turns this instruction into a suggestion the model may skip. Delete the hedge, or commit to a modal (MUST / NEVER / SHOULD).
returns.tg:6:1  info   prompt/unparsed-rule  Reads like a rule, but the rule reader cannot parse it as one, so it earns no Enforceability credit and no measurability check: no modal (MUST / NEVER / SHOULD), and the checker knows "keep" but cannot prove this shape: the object runs past six words, so it reads as prose. Fix: lead with a modal (MUST …) to make it a rule, and name the object in fewer words, or split the rule to make it provable.
returns.tg:7:1  info   typeglish/if-then  WHEN needs a THEN — write WHEN <condition> THEN <action>.
returns.tg:7:1  info   prompt/unparsed-rule  Reads like a rule, but the rule reader cannot parse it as one, so it earns no Enforceability credit and no measurability check: the condition has no THEN branch, so nothing is required when it holds. Fix: add THEN MUST / NEVER / SHOULD <verb> ….
returns.tg:7:1  info   prompt/unintroduced-definite  "the return policy" retrieves something this document never introduces - a model must guess which policy is meant. Mention a policy on an earlier line, or anchor it here ("their policy", "the policy you ...").
returns.tg:8:48  info   prompt/hedging  Hedging — "should probably" turns this instruction into a suggestion the model may skip. Delete the hedge, or commit to a modal (MUST / NEVER / SHOULD).
returns.tg:8:1  info   typeglish/if-then  IF needs a THEN — write IF <condition> THEN <action>.
returns.tg:8:1  info   prompt/unparsed-rule  Reads like a rule, but the rule reader cannot parse it as one, so it earns no Enforceability credit and no measurability check: the condition has no THEN branch, so nothing is required when it holds. Fix: add THEN MUST / NEVER / SHOULD <verb> ….

✓ 1 file — 0 error, 0 warning, 9 info
A second layer, invisible until the first was cleared. Three hedges, two conditionals that open a condition and then never say what to do about it, and prompt/unparsed-rule saying the same thing from the other side: three of the four lines read like rules and parse as prose. Consistency is still reported at 100, because these lines still are not rules.

§4Pass two: make them rules, and meet the bug

The remaining notes all ask for the same thing: commit. A hedged line is not an instruction, and When explaining the return policy, provide... is a condition with no consequent. So we do what the diagnostics say. Every rule gets a modal, and the sentences say plainly what the draft was gesturing at.

returns.tg - hedges committed to modals✗ logic/numeric
# Role
You are a support agent for Acme, a home coffee equipment retailer.

# Guidelines
- MUST keep every reply to at most 3 sentences.
- MUST give at least 5 sentences of detail when you explain the return policy.
- WHEN the request needs an exception to policy THEN escalate to a human agent.
Shorter, stricter, and now failing. Nothing was added: these are the generated draft's own two length rules, finally written as rules.
tg check - output
returns.tg:5:1  error  logic/numeric  Conflicts with line 6. Numeric conflict — "at most 3 sentences" and "at least 5 sentences" can't both hold.
            ↳ line 6: - MUST give at least 5 sentences of detail when you explain the return policy.
returns.tg:6:1  error  logic/numeric  Conflicts with line 5. Numeric conflict — "at most 3 sentences" and "at least 5 sentences" can't both hold.
            ↳ line 5: - MUST keep every reply to at most 3 sentences.

✗ 1 file — 2 error, 0 warning, 0 info
There it is. "Keep responses short" and "explain the return policy in at least five sentences" were both in the first draft, three lines apart, and they cannot both hold. On every returns-policy question the model has been picking one, per conversation, with nothing in the prompt to say which.

Note what it took to surface a two-line contradiction: a character sweep and a modal pass. Nobody was hiding it. The solver simply could not reach it, because ≤ 3 lowers with no bound and the five-sentence line opened a condition with no THEN, so it was not yet a rule the prover reads. The numbers in a prompt are constraints a prover can hold, which is the argument in the arithmetic in your prompt, but only once they are written in a form it can read.

§5The rebuild

The conflict has two honest resolutions, and choosing between them is the decision the generated draft quietly declined to make. Either reconcile the band (pick one length for every reply), or admit that the policy explanation is a genuine exception and carve it out, so each bound only applies where it belongs. A trailing "when you explain the return policy" does not scope anything, it is prose hanging off an unconditional MUST. A real WHEN ... THEN is a rule the prover reads, and it is only half the job. The cap has to carry the complementary guard, UNLESS you explain the return policy, or it still applies inside the exception and the two bounds still meet there. The first version of this post left the cap flat and called the pair scoped; 0.10.0 proves it was not.

returns.tg - rebuilt✓ compiles · A (92/100)
# Role
@@ role: name the domain, so "helpful" has an edge
You are a support agent for Acme, a home coffee equipment retailer.

# Constraints
@@ brevity: three sentences is the length a chat reply actually gets read at; the UNLESS carves the one exception out of the cap
- UNLESS you explain the return policy THEN MUST keep every reply to at most 3 sentences.
@@ policy_detail: the one place length is worth spending; its guard is the complement of the cap's, so the two can never both apply
- WHEN you explain the return policy THEN MUST give at least 5 sentences of detail.
@@ order_number: the order number is what makes a return reconcilable
- MUST ask for the order number before starting a return.
@@ window: stating the window prevents the second contact
- MUST state the 30-day return window when a return is requested.
@@ escalation: an exception costs less as a handoff than as a wrong answer
- WHEN the request needs an exception to policy THEN escalate to a human agent.
@@ no_dismissal: dismissal is the complaint that reaches the review site
- NEVER dismiss a customer's complaint.

$TEST policy_length
  - input:: What is your return policy on a grinder I opened last week?
  - expect::
    - states the 30-day return window
    - at least 5 sentences
A (92/100): no errors, no warnings, three advisory notes. The two length rules coexist because both are scoped, and disjointly: UNLESS x is IF NOT x, so no reply satisfies both guards, and the prover can see that. The $TEST pins the expensive case: tg test --dry reports coverage: 3/6 rules exercised, so you also know exactly which rules are still riding on hope.
tg test --dry - output
✓ returns.tg  coverage: 3/6 rules exercised
  uncovered (no case shares the rule's wording):
    L11  ask for the order number before starting a return.
    L15  escalate to a human agent.
    L17  dismiss a customer's complaint.
  · policy_length — "What is your return policy on a grinder I opened last week?" (not run)
      ✓ at least 5 sentences
      · rubric — 1 prose expectation, judged on a live run (needs ANTHROPIC_API_KEY)
✓ 1 prompt — 0 failed
Three of the six rules share wording with the one case; the other three are listed by line. The prose expectation, states the 30-day return window, is a rubric: --dry parses it and holds it for a live run, it does not judge it.

Compare the two files as artifacts and the trade is plain. The generated draft has a persona paragraph, four friendly bullets, and a star. The rebuild has six rules, a note on each saying why it exists, and one test. It is not more beautiful. It is checkable, and the version that ships to customers should be the checkable one.

Field note

The character plane is not a style guide with delusions of grandeur: structure/em-dash, structure/curly-quote, and structure/special-character exist because each of those marks hides meaning from a tier that parses ASCII English, and every figure above is re-run against the real checker in CI on each commit. The same "clean it before you can read it" order shows up in the voice-agent teardown, where the decorative marks were being read aloud to customers. And the hedges in §3 are the first item in say what you mean, for the same reason: a rule the model may skip is not a rule.

FAQCommon questions

Is an AI-generated system prompt good enough to ship?
As a first draft, yes; as a spec, not without a pass. A generated prompt is fluent, which is the problem: it reads as finished. The draft in this teardown drew twelve warnings before anyone looked at its logic, and the real defect (two length rules that cannot both hold) only became provable after the typography was swept and the hedges were committed to modals. Treat the generated text as raw material and run it through a checker before it reaches a customer.
Why do em-dashes and emoji matter in a system prompt?
Two reasons, and only one of them is taste. The model mirrors prompt decoration into its output, so an em-dash in the prompt buys you em-dashes in customer replies. More importantly, the marks hide meaning from the tiers that parse English: a curly apostrophe means a possession is invisible to the consistency checker, and a written-out bound like at most 3 is provable by the solver while the math symbol version lowers with no bound at all. The typography is not the bug, it is what stops you seeing the bug.
How do I clean up a system prompt a model wrote for me?
Four passes, in this order. Sweep the character plane first, so the checker can read the file: plain hyphens, straight quotes, ASCII bullets, bounds written in English. Then commit every hedge to a modal, because try to and should probably are not rules. Then finish every conditional, so a line that starts with when or if actually says what to do. Only then are the remaining diagnostics about logic, and that is where the real conflicts surface. When one rule is an exception to another, carve it out: the default carries the complementary guard, so the two can never both apply.
Why did new errors appear after I fixed the ones the checker reported?
Because the checker works in tiers and the early ones gate the later ones. A line the rule reader cannot parse as a rule is never handed to the solver, so a numeric or logical conflict inside it is not missed, it is unreachable. Clean the structure, commit the modals, and those lines become rules, the rules reach the solver, and conflicts that were always in the file finally get proven. A rising error count after a cleanup pass is progress: the file is now legible enough to be wrong out loud.
∿ washed up Jul 25, 2026 ∿