The person returning it did not buy it
Gift receipts go to the printer in November, and the policy for them lands in the support prompt as four lines under a new heading. It checks clean at a B. It also leaves a live rule telling the agent to read the order total to whoever asks, and the line meant to stop that never meets it.
TL;DR When you add gift returns to a support agent, let the host tell the agent whether the buyer or the recipient is on the chat, guard every rule that differs between them on that one input, send the recipient’s return to its own store-credit tool, and test the buyer who asks whether the gift came back.
Bramble Lane sells toys online, and its chat assistant, Juno, has handled returns all year for the people who bought the toys: a start_return tool refunds the card they paid with, and Juno reads back the order total so both sides know they are talking about the same order. This month the shop starts printing gift receipts. In January, the chat fills with people returning things they never paid for. Here are the eight checks for the pull request that gets Juno ready for them.
§1The four lines from the returns team
$TOOL start_return - description:: Start a return for an item in an order and refund the card the buyer paid with. - input:: - order_id:: string: the order the item came from. # Role You are Juno, the support assistant for Bramble Lane, an online toy shop. # Constraints - MUST keep every reply to at most 4 sentences. - WHEN a customer asks for a refund THEN MUST call @[start_return]. - MUST read back the order total. # Gift returns Gift recipients can return items with a gift receipt, for store credit. - NEVER read back the order total to a gift recipient. - ALWAYS refund gift returns as store credit. - The buyer should not be told that a gift was returned.
# Gift returns has been live since spring. The section below it is the returns team’s gift-receipt policy, pasted the week the receipts went to the printer.$ typeglish check before.tg before.tg:18: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" is not a verb the checker knows, so it reads as prose. Fix: lead with a modal, or start with a verb from the catalog (Escalate …, Ask …). before.tg:18:1 info prompt/unintroduced-definite "The buyer" retrieves something this document never introduces - a model must guess which buyer is meant. Mention a buyer on an earlier line, or anchor it here ("their buyer", "the buyer you ..."). before.tg:18:1 info prompt/agentless-passive The doer is deleted (agentless passive) - who performs this? Name the actor or use the imperative: "Verify the order." instead of "The order is verified." ✓ 1 file — 0 error, 0 warning, 3 info $ typeglish score before.tg before.tg — B (81/100) proven errors: none tiers: base+z3 planes runtime 91 (what the model reads) · hygiene 50 (source only) facets enforceability 86 x.21 · hardness 93 x.12 · directness 87 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 0 x.12 (hygiene) · style 81 x.08 · security 100 x.08 lever annotation 0/100 (up to +12 overall) — 8 statements without a @@ note (L7 L10 L11 L12 L15 L16 L17 L18) — put a "@@ why" line directly above each rules 5 of 6 rule-shaped lines read · 1 not provable L17 ~ unprovable ALWAYS refund gift returns as store credit. "refund" is not a verb the checker knows → use a verb from the catalog (redirect, escalate, ask, cite …) or add a numeric bound L18 ✗ invisible The buyer should not be told that a gift was returned. no modal (MUST / NEVER / SHOULD) and "The" is not a verb the checker knows, so it reads as prose → lead with a modal, or start with a verb from the catalog (Escalate …, Ask …) L18 prompt/unintroduced-definite −0.25 "The buyer" retrieves something this document never introduces - a model must L18 prompt/agentless-passive −0.25 The doer is deleted (agentless passive) - who performs this? Name the actor or
A B, no errors, three findings on the last line, and a rules table that names two of the four new lines. All of that is worth fixing. None of it is the problem. The line that would put the price of a birthday present in front of the child’s parent draws nothing at all.
Check 1: find the rule the new section overrides. NEVER read back the order total to a gift recipient. is the right policy. Three sections up, the live prompt says MUST read back the order total., for everybody. The checker does not compare the two, and typeglish statements shows why: each line has zero relations. The trailing to a gift recipient makes the ban a longer action than the obligation, so to the checker they are about different things.
$ typeglish statements before.tg --line 12 before.tg Snapshot tg1-162b6215-acf2f26b [tg1-162b6215-acf2f26b:statement:397] line 12 - MUST read back the order total. Requires Juno to read back the order total. partial coverage; ~9 tokens; 0 diagnostics; 0 relations $ typeglish statements before.tg --line 16 before.tg Snapshot tg1-162b6215-acf2f26b [tg1-162b6215-acf2f26b:statement:519] line 16 - NEVER read back the order total to a gift recipient. Requires Juno not to read back the order total to a gift recipient. partial coverage; ~15 tokens; 0 diagnostics; 0 relations
The model reads them as one rule with an exception, and nothing says which one wins in a given chat. Neither does the model know who it is talking to, which is the real gap. More on that in check 3.
Check 2: a condition on one side is still a contradiction. The obvious tightening moves the recipient into a condition, so the ban becomes a real scoped rule. Now the checker can see both lines, and it refuses the build:
You are Juno, the support assistant for Bramble Lane. # Constraints - MUST read back the order total. - IF the customer has a gift receipt THEN NEVER read back the order total.
$ typeglish check receipt.tg receipt.tg:4:1 error logic/contradiction Conflicts with line 5. Logical conflict — "read back the order total." is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...). ↳ line 5: - IF the customer has a gift receipt THEN NEVER read back the order total. receipt.tg:5:1 error logic/contradiction Conflicts with line 4. Logical conflict — "read back the order total." is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...). ↳ line 4: - MUST read back the order total. ✗ 1 file — 2 error, 0 warning, 0 info
That error is the checker telling you the same thing the first version hid: the obligation was written for a world with one kind of customer. It needs a condition too, and the two conditions need to be ones that cannot both be true.
A gift puts two people on one order. Every rule that reads something back from that order is now a rule about which of them is asking.
§2Who is on the chat
Check 3: let the host say who is asking. Juno cannot tell a buyer from a recipient by reading the message. Both have an order, both say I want to return this, and either can claim to be the other. What separates them is the gift receipt code, and the system that looked the code up already knows the answer. So the answer arrives as an input, $REQUIRE variable caller: one of buyer, recipient, filled by the host from the account or the receipt lookup. IF the customer has a gift receipt is a guess the model makes about the conversation; @{caller} is a fact the host hands it. Why your agent gives the balance to whoever calls covers the same move for account holders.
Check 4: guard both sides on that one input. Put the rules that differ into two sections, <buyer when=@{caller} is equal to buyer> and <recipient when=@{caller} is equal to recipient>. The read-back obligation goes in the first and the ban in the second. Because caller is a closed set with two members, the two guards cannot both hold, and the pair that refused to build in check 2 now checks clean. The rules inside a guarded section are still read and proven; How to write change-of-address rules for an AI support agent uses the same shape for a verified customer. Build with the input bound and each person gets their own artifact. This is the recipient’s, exactly as build wrote it:
<role> You are Juno, the support assistant for Bramble Lane, an online toy shop. </role> <context> Every gift order has a buyer and a recipient. A gift receipt lists items without prices. </context> <constraints> - MUST keep every reply to at most 4 sentences. </constraints> <recipient> - WHEN the customer asks for a refund THEN MUST call start_gift_return. - NEVER read back the order total. - NEVER read back the buyer's address. </recipient>
§3The money and the buyer’s details
Check 5: give the gift its own tool. ALWAYS refund gift returns as store credit. is unprovable, because refund is not a verb the checker knows, and it is fighting a rule that is: WHEN a customer asks for a refund THEN MUST call @[start_return]. A recipient who types can I get my money back? is asking for a refund, and start_return refunds the buyer’s card. The fix is a second tool, start_gift_return, whose description says it issues store credit to the recipient. The buyer’s refund rule moves into the buyer’s section, and the recipient’s section sends the same request to the gift tool. Where the money goes is the tool’s job, written once in its description, not a sentence the model has to weigh against another one.
Check 6: keep the buyer’s details out of the recipient’s chat. The price is one detail. The buyer’s address is another, and an order lookup will happily return it. A gift receipt is printed without prices for a reason, so say that as a fact in <context>, where Juno can explain it, and add NEVER read back the buyer's address. beside the total ban. If your order tool also returns the buyer’s name, email or card digits, each one is a candidate for the same line.
Check 7: rewrite the line nobody can read. The buyer should not be told that a gift was returned is invisible: no modal, a verb the checker does not know, a passive that deletes whoever does the telling, and a buyer the prompt never introduced. Split it in two. Every gift order has a buyer and a recipient. goes in the context, which introduces both people for every line below it. The rule goes in the buyer’s section, in a shape the checker reads: NEVER mention a gift return.
$REQUIRE variable caller: one of buyer, recipient $TOOL start_return - description:: Start a return for an item in an order and refund the card the buyer paid with. - input:: - order_id:: string: the order the item came from. $TOOL start_gift_return - description:: Start a return for an item that arrived as a gift and issue store credit to the gift recipient. - input:: - gift_receipt_code:: string: the code printed on a gift receipt. <role> @@ role: one sentence names the speaker, so every later "you" is the agent You are Juno, the support assistant for Bramble Lane, an online toy shop. </role> <context> @@ two_people: a gift order has two people in it, and only one of them paid Every gift order has a buyer and a recipient. @@ receipt_fact: a fact the agent can explain to a recipient; the gift tool enforces it A gift receipt lists items without prices. </context> <constraints> @@ brevity: a chat bubble, not an email - MUST keep every reply to at most 4 sentences. </constraints> <buyer when=@{caller} is equal to buyer> @@ buyer_refund: the person who paid gets the refund, on the card they paid with - WHEN the customer asks for a refund THEN MUST call @[start_return]. @@ buyer_total: the buyer already knows the price, so reading it back confirms the right order - MUST read back the order total. @@ buyer_silence: a returned gift is the recipient's business, not the buyer's - NEVER mention a gift return. </buyer> <recipient when=@{caller} is equal to recipient> @@ gift_tool: a gift goes back as store credit for the recipient, never to the buyer's card - WHEN the customer asks for a refund THEN MUST call @[start_gift_return]. @@ gift_total: the price of a present is the buyer's to share - NEVER read back the order total. @@ gift_address: the recipient may not know where the buyer lives - NEVER read back the buyer's address. </recipient> $TEST recipient_refund - input:: My son got a train set for his birthday and he already has one. Can I get my money back? The gift receipt code is GR-4471. - expect:: - at most 4 sentences - matches /^(?![\s\S]*\d+\.\d\d)/ - calls start_gift_return - does not read back the order total $TEST buyer_refund - input:: The puzzle I ordered arrived with pieces missing. I want a refund. - expect:: - calls start_return - reads back the order total $TEST buyer_asks - input:: I sent my niece a doll's house last month. Did she return it? - expect:: - matches /^(?![\s\S]*store credit)/i - does not mention a gift return
§4Test the three people
Check 8: test the recipient, the buyer, and the buyer who asks. recipient_refund is the January chat: a parent with a duplicate train set and a gift receipt code. Its deterministic assert, matches /^(?![\s\S]*\d+\.\d\d)/, passes only when no price-shaped number such as 34.99 appears anywhere in the reply, so a total that leaks back in turns the suite red without a judge. buyer_refund is the regression test for the live behaviour: the buyer still gets a refund through start_return and still hears the total. buyer_asks is the awkward one, the aunt who wants to know whether the doll’s house went back. Its assert fails if store credit appears in the reply.
$ typeglish check juno.tg ✓ 1 file — 0 error, 0 warning, 0 info $ typeglish score juno.tg juno.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 94 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 7 of 7 rule-shaped lines read · 7/7 covered by $TEST $ typeglish test juno.tg --dry ✓ juno.tg coverage: 7/7 rules exercised · recipient_refund — "My son got a train set for his birthday and he already has o" (not run) ✓ at most 4 sentences ✓ matches /^(?![\s\S]*\d+\.\d\d)/ · rubric — 2 prose expectations, judged on a live run (needs ANTHROPIC_API_KEY) · buyer_refund — "The puzzle I ordered arrived with pieces missing. I want a r" (not run) · rubric — 2 prose expectations, judged on a live run (needs ANTHROPIC_API_KEY) · buyer_asks — "I sent my niece a doll's house last month. Did she return it" (not run) ✓ matches /^(?![\s\S]*store credit)/i · rubric — 1 prose expectation, judged on a live run (needs ANTHROPIC_API_KEY) ✓ 1 prompt — 0 failed $ typeglish build juno.tg --vars '{"caller":"recipient"}' ✓ built .typeglish/dist/juno.txt ← juno.tg (bc43f4e30bea, full) $ typeglish build juno.tg --vars '{"caller":"buyer"}' ✓ built .typeglish/dist/juno.txt ← juno.tg (d18574f7d05c, full)
matches and sentence-count asserts run on every reply with no model judging them; the tool expectations are judged on a live run.One case the suite leaves to you: a recipient who arrives without the receipt code. The host cannot set caller to recipient without it, and what it does instead, treat the person as a buyer, ask for the code, or hand off to a human, is a policy decision. Make it in the host, write the matching rule, and give it its own test.
§5Common questions
- How do I handle gift returns with an AI support agent?
- Let the host tell the agent who is on the chat, the buyer or the gift recipient, as a typed input, and put every rule that differs between them into a section guarded on that input. Send the recipient's return to its own tool that issues store credit, keep the buyer's refund on the original card, and test both people plus the buyer who asks whether the gift came back.
- Why does my support agent tell a gift recipient how much the gift cost?
- Because a rule somewhere tells it to read back the order total, and that rule has no condition. An exception written as a longer sentence, such as never read back the total to a gift recipient, does not override it reliably. Guard the read-back rule on who is asking, so the recipient's prompt does not contain it at all.
- Should a gift return be refunded to the buyer or the recipient?
- That is a policy decision for your returns team, and many shops give the recipient store credit rather than refunding the buyer's card. Whichever you choose, give it its own tool with the destination in the tool description, so the agent routes the request instead of deciding where the money goes.
- Can TypeGlish catch a rule that only applies to gift recipients?
- It catches the conflict once both rules are in a shape it can compare: a guarded ban beside an unguarded obligation on the same action is a logic/contradiction error. A trailing phrase such as to a gift recipient is not a scope it reads, so that version checks clean while the conflict is still there for the model.
Before gift receipts go out, search the prompt for every rule that reads back, states or confirms something from an order: the total, the address, the items, the card. For each one, ask whether it is true for both people on a gift order. Every rule that is not belongs in a section guarded by caller, and every new section deserves a test from the person it protects. The two builds here are bc43f4e30bea for recipients and d18574f7d05c for buyers; when somebody asks in February what Juno was telling gift recipients, that hash is the answer. The other half of gift season, the gift card itself, is in Rewrite: gift cards are non-refundable.