Teardown: your macro library speaks in the first person
Four approved replies out of the help desk, pasted under four rules that already work. The checker finds one thing to say about them, and it is not the macro that promises a callback three days after the rule above it promised one in two hours.
TL;DR A pasted macro library is invisible to the checker because rules are indexed by their subject and a macro is written in the first person: the identical clause is 2 blocking logic/time-strength errors behind You and 0 error, 0 warning, 0 info behind I, We or the agent's own name. Meanwhile I am sorry about the wait. is a declaration that binds the name i, and the whole block reflows into one paragraph in the artifact. Keep the wording in the help desk and move the commitment it carries into a rule plus a $TEST assert.
The macro library is the last document in the contact centre to get pasted into the prompt, and the one with the best paperwork. Every entry was written by somebody in the CX team, reviewed by brand, checked by legal in the cases that needed it, and then tested against thousands of real conversations by human agents who complained when the wording was wrong. It is, by a distance, the most quality-controlled text the org owns. So when the support agent starts writing replies that sound nothing like the brand, pasting the top of the macro library under the rules is not a shortcut. It is the obvious move, and everybody in the room agrees with it.
§1The paste
Ellis answers first-notification-of-loss chat for Thornbury, a home insurance provider. Four rules, then four macros, in the order somebody actually pasted them.
# Role You are Ellis, a customer support agent for Thornbury, a home insurance provider. # Constraints - You MUST ask for a policy number before you discuss a claim. - You MUST call a customer back within 2 hours. - You MUST keep every reply to at most 3 sentences. - WHEN a customer reports a flood THEN you MUST escalate the claim to a human agent. # Macros Use the approved wording below wherever it fits. Thanks for getting in touch. I have logged your claim and I call a customer back within 3 days. I am sorry about the wait. I refund the excess as a goodwill gesture whenever the delay was ours. I will pass this straight to our claims team. I am not able to discuss a policy until I have the policy number, so I ask for that first.
$ typeglish check thornbury.tg thornbury.tg:13:30 info prompt/declared-later `i` is declared on line 11, after its first use here: declare the identity before the rules that use it (the model reads top-down). ✓ 1 file — 0 error, 0 warning, 1 info $ typeglish score thornbury.tg thornbury.tg — B (85/100) proven errors: none tiers: base+z3 planes runtime 97 (what the model reads) · hygiene 50 (source only) facets enforceability 94 x.21 · hardness 100 x.12 · directness 95 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 0 x.12 (hygiene) · style 93 x.08 · security 100 x.08
consistency 100 is the facet that asks what the rules do to each other, and it has looked at the four macros and found nothing to compare.Enforceability 94 is the number that flatters this file. The four rules really are well formed, so the facet that measures whether rules parse proof-grade is nearly perfect, and the four lines underneath them contribute nothing to it in either direction. This is the same shape as a pasted new-hire handbook: the grammar arrives correct and the addressee arrives wrong. The difference is that a handbook is at least written in the imperative. A macro is written by the agent, to the customer, about itself.
§2One word decides whether a line is a rule
Take the disagreement that matters and reduce it to one clause. The rule promises a callback within two hours. The macro promises one within three days. Four files, identical except for the subject of the last line.
$ tail -1 s_you.tg You call a customer back within 3 days. $ typeglish check s_you.tg s_you.tg:5:1 error logic/time-strength Conflicts with line 6. One bound per slot — "within 2 hours" already entails "within 3 days"; the looser rule on "you call a customer · back" is dead weight. Keep one. s_you.tg:8:1 error logic/time-strength Conflicts with line 4. One bound per slot — "within 2 hours" already entails "within 3 days"; the looser rule on "you call a customer · back" is dead weight. Keep one. ✗ 1 file — 2 error, 0 warning, 0 info $ tail -1 s_i.tg I call a customer back within 3 days. $ typeglish check s_i.tg ✓ 1 file — 0 error, 0 warning, 0 info $ tail -1 s_we.tg We call a customer back within 3 days. $ typeglish check s_we.tg ✓ 1 file — 0 error, 0 warning, 0 info $ tail -1 s_ellis.tg Ellis calls a customer back within 3 days. $ typeglish check s_ellis.tg ✓ 1 file — 0 error, 0 warning, 0 info
Read the slot name in the error message, because the compiler prints its own index: the looser rule on "you call a customer · back". The subject is inside the key. A bound is not attached to an action, it is attached to an action performed by a party, and the party the deontic layer resolves is the addressee that the role line registered. You are Ellis registers exactly one of those. I is not it, We is not it, and neither, in this pairing, is Ellis.
A macro library is a set of rules written about a person who does not exist in the file.
That last case is worth sitting with, because it is the one that catches experienced authors out. The prompt has just declared that you are Ellis, so a line about Ellis feels like a line about you, and in the artifact the model reads it will behave like one. In the checker it does not, here, resolve onto the same slot as the rules above it. This is the same subject-indexing behaviour that lets two flat contradictions sit in one file at consistency 100 when half the rules say you and half say the agent: the difference is that in a macro library, every single line is on the wrong side of it, and not by accident. First person is what a macro is for.
§3The one line the checker did read
Back to the single finding on the real file: `i` is declared on line 11. Nobody wrote a declaration. Line 11 in the blank-stripped scheme is the second macro, and it opens with the four most common words in customer support.
# Role You are Ellis, a customer support agent for Thornbury. # Macros I am sorry about the wait. # Constraints - You MUST tell a customer that you are @{i}.
$ typeglish check iref.tg iref.tg:5:1 warn prompt/duplicated-declaration The model reads this value twice — this declaration renders AND @{i} expands it (line 6). If the repetition is deliberate emphasis, keep it; otherwise reword this line or the reference so the value appears once. ✓ 1 file — 0 error, 1 warning, 0 info $ typeglish build iref.tg ✓ built .typeglish/dist/iref.txt ← iref.tg (8dc9b489048b, full) $ tail -1 .typeglish/dist/iref.txt - You MUST tell a customer that you are sorry about the wait.
I am sorry about the wait. bound the name i to the value sorry about the wait, and a rule three lines down expanded it.There is no special case here and no bug. IS, ARE and AM are one operator with three surface forms, and a declaration binds a name to a value and renders at the same time. Persona IS a support agent and I am sorry about the wait are the same statement type. So the single most-used sentence in any macro library is a binding, and in a file with twenty macros the last I am line wins the name. The checker's only complaint about the four-macro paste is not that the callback promise is wrong; it is that the agent's identity was declared underneath the rules that use it.
The corollary is the useful part. Discourse findings do not care about the subject: a definite article is a lookup instruction wherever it appears, so unintroduced definites fire inside macros exactly as they fire inside rules. That is what makes the paste so convincing. Run the checker on a real macro library and you will get findings, on macro lines, with line numbers pointing at macros. It looks like the block was read. What was read is the prose plane; the rule plane skipped every line.
§4What the model is actually handed
None of the above has cost anything yet, because the file still says roughly the right things. The build is where the shape of the paste stops being the shape of the deliverable.
$ typeglish build thornbury.tg
✓ built .typeglish/dist/thornbury.txt ← thornbury.tg (ba34d1ddbe1a, full)
$ tail -3 .typeglish/dist/thornbury.txt
Use the approved wording below wherever it fits.
Thanks for getting in touch. I have logged your claim and I call a customer back within 3 days. I am sorry about the wait. I refund the excess as a goodwill gesture whenever the delay was ours. I will pass this straight to our claims team. I am not able to discuss a policy until I have the policy number, so I ask for that first.
Two things are now true of the deployed prompt at once. It contains a sentence promising a callback in three days, sitting six lines under a rule requiring one in two hours, with no boundary marking either as a quotation. And it contains a sentence offering to refund an excess, which is a payment the prompt nowhere authorises and which somebody would have argued about for a week if it had been proposed as a rule. Neither was proposed as a rule. They arrived as wording, and wording is not reviewed like policy.
This is the same trap as a bad example that reads as a good one, with the polarity flipped. There, a fluent in-domain reply sitting in an examples block gets imitated. Here, a fluent in-domain reply sitting anywhere at all gets imitated, because the file never had a way to say this is a quotation, not an instruction. If wording genuinely has to reach the model byte for byte, that is what a literal zone is for. In practice almost none of a macro library needs to.
§5The rewrite: a macro is an assertion
Every macro in the library exists because somebody decided a reply must carry something: a commitment, a refusal, a number, a boundary. That decision is policy and it belongs in the rules. The sentence that carries it is copy, and it belongs where copy is reviewed. The two have been living in one artifact because the help desk stores them that way, not because a prompt needs them together.
So the rewrite deletes the macro block and asks, of each entry, what it was actually asserting. The callback macro was asserting a deadline that is two revisions out of date. The excess macro was asserting an authority nobody granted, so it becomes the prohibition it should always have been. The claims-team macro was asserting a route. The policy-number macro was restating a rule that already existed.
<$CONFIG> $IMPORT route claims_team </$CONFIG> # Role @@ role: first notification of loss for Thornbury home insurance You are Ellis, a customer support agent for Thornbury, a home insurance provider. # Constraints @@ identity before claim data - You MUST ask for a policy number before you discuss a claim. @@ CX-204: the callback promise moved to 2 hours in April; the macro still said 3 days - You MUST call a customer back within 2 hours. @@ 3 sentences is what the chat widget shows without a scroll - You MUST keep every reply to at most 3 sentences. @@ an excess is a policy figure, never a goodwill decision - You MUST NOT waive an excess. @@ a flood is a surge event and belongs to the claims desk - WHEN a customer reports a flood THEN you MUST escalate the claim to claims_team. $TEST callback_promise - input:: My kitchen flooded last night. When will someone call me? - expect:: - contains "2 hours" - at most 3 sentences $TEST excess_request - input:: Can you waive my excess? The delay was your fault. - expect:: - The reply declines to waive the excess and does not offer one.
$ typeglish check ellis.tg ✓ 1 file — 0 error, 0 warning, 0 info $ typeglish score ellis.tg ellis.tg — A (97/100) proven errors: none tiers: base+z3 planes runtime 96 (what the model reads) · hygiene 100 (source only) facets enforceability 88 x.21 · hardness 100 x.12 · directness 98 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 100 x.12 (hygiene) · style 100 x.08 · security 100 x.08 $ typeglish test ellis.tg --dry ✓ ellis.tg coverage: 3/5 rules exercised · callback_promise — "My kitchen flooded last night. When will someone call me?" (not run) ✓ contains "2 hours" ✓ at most 3 sentences · excess_request — "Can you waive my excess? The delay was your fault." (not run) ✓ rubric 0.00 — not run (--dry) ✓ 1 prompt — 0 failed $ typeglish build ellis.tg ✓ built .typeglish/dist/ellis.txt ← ellis.tg (e93160c09d8a, full)
The two asserts are the part worth copying. contains "2 hours" is the callback macro, restated as the only thing about it that was ever load-bearing, and it now lives beside the rule it depends on: change the rule to four hours and the case fails. at most 3 sentences is the brevity rule, exercised. Neither is a template, so neither can drift out of date without something going red, and the artifact is smaller for it.
One honest limit ships with the approach, and it is the same one the examples rewrite ran into: nothing reconciles an expectation against a rule. You can write contains "3 days" under a prompt that promises two hours and the suite will validate, because --dry checks that the suite is well formed and reports coverage, not that it agrees with the file. Coverage is the number to watch: 3/5 rules exercised says two rules have nothing asserting anything about them, and the two are the policy-number gate and the flood escalation, which is a fair description of where this prompt's remaining risk is.
§6Common questions
- Should I paste my canned responses into my AI agent's system prompt?
- No. A canned response is output and a system prompt is instruction, and pasting one into the other converts approved copy into unapproved policy. It is also unchecked policy: a macro is written in the first person, rules are indexed by their subject, and
Iis not a subject the checker resolves to the agent. In a four-macro paste the checker returns0 error, 0 warning, 1 infoat B (85/100) withconsistency 100, and the identical clause that is 2 blockinglogic/time-strengtherrors when it begins withYouis0 error, 0 warning, 0 infowhen it begins withI. Keep the wording in your help desk and put the behaviour it encodes in the prompt as a rule with a bound. - Why does typeglish check ignore lines that start with I?
- Because a rule is indexed by who it is about, and the deontic layer resolves
youto the addressee registered by the role line. The slot key printed in the diagnostic says so out loud: it readsyou call a customer, with the subject inside the key. A line beginningI,We,The agent, or even the agent's own persona name lands on a different key, so it is never compared with the rules above it. It still reaches the model, which is the whole problem: the model reads a first-person sentence about the agent as an instruction, and the compiler reads it as a statement about somebody else. - Is I am sorry about the wait a rule or a fact?
- Neither: it is a declaration.
Isandamare the same copula operator, soI am sorry about the waitbinds the nameito the valuesorry about the wait, exactly asPersona IS a support agentbindspersona. You can prove it by pointing at it: a rule readingYou MUST tell a customer that you are @{i}compiles, draws aprompt/duplicated-declarationwarning because the value now reaches the model twice, and builds to an artifact whose last line reads you are sorry about the wait. The most common sentence in a macro library is silently registering the agent's identity. - Where should approved wording live if not in the prompt?
- In the test suite, as an assertion about the reply rather than a template for it. A macro exists because somebody decided a reply must contain a particular commitment, and that decision is expressible as a
$TESTwith a deterministic assert:contains "2 hours"plusat most 3 sentences. The rules then say what the agent must do and the tests say what the wording must carry, which is the split the macro was collapsing. A five-rule prompt with two such cases checks at0 error, 0 warning, 0 info, scores A (97/100), and reportscoverage: 3/5 rules exercised. - Does a macro library change what my agent actually receives?
- Yes, and not in the shape you pasted. A prose body reflows on compile, so four macros under a
# Macrosheading arrive at the model as one run-on paragraph of first-person text with no boundary between one approved reply and the next. Building the file writes that artifact at0 errorand exit 0, because nothing in it is wrong: the compiler was never told those lines were quotations. If wording genuinely has to ship verbatim, it belongs in a literal zone, and if it only has to be honoured, it belongs in a$TEST.
The reflex this teardown should install is a grep, not a rule. Before you read a single finding on a prompt somebody else assembled, count the lines whose first word is I or We. Every one of them is text the model will treat as an instruction and the compiler will treat as a remark about a third party, and in a file assembled out of help-desk exports that count is frequently larger than the number of actual rules. It is a cheap gate and nothing in the toolchain will do it for you, because there is nothing wrong with the lines: a prompt is allowed to contain prose, and the exemption that lets a Context block hold free English is the same exemption that swallows a macro library whole. The number to distrust here is consistency 100. It does not mean the file agrees with itself. It means everything in the file that could disagree was written in the same grammatical person, and the four sentences that contradict the rules were not.