Your quote marks never reach the model
Compliance sends one sentence the voice agent must say on every call, exactly as written. You put it in quotes, because that is what quotes are for. In a .tg file they are for something else, and the build takes them out.
TL;DR TypeGlish strips straight double quotes on build, so a recording notice written as The recording notice is "..." reaches the model as unmarked prose, run into the next sentence; put the approved wording in a <"notice"> literal zone, point the rule at the tag, and pin the exact sentence with a contains test.
Larkfield Water is moving its billing line to a voice assistant called Ivy. Compliance has one hard requirement: every call opens with the recording notice, in the wording legal signed off. Someone pastes it into the prompt in quotation marks, adds a rule that Ivy says it when a call starts, and runs the checker. A clean file, an A. Nothing about that is wrong, and the agent still has no way to know where the approved sentence ends.
§1The notice in quotes
# Role @@ role: one sentence names the speaker, so every later "you" is the agent You are Ivy, the voice assistant for Larkfield Water. # Context @@ notice_text: compliance approved this wording, and it is quoted so it reads as one line The recording notice is "Larkfield Water records this call for training and quality." @@ hours: the line the agent reads when a caller asks about opening times The billing team answers calls from 8am to 6pm on weekdays. # Constraints @@ notice_first: the notice opens every call - WHEN a call starts THEN MUST say the recording notice.
$ typeglish check notice.tg ✓ 1 file — 0 error, 0 warning, 0 info $ typeglish score notice.tg notice.tg — A (99/100) proven errors: none tiers: base+z3 planes runtime 99 (what the model reads) · hygiene 100 (source only) facets enforceability 100 x.21 · hardness 100 x.12 · directness 90 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 100 x.12 (hygiene) · style 100 x.08 · security 100 x.08 rules 1 of 1 rule-shaped line read $ typeglish build notice.tg ✓ built .typeglish/dist/notice.txt ← notice.tg (50bf5d0dc2ce, full) $ cat .typeglish/dist/notice.txt # Role You are Ivy, the voice assistant for Larkfield Water. # Context The recording notice is Larkfield Water records this call for training and quality. The billing team answers calls from 8am to 6pm on weekdays. # Constraints - WHEN a call starts THEN MUST say the recording notice.
Nothing here is a defect the checker should have flagged. The source is correct, the rule is provable, and the build did exactly what the language says it does. The problem lives in the gap between what the quotes meant to the person who typed them and what they mean to the compiler.
§2Quotes are for the compiler
In English prose, quotation marks say these exact words. In a .tg file, a pair of straight double quotes is an escape hatch. The language reference calls it a literal string: nothing inside is parsed as an operator or a declaration, and the quotes strip on compile. It exists so you can write "You must be joking," the customer said in an example without the checker reading a MUST.
That is a good feature for its job, and its job is protecting the source from the compiler. It says nothing to the model. Once the build removes the marks, the model gets the words with no boundary, and whether it treats them as a script, a paraphrase target, or a description of a policy depends on how it reads a sentence that begins The recording notice is Larkfield Water records.
A quote mark in the source is a message to the compiler. If the model needs to see the edges of the text, the edges have to survive the build.
This is a common shape for prompt bugs: a mark that means something to one reader and something else to the other. Your prompt has a second reader covers the general case. Here the stakes are specific, because a recording notice that comes out shortened, reordered, or merged into a greeting is a compliance finding, not a tone issue.
§3A wall the build keeps
TypeGlish has one construct whose body is guaranteed to reach the model byte for byte: the literal zone. Wrap the text in a quoted tag pair, <"notice"> and </"notice">, and the interior is exempt from rule analysis and emitted verbatim inside a bare <notice> tag. The quotes still strip. This time they strip off the tag name, and the tag is the boundary the model reads.
Drop the zone into the heading-structured file and the checker has something to say first:
# Role @@ role: one sentence names the speaker, so every later "you" is the agent You are Ivy, the voice assistant for Larkfield Water. <"notice"> Larkfield Water records this call for training and quality. </"notice"> # Context @@ hours: the line the agent reads when a caller asks about opening times The billing team answers calls from 8am to 6pm on weekdays. # Constraints @@ notice_first: the notice opens every call, in the words compliance approved - WHEN a call starts THEN MUST quote the notice.
$ typeglish check notice-zone.tg notice-zone.tg:1:1 warn structure/heading-outside-section This heading sits outside every <tag> section. One XML section anywhere makes XML the file's structure model, and every # heading must then live INSIDE a tag. Two fixes: move this heading (and its body) into a section, or make it a section itself (<name> ... </name>). (<$CONFIG> is control-plane — it neither triggers this nor counts as a home.) notice-zone.tg:9:1 warn structure/heading-outside-section This heading sits outside every <tag> section. One XML section anywhere makes XML the file's structure model, and every # heading must then live INSIDE a tag. Two fixes: move this heading (and its body) into a section, or make it a section itself (<name> ... </name>). (<$CONFIG> is control-plane — it neither triggers this nor counts as a home.) notice-zone.tg:13:1 warn structure/heading-outside-section This heading sits outside every <tag> section. One XML section anywhere makes XML the file's structure model, and every # heading must then live INSIDE a tag. Two fixes: move this heading (and its body) into a section, or make it a section itself (<name> ... </name>). (<$CONFIG> is control-plane — it neither triggers this nor counts as a home.) notice-zone.tg:15:1 info prompt/unintroduced-definite "the notice" retrieves something this document never introduces - a model must guess which notice is meant. Mention a notice on an earlier line, or anchor it here ("their notice", "the notice you ..."). ✓ 1 file — 0 error, 3 warning, 1 info
The fix is the one the warning names: make each heading a section. The mechanics are covered in how to structure an agent prompt with XML tags; for Ivy it is three tag pairs and two-space indents. While we are in there, the rule gets a verb that means the thing compliance asked for. Quote and summarize are both in the checker's catalog, so quote the notice and never summarize the notice are rules it can read and prove, where read it word for word would be a verb the checker does not know.
<role> @@ role: one sentence names the speaker, so every later "you" is the agent You are Ivy, the voice assistant for Larkfield Water. </role> <"notice"> Larkfield Water records this call for training and quality. </"notice"> <context> @@ hours: the line the agent reads when a caller asks about opening times The billing team answers calls from 8am to 6pm on weekdays. </context> <constraints> @@ notice_first: the notice opens every call, in the words compliance approved - WHEN a call starts THEN MUST quote the notice. @@ notice_whole: a shortened notice is a missing notice - NEVER summarize the notice. </constraints> $TEST opening - input:: Hello? - expect:: - contains "Larkfield Water records this call for training and quality." - quotes the notice - does not summarize the notice
§4Pin the words with a test
The test at the bottom does two different jobs. The two prose expectations cover the two rules by sharing their wording, which is how test --dry counts coverage. The first expectation is not prose at all. contains "..." is a deterministic assert: on a live run it passes only if those exact characters appear in the reply. A judge can be talked into close enough. A string comparison cannot. That trade is the subject of deterministic asserts vs. an LLM judge, and a regulated sentence is the clearest case for the assert side.
$ typeglish check notice-walled.tg ✓ 1 file — 0 error, 0 warning, 0 info $ typeglish score notice-walled.tg notice-walled.tg — A (99/100) proven errors: none tiers: base+z3 planes runtime 99 (what the model reads) · hygiene 100 (source only) facets enforceability 100 x.21 · hardness 100 x.12 · directness 92 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 100 x.12 (hygiene) · style 100 x.08 · security 100 x.08 rules 2 of 2 rule-shaped lines read · 2/2 covered by $TEST $ typeglish test notice-walled.tg --dry ✓ notice-walled.tg coverage: 2/2 rules exercised · opening — "Hello?" (not run) ✓ contains "Larkfield Water records this call for training and quality." · rubric — 2 prose expectations, judged on a live run (needs ANTHROPIC_API_KEY) ✓ 1 prompt — 0 failed $ typeglish build notice-walled.tg ✓ built .typeglish/dist/notice-walled.txt ← notice-walled.tg (7888879db4f8, full) $ cat .typeglish/dist/notice-walled.txt <role> You are Ivy, the voice assistant for Larkfield Water. </role> <notice> Larkfield Water records this call for training and quality. </notice> <context> The billing team answers calls from 8am to 6pm on weekdays. </context> <constraints> - WHEN a call starts THEN MUST quote the notice. - NEVER summarize the notice. </constraints>
One honest limit on that dry run: the tick next to contains means the assert is well formed, not that it passed. --dry never calls a model, so there is no reply to compare yet. The comparison happens on the first live run, and on every live run after that. That is the point of writing it as an assert rather than as a sentence: once the reply exists, nobody has to judge it.
§5Which lines get a wall
Not much of a support prompt should be verbatim. A greeting the model reads off a script sounds like a script, and a wall around every phrase turns the prompt back into an IVR tree. The test is ownership: does someone outside the prompt own this exact wording, and would a changed word fail an audit?
- Wall it. The call-recording notice. A regulated disclosure legal approved line by line. A consent or cancellation statement read back for the record. The AI disclosure, if your jurisdiction or your legal team fixed its wording; how to write the AI disclosure rule covers when it should fire.
- Rule it. Greetings, sign-offs, apologies, hold messages. These need an intent and maybe a bound, and the model should be free to phrase them for the conversation in front of it.
- Quote it in the source only. A phrase that contains an operator word, like a customer saying you must be kidding in an example. That is exactly what straight quotes are for, and losing them in the artifact costs nothing.
The rule of thumb fits on one line. Quotes protect the source. Tags protect the text. If the model needs to know where the words start and stop, give them a tag the build will keep.
§6Common questions
- How do I make my AI voice agent read a disclosure word for word?
- Give the wording its own literal zone and point the rule at it. In TypeGlish, a
<"notice"> ... </"notice">block reaches the model byte-identical inside a bare<notice>tag, so the start and end of the approved text are visible. Then write the rule against the tag,WHEN a call starts THEN MUST quote the notice, addNEVER summarize the notice, and pin the exact sentence with a$TESTthat assertscontainson the wording. - Why did the quotation marks disappear from my compiled prompt?
- Because in a .tg file a pair of straight double quotes is an instruction to the compiler, not to the model. The language reference calls it a literal string: nothing inside is parsed as an operator, and the quotes strip on compile. That protects words like must or never inside a quoted phrase, but it also removes the only marker that told the model where a verbatim script began and ended.
- Can a test check that my agent said the exact wording?
- Yes. A
$TESTexpectation written ascontainsfollowed by the quoted sentence is a deterministic assert rather than a judged rubric line, so a live run passes only when those characters appear in the reply.typeglish test --dryvalidates the suite offline and reports rule coverage, but it does not call a model, so the comparison itself happens on a live run. - Which parts of a support agent prompt need to be verbatim?
- Only the lines someone outside the prompt owns word for word: a call-recording notice, a regulated disclosure your legal team approved, a cancellation or consent statement that is read back for the record. Greetings, sign-offs and apology lines usually need the intent, not the bytes, and are better as ordinary rules the model can phrase naturally. If nobody would fail an audit over a changed word, it does not need a wall.
The quick audit is a diff, not a read. Build the prompt, open the artifact next to the source, and search the source for every straight double quote. Each hit is either a phrase that only needed protecting from the compiler, which is fine, or a script somebody expected the model to treat as fixed text, which is now a sentence with no edges. Move the second kind into a <"name"> zone and give each one a contains case. The artifact is the only thing the model ever reads, which is why typeglish build writes it to disk where you can look at it.