The customer wants the next size up
Merchandising decides that exchanges are free now, and the announcement lands in the support prompt as four lines under a new heading. It checks clean. It also puts an exchange pitch in front of every refund, lets the agent promise sizes it has not looked up, and leaves the money to a sentence nobody owns.
TL;DR When you add size and colour exchanges to a support agent, give the exchange its own tool and its own window, let a stock lookup decide before any size is promised, keep refund requests out of the exchange path, and test the customer who is past the return window but inside the exchange one.
Tamsin Row sells clothes online, and its chat assistant, Ivy, has handled refund returns for a year: 30 days from delivery, through a start_return tool. This month the business adds exchanges. Same product, different size or colour, free, for 60 days. Here are the eight checks for the pull request that teaches Ivy to swap a jumper.
§1The four lines from merchandising
$TOOL start_return - description:: Start a refund return for an item in an order. - input:: - order_id:: string: the order the item came from. # Role You are Ivy, the support assistant for Tamsin Row, an online clothing shop. # Constraints - MUST keep every reply to at most 4 sentences. - WHEN a customer asks for a refund THEN MUST call @[start_return]. - MUST offer a return within 30 days of delivery. # Exchanges Customers can now exchange items for a different size or colour within 60 days, free of charge. - ALWAYS offer an exchange before a refund. - MUST check that the new size is in stock. - Price differences will be refunded or charged.
# Exchanges is the prompt that has been live since last year. The section below it is the announcement, pasted the afternoon it was approved.$ typeglish check before.tg before.tg:15:1 info prompt/unregistered-doer A bare generic doer never enters the world model - instruction to the agent, or background about users? Use the imperative if the agent acts, or a definite party ("The user should ...") to register the doer. 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 "Price" 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/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 (80/100) proven errors: none tiers: base+z3 planes runtime 90 (what the model reads) · hygiene 50 (source only) facets enforceability 80 x.21 · hardness 100 x.12 · directness 76 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 · 2 not provable L16 ~ unprovable ALWAYS offer an exchange before a refund. the checker knows "offer" but cannot prove this shape: it stops reading at "before" — the clause after it is not part of the provable action → if the clause is a condition, make it one: WHEN <clause> THEN MUST <verb> …; otherwise give it its own rule L17 ~ unprovable MUST check that the new size is in stock. the checker knows "check" but cannot prove this shape: it stops reading at "that" — the clause after it is not part of the provable action → if the clause is a condition, make it one: WHEN <clause> THEN MUST <verb> …; otherwise give it its own rule L18 ✗ invisible Price differences will be refunded or charged. no modal (MUST / NEVER / SHOULD) and "Price" 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 …) L15 prompt/unregistered-doer −0.25 A bare generic doer never enters the world model - instruction to the agent, o L18 prompt/agentless-passive −0.25 The doer is deleted (agentless passive) - who performs this? Name the actor or
No errors, a B, and a rules table that names three of the four new lines. That is the useful part of a clean run: it tells you which lines the checker could read, and which it is only passing along to the model.
Check 1: turn the announcement into something the agent does. Customers can now exchange items for a different size or colour within 60 days, free of charge is the most important line in the section, and it is news. Its subject is customers, so the checker cannot tell whether it is an instruction or background, and says so on line 15. Ivy needs two things from it: a fact she can repeat, and a rule that says when to offer the swap. Write them separately, the fact in # Context and the offer as WHEN the customer says an item does not fit THEN MUST offer an exchange.
Check 2: do not put the exchange in front of the refund. ALWAYS offer an exchange before a refund. reads as a sequencing rule, and the checker stops reading at before. What it says to the model is worse than unprovable: every customer who asks for their money back now hears a pitch first. Ivy already has a rule for refund requests, and it should keep winning. A request for a refund calls @[start_return]; the exchange offer belongs to the customer whose problem is fit. Your agent's no is an opening bid covers what happens when a prompt teaches the agent to haggle, and the same goes in reverse: the customer should not have to say no twice to get what they asked for.
Check 3: delete the sentence nobody owns. Price differences will be refunded or charged is invisible to the checker: no modal, no verb it knows, and a passive that deletes whoever does the refunding or charging. It is also a money rule, written in the future tense, with no tool behind it. Tamsin Row’s exchanges are like for like, the same product in another size or colour, so no price difference ever comes up. Say that with a ban, NEVER quote a price difference., and let a change of product go through the ordinary return.
An announcement tells customers what they can now do. The prompt needs the version that tells the agent when to do it.
§2Two windows, two processes
Check 4: keep each window on its own process. Returns close at 30 days and exchanges at 60, and those numbers have to coexist. They do, as long as they are about different things. Tamsin Row takes returns for 30 days after delivery and Tamsin Row takes exchanges for 60 days after delivery sit side by side in # Context and check clean, because a return and an exchange are two processes with two windows. The trouble starts when somebody writes the exchange as a kind of return.
Check 5: do not write the exception into the prompt. The awkward customer is the one on day 45 whose new size is out of stock. The exchange was promised, and it cannot be filled, so the business refunds instead, past the 30-day window. The natural first draft writes that exception next to the window:
You are Ivy, the support assistant for Tamsin Row. # Constraints - MUST offer a return within 30 days of delivery. - IF the stock check finds no stock THEN MUST offer a return within 60 days of delivery.
$ typeglish check window.tg window.tg:4:1 error logic/time-strength Conflicts with line 5. One bound per slot — "within 30 days" already entails "within 60 days"; the looser rule on "you offer a · return" is dead weight. Keep one. ↳ line 5: - IF the stock check finds no stock THEN MUST offer a return within 60 days of delivery. window.tg:5:1 error logic/time-strength Conflicts with line 4. One bound per slot — "within 30 days" already entails "within 60 days"; the looser rule on "you offer a · return" is dead weight. Keep one. ↳ line 4: - MUST offer a return within 30 days of delivery. ✗ 1 file — 2 error, 0 warning, 0 info
The checker is right to refuse it. To the model, those two lines are one promise with two deadlines, and the condition in front of the second one does not stop the first from applying on day 45. The fix is not a cleverer sentence. The exception is the returns system’s policy, so the returns tool owns it: its description says it accepts items for 30 days, or 60 when an exchange cannot be filled, and the prompt says only IF the stock check finds no stock THEN MUST call @[start_return]. How to write order-cancellation rules for an AI support agent makes the same move with a dispatch cutoff.
§3The stock system decides
Check 6: look up the size before anybody promises it. MUST check that the new size is in stock. is the right instinct with a shape the checker cannot prove: it stops at that. So does the ban we drafted next, NEVER confirm that a size is in stock. Turn the instinct into a tool call on an event, WHEN the customer picks a new size THEN MUST call @[check_stock]., and a ban that holds until the lookup runs: IF the stock check has not run THEN NEVER confirm a size. Both read. The second one is the line that stops “yes, we have the medium!” on a size that sold out on Saturday.
Check 7: give the swap its own tool, and both outcomes a rule. An exchange is not a return with a note on it. It sends a replacement, so it needs a $TOOL start_exchange with the order, the size and the colour. The stock check has two answers, and each needs a line: found, call @[start_exchange]; not found, call @[start_return]. Point at a tool you forgot to declare and the checker refuses the build with structure/undefined-tool-inline, which is the cheapest way to find out that the exchange tool was only ever in the announcement.
$TOOL start_return - description:: Start a refund return. It accepts items up to 30 days after delivery, or 60 days when an exchange cannot be filled. - input:: - order_id:: string: the order the item came from. $TOOL check_stock - description:: Check whether a product is in stock in a given size and colour. - input:: - product_id:: string: the product the customer wants. - size:: string: the size the customer wants. - colour:: string: the colour the customer wants. $TOOL start_exchange - description:: Swap an item for the same product in another size or colour, and send the replacement. - input:: - order_id:: string: the order the item came from. - size:: string: the replacement size. - colour:: string: the replacement colour. # Role @@ role: one sentence names the speaker, so every later "you" is the agent You are Ivy, the support assistant for Tamsin Row, an online clothing shop. # Context @@ return_window: a fact the agent can explain; the returns tool enforces it Tamsin Row takes returns for 30 days after delivery. @@ exchange_window: a second fact on a second process, so the two windows never share a slot Tamsin Row takes exchanges for 60 days after delivery. # Constraints @@ brevity: a chat bubble, not an email - MUST keep every reply to at most 4 sentences. @@ refunds: a refund request goes straight to a return, with no exchange pitch in the way - WHEN a customer asks for a refund THEN MUST call @[start_return]. @@ fit: an item that does not fit is the exchange customer - WHEN the customer says an item does not fit THEN MUST offer an exchange. @@ stock_first: the stock system decides, not the agent - WHEN the customer picks a new size THEN MUST call @[check_stock]. @@ no_stock_claims: a size is only available once the stock check says so - IF the stock check has not run THEN NEVER confirm a size. @@ swap: the exchange tool sends the replacement - IF the stock check finds the item THEN MUST call @[start_exchange]. @@ no_stock: an exchange that cannot be filled becomes a refund; the returns tool owns that 60-day exception - IF the stock check finds no stock THEN MUST call @[start_return]. @@ same_product: exchanges are like for like, so no price difference ever comes up - NEVER quote a price difference. $TEST too_small - input:: The jumper I ordered is too small. Can I swap it for a medium? - expect:: - at most 4 sentences - matches /^(?![\s\S]*price difference)/ - offers an exchange - calls check_stock - calls start_exchange when the medium is in stock $TEST refund_only - input:: The dress arrived on Monday and I do not like it. I want a refund. - expect:: - matches /^(?![\s\S]*exchange)/ - calls start_return $TEST day_45 - input:: My coat arrived 45 days ago and the sleeves are too short. Can I get a longer size? - expect:: - offers an exchange - calls check_stock - calls start_return when no longer size is in stock
§4Test the three customers
Check 8: test the swap, the refund, and day 45. too_small is the happy path: an exchange offer, a stock check, the swap, and no price talk. refund_only is the regression test for check 2, and its deterministic assert is the one to keep: matches /^(?![\s\S]*exchange)/ passes only when the word appears nowhere in the reply, so the day somebody puts the pitch back in front of the refund, the suite goes red. day_45 is the customer the two windows were written for: inside the exchange window, outside the return one, and refunded through the tool if the longer size is gone.
$ typeglish check ivy.tg ✓ 1 file — 0 error, 0 warning, 0 info $ typeglish score ivy.tg ivy.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 93 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 8 of 8 rule-shaped lines read · 8/8 covered by $TEST $ typeglish test ivy.tg --dry ✓ ivy.tg coverage: 8/8 rules exercised · too_small — "The jumper I ordered is too small. Can I swap it for a mediu" (not run) ✓ at most 4 sentences ✓ matches /^(?![\s\S]*price difference)/ · rubric — 3 prose expectations, judged on a live run (needs ANTHROPIC_API_KEY) · refund_only — "The dress arrived on Monday and I do not like it. I want a r" (not run) ✓ matches /^(?![\s\S]*exchange)/ · rubric — 1 prose expectation, judged on a live run (needs ANTHROPIC_API_KEY) · day_45 — "My coat arrived 45 days ago and the sleeves are too short. C" (not run) · rubric — 3 prose expectations, judged on a live run (needs ANTHROPIC_API_KEY) ✓ 1 prompt — 0 failed $ typeglish build ivy.tg ✓ built .typeglish/dist/ivy.txt ← ivy.tg (a2719f8cb130, 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: the customer who says it is too small and I want a refund fires both the fit rule and the refund rule. The refund rule calls the tool either way, so the customer gets what they asked for. If an exchange mention in that reply bothers you, that is a policy call; write it down as its own rule and its own test rather than hoping the model reads your mind.
§5Common questions
- How do I let my AI support agent handle size exchanges?
- Give the exchange its own tool, separate from the refund return, and a rule that offers it when the customer says an item does not fit. Call a stock lookup when the customer picks a new size, ban confirming any size until that lookup has run, and route each answer of the lookup to a tool: the exchange when the size is found, the return when it is not.
- Should my agent offer an exchange before a refund?
- Not to a customer who asked for a refund. Send a refund request straight to the return tool, and keep the exchange offer for the customer whose problem is fit. Pin it with a test whose input asks for a refund and whose assert fails if the word exchange appears in the reply.
- How do I write a return window and a longer exchange window in the same prompt?
- State each window as a fact about its own process, returns for 30 days and exchanges for 60, and they check clean side by side. Do not write an exception as a second return rule with a longer window: two return windows on one slot is a logic/time-strength error, and a condition in front of one of them does not separate them. Let the returns tool own the exception.
- Why does my chatbot say a size is in stock when it is not?
- Because nothing in the prompt makes it look first, so it answers from the conversation. Add a rule that calls the stock tool when the customer picks a size, and a ban that holds until the lookup runs: IF the stock check has not run THEN NEVER confirm a size.
The exchange launch is the kind of change that gets a second edit within a week: a colour added, a sale item excluded, a window extended for the holidays. Each edit should land where its owner lives, the stock rules in the stock system, the windows in the returns and exchange tools, so the prompt only changes when the behaviour does. When it does change, ship it as its own build: typeglish build ivy.tg wrote this one as a2719f8cb130, and one hash per wording is how you answer “what was Ivy saying about exchanges last Tuesday?” Building a returns-eligibility agent, prompt-first covers the returns half of this prompt from scratch.