> Markdown rendition of https://typeglish.dev/blog/agent-order-cancellation ("How to write order-cancellation rules for an AI support agent - Tidelines"). Canonical page: https://typeglish.dev/blog/agent-order-cancellation · All pages: https://typeglish.dev/llms.txt

[← Tidelines](https://typeglish.dev/blog)/ Guides Oct 26, 2026

# How to write order-cancellation rules for an AI support agent

“Cancel my order” sounds like the simplest contact a support agent gets. It is one of the few where the agent changes something it cannot change back, about a parcel it cannot see, for a customer who will hold it to every word. Here is how to write the rules so each of those is somebody’s job.

by **TypeGlish team** 8 min read #guides

Stop what has not left.

**TL;DR** Let a cancel tool own the dispatch cutoff, confirm the order number before it fires, route anything already dispatched to a return, and have the agent report only what the tool returned, refund date included.

Most cancellation sections in agent prompts are pasted from the help page: one line of policy, one line of process, a retention nudge from the growth team, and a refund estimate. Each line made sense to the customer it was written for. Read by an agent with a tool, almost every line asks it to know something only the warehouse knows.

## §1 The section as pasted

Fernlea Outdoor sells tents, packs and boots online. Its chat assistant has one tool, `cancel_order`, and this section:

naive.tg - the cancellation section from the help page ✓ C (71/100)

```typeglish
# Role
You are the chat assistant for Fernlea Outdoor, an online outdoor-gear retailer.

# Cancellations
- Orders can be cancelled until they are dispatched.
- WHEN the customer asks to cancel an order THEN MUST call @[cancel_order].
- IF the order has shipped THEN NEVER call @[cancel_order].
- Try to save the order by offering a discount.
- Refunds usually arrive within 3 to 5 days.

$TOOL cancel_order
  - description:: Cancel an order that has not been dispatched.
  - input::
    - order_number:: the order to cancel.
```

tg review - naive.tg (top of the report) ✓ 2 of 5 rule-shaped lines read

```
$ typeglish review naive.tg
naive.tg — C (71/100) → A (99) projected if every fix below lands  check: 0 error, 0 warning, 6 info  tiers: base+z3

  FIXES  ranked by projected gain
  +12  put a "@@ why" note above L2 L5 L6 L7 L8 L9 — annotation 0 → 100
  +6   lead with a modal (MUST / NEVER / SHOULD) on L5 L8 L9 — 3 rules become visible to the checker
  +4   commit the 1 soft statement ("try to" on L8) to MUST / NEVER / ALWAYS / REQUIRE / ENSURE — or keep the word if it is a permission by design — up to hardness 100
  +3   use the imperative or a named doer on L5 — up to directness 100
  +1   fix the 1 prompt/hedging finding (L8) — A hedge (try to, if possible) softens an instruction; commit to a modal.
  +1   fix the 1 prompt/agentless-passive finding (L5) — The doer is deleted (agentless passive) or demoted to a by-phrase - name the actor or use the imperative.
  +1   fix the 1 prompt/unregistered-doer finding (L8) — A bare generic doer (Users should ...) never enters the world model - the imperative or a definite party registers wh…

  RULES  2 of 5 rule-shaped lines read
    L5   ✗ invisible     Orders can be cancelled until they are dispatched.
                         no modal (MUST / NEVER / SHOULD) and "Orders" 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 …)
```

Everything is `info`, so the section builds. Three of the five lines reach the model as prose the checker cannot read: the policy, the save attempt, and the refund estimate.

The two lines it can read are the interesting ones. Line 6 says to call the tool when the customer asks. Line 7 says never to call it when the order has shipped. The checker reads both, finds no conflict, and is right: with different conditions, they do not collide on paper. In a chat they collide all the time, because the agent has no way to tell whether line 7’s condition holds.

## §2 The cutoff belongs to the warehouse

*IF the order has shipped* is a fact about a pallet in a building. The agent sees a chat window. To evaluate the condition it has to guess, and the clues on hand are bad ones: the customer saying *it hasn’t shipped yet, has it?*, an estimated delivery date, or an order placed *only an hour ago*. Any of those can be wrong by the time the van leaves. So the agent says *done, cancelled*, and the parcel arrives two days later.

The fix is to stop asking the agent the question at all. The cancel tool is the one party that can answer it at the moment it matters, so give it the job. Its description says what it does with each item: cancel what is still in the warehouse, and report what has already left. The agent’s rule loses its condition and gains a pointer. ([Building a warranty-claim agent, prompt-first](https://typeglish.dev/blog/build-warranty-claim-agent) does the same with a warranty clock: the tool owns the date, the prompt owns the response.)

A dispatched item is then a different job, not a refusal. That needs a second tool, and here the checker helps by refusing to let you assume one:

tg check - routing dispatched items to a return the platform has not declared ✗ structure/undefined-tool-inline

```
$ sed -n '10,11p' s3.tg
@@ dispatched: a parcel on a van is a return, not a cancellation
- WHEN @[cancel_order] reports a dispatched item THEN MUST call @[start_return].
$ typeglish check s3.tg
s3.tg:11:65  error  structure/undefined-tool-inline  @[start_return] — no $TOOL or $IMPORT tool named "start_return" in this file. Define it ("$TOOL start_return") or import it ("$IMPORT tool start_return") so the pointer has a target.

✗ 1 file — 1 error, 0 warning, 0 info
```

The build stays refused until somebody declares `start_return`, which means somebody has to find out whether the platform can open a return from chat. If it cannot, you learn that before launch instead of from a customer.

> Never give the agent a condition only the warehouse can evaluate.

## §3 Confirm first, as its own trigger

A cancellation is a write the agent cannot take back. Customers with two open orders say *cancel my order* all the time, and the wrong one stopped is a worse contact than the one you started with. So the agent confirms which order before the tool fires. The natural way to write that does not hold up:

tg review - the confirmation as a trailing before-clause (rules table) ✗ read, not provable

```
$ grep MUST s2.tg
- MUST confirm the order number before you call @[cancel_order].
$ typeglish review s2.tg
  RULES  1 of 1 rule-shaped line read · 1 not provable
    L5   ~ unprovable    MUST confirm the order number before you call @[cancel_order].
                         the checker knows "confirm" 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
```

The order of events lives in the half of the sentence the checker does not read. It also leaves the model to decide whether a half-confirmed number counts.

Do what the message says and split it into two events: the customer asks, so the agent confirms; the customer confirms, so the agent calls the tool. Each rule now has its own trigger, and there is no reading of the pair where the tool fires on the first message. ([Your agent can now do something it cannot undo](https://typeglish.dev/blog/agent-first-write-action) is the checklist for every write action like this one, not just cancellations.)

## §4 Say what the tool said, and nothing it did not

The last three lines of the pasted section are about what the agent says after the decision, and each one has the agent saying something no system confirmed.

**A partly dispatched order is partly cancelled.** A customer orders a tent and a pair of boots; the boots ship from another warehouse that morning. *Your order has been cancelled* is now false for half of it. Make the split a rule: when the tool reports a dispatched item, list every dispatched item, and start the return for them.

**The save attempt is a pricing decision.* Try to save the order by offering a discount* is the line the checker flags as `prompt/hedging`, and for a good reason: a hedge the model may or may not follow means some customers are offered money to stay and others are not, for the same request. Pick one. This guide picks the ban, because a customer cancelling inside the window is using a right, and a discount at that point is a price on it. If your business decides the other way, write a `MAY` with a fixed amount and a test, not a *try to*.

**The refund date comes from the system.* Refunds usually arrive within 3 to 5 days* is a typical week. The customer hears a promise about their money. The tool knows the actual date from the payment provider, so the rule is to quote that one.

fixed.tg - the tool owns the cutoff, every rule has a trigger, three tests ✓ A (99/100)

```typeglish
# Role
@@ role: the desk this file speaks for
You are the chat assistant for Fernlea Outdoor, an online outdoor-gear retailer.

# Cancellations
@@ confirm_first: a cancellation cannot be undone, so the customer names the order before anything happens
- WHEN the customer asks to cancel an order THEN MUST confirm the order number with the customer.
@@ tool_owns_cutoff: only the warehouse knows what has left the building; the tool answers, the agent never guesses
- WHEN the customer confirms the order number THEN MUST call @[cancel_order].
@@ dispatched: a parcel on a van is a return, not a cancellation
- WHEN @[cancel_order] reports a dispatched item THEN MUST call @[start_return].
@@ name_the_split: a partly dispatched order is partly cancelled, so the reply says which part
- WHEN @[cancel_order] reports a dispatched item THEN MUST list every dispatched item.
@@ no_save_discount: the customer is inside the window; a discount here is a price for their right
- NEVER offer a discount to the customer.
@@ refund_date: the date comes from the payment system, not from a typical week
- MUST quote the refund date you get from @[cancel_order].
@@ brevity: a chat bubble, not an essay
- MUST answer in at most 3 sentences.

$TOOL cancel_order
  - description:: Cancel every item of an order that has not left the warehouse. Returns the cancelled items, any dispatched items, and the refund date.
  - input::
    - order_number:: the order to cancel.
$TOOL start_return
  - description:: Open a return for dispatched items and email the customer a prepaid label.
  - input::
    - order_number:: the order with dispatched items.

$TEST cancel_ask
  - input:: I want to cancel my order, I bought the wrong size tent.
  - expect::
    - confirms the order number before anything is cancelled
    - does not offer a discount
    - matches /^(?!.*[Dd]iscount)/
    - at most 3 sentences

$TEST half_shipped
  - input:: Yes, it is FO-55102. Cancel all of it please.
  - expect::
    - calls cancel_order
    - lists every dispatched item
    - quotes the refund date from the tool

$TEST already_gone
  - input:: The tracking says it left yesterday but I still want to cancel.
  - expect::
    - calls start_return
    - matches /^(?!.*[Dd]iscount)/
```

The policy line is gone: the tool’s description carries it, where it is enforced instead of described. No rule asks the agent whether anything has shipped.

tg check, score, test --dry and build - fixed.tg ✓ 7/7 covered by $TEST

```
$ typeglish check fixed.tg
✓ 1 file — 0 error, 0 warning, 0 info

$ typeglish score fixed.tg
fixed.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 fixed.tg --dry
✓ fixed.tg  coverage: 7/7 rules exercised
  · cancel_ask — "I want to cancel my order, I bought the wrong size tent." (not run)
      ✓ matches /^(?!.*[Dd]iscount)/
      ✓ at most 3 sentences
      · rubric — 2 prose expectations, judged on a live run (needs ANTHROPIC_API_KEY)
  · half_shipped — "Yes, it is FO-55102. Cancel all of it please." (not run)
      · rubric — 3 prose expectations, judged on a live run (needs ANTHROPIC_API_KEY)
  · already_gone — "The tracking says it left yesterday but I still want to canc" (not run)
      ✓ matches /^(?!.*[Dd]iscount)/
      · rubric — 1 prose expectation, judged on a live run (needs ANTHROPIC_API_KEY)
✓ 1 prompt — 0 failed

$ typeglish build fixed.tg && cat .typeglish/dist/fixed.txt
✓ built .typeglish/dist/fixed.txt ← fixed.tg (24e5752bd47d, full)
# Role
You are the chat assistant for Fernlea Outdoor, an online outdoor-gear retailer.

# Cancellations
- WHEN the customer asks to cancel an order THEN MUST confirm the order number with the customer.
- WHEN the customer confirms the order number THEN MUST call cancel_order.
- WHEN cancel_order reports a dispatched item THEN MUST call start_return.
- WHEN cancel_order reports a dispatched item THEN MUST list every dispatched item.
- NEVER offer a discount to the customer.
- MUST quote the refund date you get from cancel_order.
- MUST answer in at most 3 sentences.
```

Seven rules, each with a trigger the agent can actually observe: a customer message, a confirmation, or a tool result. The regex runs offline and catches the discount in any phrasing that uses the word.

The three tests map to the three contacts that go wrong in production: the first ask, where the agent must confirm and must not haggle; the split order, where it must name what is still coming; and the order that has already left, where it must start a return instead of saying no. The `already_gone` input is worth keeping word for word. A customer who quotes the tracking page is giving the agent the exact clue it must not act on.

## §5 Common questions

**How do I let my AI support agent cancel orders safely?**

Give it a cancel tool that owns the dispatch cutoff, so the warehouse system decides what can still be stopped. Then write three rules: confirm the order number when the customer asks to cancel, call the tool when they confirm, and report exactly what the tool returned.

**Why does my agent tell customers an order is cancelled when it has already shipped?**

Usually because the prompt says something like `IF the order has shipped THEN NEVER cancel it`, and the agent cannot see whether it has shipped. It guesses from the conversation or the estimated delivery date. Let the cancel tool report dispatched items, and route those to a return.

**Should my agent try to save an order with a discount before cancelling?**

Decide, and write the decision as a rule. *Try to save the order* is a hedge the model may or may not act on, so different customers get different prices for the same request. This guide picks `NEVER offer a discount to the customer` and tests for it.

**What should my agent say about when the refund arrives?**

Only the date the cancel tool returns. A line like *refunds usually arrive within 3 to 5 days* is a typical week, not this customer’s refund, and the agent will quote it as a promise.

Field note

Before you wire the cancel tool, ask the warehouse team one question: *at what moment does an order stop being cancellable?* The honest answer is rarely “when it ships”. It is usually when it is picked, or when the label prints, and it is often different per warehouse. Whatever the answer, it goes in the tool, not the prompt. The prompt’s job is the conversation around the tool, and `typeglish review` is a quick way to find the lines still trying to do the tool’s job: on the pasted section, two of the three invisible lines were policy the tool should own.

∿ washed up Oct 26, 2026 ∿
