← Tidelines/Deep dives

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.

by TypeGlish team6 min read#deep-dives
The words ship. The quotes do not.

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

notice.tg - the approved wording, in quotes✓ A (99/100)
# 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.
The notice is declared once, the rule points at it by name, and every line carries an annotation. This is a well-built file by any measure the checker has.
tg check, score and build - notice.tg✗ quotes gone in the artifact
$ 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.
Read the Context line the way the model does. The quotation marks are gone, and the two prose lines have flowed into one paragraph. Where does the recording notice end: after quality, or after weekdays? The sentence the agent must read word for word now has no edges.

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:

notice-zone.tg - a literal zone in a heading file✗ structure/heading-outside-section
# 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.
tg check - notice-zone.tg✗ 3 warning, 1 info
$ 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
One tagged section makes XML the file's structure model, so the three headings are now outside it. The info line is the same story from the other side: while the file is still organised by headings, the notice on line 15 does not resolve to anything.

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.

notice-walled.tg - the wording in a literal zone✓ A (99/100)
<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.

tg check, score, test --dry and build - notice-walled.tg✓ edges survive the build
$ 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>
The approved sentence now sits alone between two tags, and the rule names the tag. The hours line lives in its own section and can no longer run into the notice. The score is the same A as before; the difference is entirely in what the model reads.

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, add NEVER summarize the notice, and pin the exact sentence with a $TEST that asserts contains on 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 $TEST expectation written as contains followed 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 --dry validates 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.
Field note

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.

∿ washed up Oct 3, 2026 ∿