Skip to main content
This walkthrough builds the most common e-commerce support flow on Fini, end to end: “where’s my order?”, “can I cancel it?”, “can you change the shipping address?”, in a single rule. By the time you finish, your agent will reliably handle all three intents, branch on the order’s actual shipping state (not the customer’s perception), execute the right destructive Action when allowed, and respond with a real tracking link, real refund amount, or a real updated delivery estimate, every value pulled live from your order-management system, every step recorded in the AI Steps trace. The rule you’ll build is 100% deterministic at every branch: same conversation context, same tree walk, same outcome. That property is what makes it safe to put cancellation and address-change Actions behind an LLM-routed conversation, the LLM picks the intent, but every step after the pick is encoded in the tree. This walkthrough leans on a Rulebook pattern the Cancellation flow walkthrough didn’t show: a Fallback at the root, with one child per intent. Multiple sibling intents handled in a single rule, each child Steps gates on its own intent, the Fallback walks through them until one matches.
Use this walkthrough when you need one rule to handle multiple related intents. The Fallback-root pattern is also the right shape for FAQ-with-fallback-to-agent rules, eligibility check before destructive Action flows, and any case where you want to “try path A; if that doesn’t apply, try path B; otherwise ask”. For single-intent flows, the simpler Steps-rooted Cancellation flow is a cleaner template.

Before you start

This walkthrough configures the Behavior layer of your Fini agent (a Fallback-rooted rule that branches across three intents, plus the read-only and destructive Actions). It assumes the other three layers your agent needs are already in place: Knowledge so the agent can back up the blocked-Reply paths (return policies, carrier-redirect instructions, general “what statuses mean” articles), Prompts so the Reply nodes inherit your tone, and Deployment so customers can reach the agent on the channels you serve. If you’re starting from scratch, run Quickstart first. This rule specifically also needs the following: If you don’t have an OMS ready and want to follow along anyway, swap the URLs for your own test endpoints and the rest of the walkthrough still works.
The Lookup Order Action is reusable. The Action you build in Step 2 is read-only and useful far beyond this rule, exposure-tag automations, return-eligibility checks, and analytics queries can all reference it. Build it cleanly here once and reuse the workspace-level Action wherever you need order context downstream.

What you’ll build

A rule that handles three intents in a single Fallback-rooted tree:
  1. Order status (“where’s my order?”, “has it shipped yet?”): looks up the order, replies with shipping status, expected delivery, and tracking link.
  2. Cancel order (“please cancel my order”, “I want to cancel order #12345”): looks up the order, checks eligible_for_cancel, if eligible, calls the cancel Action and confirms the refund; if not, explains why (already shipped) and offers the return path.
  3. Change shipping address (“can you redirect my package?”, “change my shipping address”): looks up the order, checks eligible_for_change, if eligible, renders a Form to collect the new address, updates it, and confirms; if not (already shipped), explains and offers the return-to-sender path.
A single Customer Identity attribute provides customer_id and recent_orders (the customer’s last few order numbers, so the Read can disambiguate when the customer says “my order” without a number). A single Lookup Order Action is the read-only common dependency every branch starts with, no destructive work happens before the order’s actual state is loaded. The final rule tree:
Order status and changes rule tree. Root Fallback node contains four children in order. Child 1: a Steps labeled Status branch with three children, Check Topic Equals Order Status, Tool Lookup Order with order_id input and full order output, Reply composing a status response with tracking url. Child 2: a Steps labeled Cancel branch with five children, Check Topic Equals Cancel Order, Tool Lookup Order, Check eligible_for_cancel Equals True, Tool Cancel Order with order_id input and refund output, Reply confirming the cancellation. Child 3: a Steps labeled Change branch with five children, Check Topic Equals Change Order, Tool Lookup Order, Check eligible_for_change Equals True, Form Shipping address collecting four fields, Tool Update Shipping Address binding the form fields and order_id, Reply confirming the new estimated delivery. Child 4: a Reply labeled Fallback ask asking the customer to clarify which action they want.
Reading the tree: the root Fallback evaluates its children in order. Each of the first three children is a Steps block gated by its own intent Check at the top, when the LLM routes a “where’s my order?” message to this rule, the Status branch’s Check passes and that branch runs to completion; the Fallback succeeds; the other two branches never evaluate. If the customer’s message doesn’t match any of the three intents (unusual but possible because the LLM routed to this rule on a borderline message), the Fallback ask Reply runs as the final child and asks the customer to clarify. The four pieces and where each is configured: Build in this order. The Action used most (Lookup Order) is shared across all three Rulebook branches, so it ships once.

Step 1: Configure the User Attribute

A single attribute supplies the two fields the rule needs upstream: the customer_id to scope all order lookups, and a recent_orders array so the Read can map “my order” to a specific id when the customer doesn’t quote one.
1

Create the attribute

On API Setup → Attributes, click New Attribute. Name it Customer Identity. Pick a Source:
  • widget if your storefront’s widget signs the shopper’s identity into a JWT (the recommended pattern for authenticated e-commerce sessions, see Widget → Identify logged-in users for the JWT payload shape).
  • ui if your storefront passes the shopper’s email through embed metadata.
  • The integration’s own name (zendesk, intercom, gorgias, front, hubspot, salesforce, livechat) if your support traffic flows through that helpdesk, the integration’s user identifier becomes the lookup key.
2

Declare the input field

Under Passed-in fields (or Connection Settings if integration-sourced), list the lookup key with Use as Input in API turned on. For most e-commerce setups this is email. For Shopify, you may also have a customer_id already on the conversation, in which case use that directly and skip the email-based lookup below.
3

Add a Data Collection Step

  • Step Name: Lookup Customer
  • Method: GET
  • URL: https://api.yourshop.com/customers?email=${email}&include=recent_orders
  • Headers: {"x-api-key": "your-oms-api-key"} (or your OMS’s native scheme, Shopify uses X-Shopify-Access-Token).
  • Save From Response: {"customer_id": "id", "recent_orders": "recent_orders"}
Pull the whole recent_orders array unchanged, the Read node downstream can pick the first element when needed. Fini’s Save From Response documentation uses dotted JSON paths (order.status, user.organization_id); whether bracket-star projection (recent_orders[*].id) is supported isn’t documented, so the safe pattern is to pull the array and access elements in Read or Tool input instructions.
Where the API token lives. Fini doesn’t have a separate “workspace secrets” store for custom Attribute calls, the credential is hardcoded here in the Headers JSON. The Fini workspace itself is the secret boundary. Native integrations expose their own connection fields only for the matching integration source, and selected active integration fields can be used by widget-source attributes.
4

Toggle the collected fields

Collected fields show two switches, Use in Rulebooks and Visible to AI (no input switch, collected fields are automatically available to later steps in the same chain).recent_orders is Visible to AI so when a customer says “can you check my order?” without specifying which one, the agent can naturally reply “Sure, are you asking about order #1002 or #1003?”. Without this visibility, the agent would either guess (bad) or use opaque language (worse). The Read node downstream consumes recent_orders to default the order_id; the Tool’s input binding picks up the value via the tree variable the Read populates.
5

Attach and verify

Pick the agent from the top-right dropdown, toggle Customer Identity on, leave both Chat and Email selected (this rule works across both channels, with one Form-vs-Read caveat covered in Step 3). Click Save, then Play the Data Collection Step with a real email to confirm both fields populate correctly.
  • recent_orders comes back as something other than an array: the path you used on the right side of Save From Response doesn’t point at the array. Click Play on the Data Step to inspect the raw response, then adjust the path. Some APIs nest it under data.orders or customer.recent_orders, the Save From Response value is a JSON path into the response, not a Fini-specific transform.
  • Customers with no orders cause the attribute to return null: the lookup may need to be Allow Empty on the array field. If your OMS returns recent_orders: [] for new customers, the empty array is fine; null is the problem.
  • The attribute fires twice per message: you may have an upstream attribute that depends on this one. Order matters; if the chain is misconfigured, Fini retries. Inspect the attribute order in the editor.

Step 2: Configure the three Actions

Three Actions: one read-only that every Rulebook branch invokes first, and two destructive ones that only specific branches invoke. Build the read-only one first, it’s the dependency.

2a. Lookup Order (read-only)

1

Create the action

On API Setup → Actions (page header reads External Actions), click New Action.
  • Name: Lookup Order
  • Description: Looks up an order's full state and returns the fields downstream branches need: shipping status, totals, tracking, plus computed booleans for cancel and address-change eligibility. Read-only.
2

Define the inputs

Single input. The Rulebook’s Read node will resolve order_id from the customer’s message or, when the customer didn’t specify, from the first entry of recent_orders (the most recent order).
3

Define the outputs

The two eligibility booleans are the load-bearing output fields, the Cancel and Change branches Check on them before invoking the destructive Tools. Compute these on your OMS side, not in Fini. The OMS already knows the policy windows, the carrier handoff rules, the “shipping label printed but not picked up” edge cases. Letting Fini reconstruct that logic in Rulebook duplicates rules that should live in one place.
4

Add the Data Step

  • Step Name: Fetch order via OMS
  • Method: GET
  • URL: https://api.yourshop.com/orders/${order_id}
  • Headers: {"x-api-key": "your-oms-api-key"} (or your OMS’s native scheme).
  • Save From Response: map each output to the OMS’s actual field paths. For example:
If your OMS doesn’t compute policy.cancellable natively, build it as a derived field in a small middleware layer between Fini and the OMS, anywhere except inside Fini’s Rulebook.
5

Test, then save

Click Play. Plug in a real order_id from your dev OMS and confirm every output field populates with the right type. Pay attention to the two *_blocker_reason fields, they’re populated only when their respective eligibility booleans are false, so on a fully eligible order they’ll be empty strings or null. Both should still pass through the response without throwing.

2b. Cancel Order (destructive)

1

Create the action

  • Name: Cancel Order
  • Description: Cancels an order that is eligible for cancellation. Returns the confirmation id and refund amount. Will error if the order is already shipped; downstream Rulebook must check eligible_for_cancel before invoking.
2

Define the inputs

3

Define the outputs

4

Add the Data Step

  • Step Name: Cancel via OMS
  • Method: POST
  • URL: https://api.yourshop.com/orders/${order_id}/cancel
  • Headers: {"x-api-key": "your-oms-api-key"} (or your OMS’s native scheme).
  • Body: {"reason": "${reason}", "idempotency_key": "${order_id}-cancel"}
  • Save From Response: {"cancel_confirmation_id": "id", "refund_amount": "refund.amount", "refund_eta_days": "refund.eta_days"}
5

Test, then save

Play the Action against an actual cancellable test order. Confirm 200 and that all three output fields populate. The refund flow on your OMS may be async, refund_eta_days of 5-10 is normal for credit-card refunds.

2c. Update Shipping Address (destructive)

1

Create the action

  • Name: Update Shipping Address
  • Description: Updates the shipping address on a pre-shipment order. Returns the new estimated delivery date. Will error if the order is already shipped; downstream Rulebook must check eligible_for_change before invoking.
2

Define the inputs

3

Define the outputs

4

Add the Data Step

  • Step Name: Update address via OMS
  • Method: PATCH
  • URL: https://api.yourshop.com/orders/${order_id}/shipping_address
  • Headers: {"x-api-key": "your-oms-api-key"} (or your OMS’s native scheme).
  • Body:
  • Save From Response: {"change_confirmation_id": "id", "new_expected_delivery": "shipping.estimated_delivery"}
5

Test, then save

Play with a pre-shipment test order and a deliberately different address. Verify the OMS records the new address and recalculates the delivery date.
For the full reference on Input/Output schemas, chaining Tools, and the workspace-vs-rule scoping model, see API Setup → Actions. Actions are workspace-level, there’s no per-agent toggle, an Action becomes effective for an agent only when a Rulebook rule referencing it is assigned to that agent.

Step 3: Configure the Rulebook rule

This is where the Fallback-root pattern lives. One rule, three branches, one read-only Tool shared across all branches.
Tree reference. Final shape you’ll build below:
The pattern at each branch: gate on intent, run the read-only Lookup Order, then for destructive branches, gate on eligibility and either run the destructive Tool or explain the blocker. The two destructive branches use a nested Fallback to express the “if eligible, do the work; else explain” alternative.
1

Create a new rule

On Automations → Rulebook, click + Create Rule. Set the Name to Order status and changes.
2

Write the Description

The Description routes the LLM. For a multi-intent rule like this, you describe the rule’s scope (orders) rather than a single intent:
“Use this rule when the customer asks about the status of an order they placed, wants to cancel an order, or wants to change the shipping address on an order. Example queries: ‘where is my order?’, ‘has my order shipped?’, ‘can you cancel order #12345?’, ‘I need to change my shipping address’, ‘can you redirect my package?’. Do not use this rule for: refunds on already-delivered orders (use Refund Request), returns (use Return Request), or general product questions (let the knowledge base handle these).”
The negative examples matter, e-commerce has many adjacent rules (returns, refunds, exchanges), and you want the Planner to route to the most specific one.
Because this rule covers three intents, the Topic tag group must distinguish between them. The Check at the top of each branch reads a specific Topic value, so your Topic group needs values like Order Status, Cancel Order, Change Order configured under Configuration → Tags. Without the granular tags, the LLM routes correctly but all three branches’ intent Checks read the same broad tag and the wrong branch may fire.
3

Change the root from Steps to Fallback

Every new rule starts with a Steps root. For this rule, change it to Fallback. Click the root node in the editor; the configuration panel offers a node-type switcher. Switch to Fallback. The semantics shift: now children evaluate in order until one succeeds.
4

Build the Status branch

Click + Add step on the Fallback root. Pick Steps. Inside that Steps, add four children:
  • Check Intent is status: in the field picker, expand Tag Groups → pick your routing group (Type of Issue or your custom Topic group). Operator Equals. Value Order Status. The three subsequent branch Checks follow the same pattern with different values.
  • Read Extract order_id:
    • field order_id, type String, required.
    • Instructions: “Extract the order id mentioned by the customer (looks like #12345, order #12345, or just a number). If no order id is mentioned and the customer has more than one recent order, return the first id from recent_orders (the most recent). If the customer has no recent orders, fail the Read.”
  • Tool Lookup Order: bind order_id input to the Read’s order_id.
  • Reply:
    “Tell the customer their order status. Include: the order’s current shipping status (shippingstatus),theexpecteddeliverydate({shipping_status}), the expected delivery date (), and the tracking link (trackingurl)ifit′snotempty.Ifshippingstatusis′delivered′,saysocheerfullyandmentionthedate;if′notshipped′,letthemknowit′sstillbeingprocessed;if′intransit′or′outfordelivery′,sharethetrackinglink.Mentiontheitemsbeingshipped({tracking_url}) if it's not empty. If shipping_status is 'delivered', say so cheerfully and mention the date; if 'not_shipped', let them know it's still being processed; if 'in_transit' or 'out_for_delivery', share the tracking link. Mention the items being shipped () so they’re certain it’s the order they meant.”
Voice control lives elsewhere. Reply instructions across all three branches are for content directives (“include the tracking link”, “explain the blocker”). For voice (tone, register, brand vocabulary), edit your agent’s Main Guidelines → Tone, those rules apply to every reply your agent sends. The cancel-blocked and change-blocked Replies in the destructive branches in particular lean on your agent’s general escalation guidance from Planning Prompt → Escalation Topics, make sure that’s tuned for cases like “customer is very upset about a delivery delay.”
5

Build the Cancel branch

Add another Steps to the Fallback root. Inside:
  • Check Intent is cancel: Topic Equals Cancel Order.
  • Read Extract order_id and reason:
    • field order_id, type String, required (same instructions as the Status branch).
    • field reason, type String, optional. Instructions: “Extract the reason the customer wants to cancel, if they volunteer one. Examples: ‘changed my mind’, ‘found it cheaper elsewhere’, ‘shipping is taking too long’, ‘ordered by mistake’. Empty string if no reason given.”
  • Tool Lookup Order: bind order_id to the Read.
  • Fallback: this is the nested “if eligible, do; else explain” fork. Inside the Fallback, add:
    • Steps (cancel-allowed):
      • Check Eligible to cancel: eligible_for_cancel Equals True.
      • Tool Cancel Order: bind order_id (Read), reason (Read).
      • Reply:
        “Confirm the cancellation. Tell the customer their order has been cancelled and a refund of refundamountwillbeprocessedwithin{refund_amount} will be processed within business days. Share the confirmation id ($) for their records. Reassure them and ask if there’s anything else they need help with.”
    • Reply (cancel-blocked):
      “Politely explain that the order can no longer be cancelled because $. Acknowledge the inconvenience, and offer the next-best option: if the order hasn’t been delivered yet, suggest waiting and then initiating a return; if it has been delivered, walk them through the return process. Mention that returns typically refund within 5-10 business days.”
6

Build the Change-address branch

Add a third Steps to the Fallback root. Inside:
  • Check Intent is change: Topic Equals Change Order.
  • Read Extract order_id:
    • field order_id, type String, required.
  • Tool Lookup Order: bind order_id to the Read.
  • Fallback (nested if-else):
    • Steps (change-allowed):
      • Check Eligible to change: eligible_for_change Equals True.
      • Form New shipping address:
        • Title: New shipping address.
        • Fields: shipping_street, shipping_city, shipping_postal_code, shipping_country (all String, all required). Pre-fill from the order’s current shipping_address.* fields if your Lookup Order Action exposes them, the customer typically only changes one or two fields, so a pre-filled form drops friction.
        • Form Error: shipping_postal_code invalid for country.
      • Tool Update Shipping Address: bind order_id, plus the four shipping_* fields from the Form.
      • Reply:
        “Confirm the address change. Tell the customer the new address is locked in and the order will now arrive by newexpecteddelivery.Sharetheconfirmationid({new_expected_delivery}. Share the confirmation id (). Mention that no further action is needed; the carrier will route to the new address automatically.”
    • Reply (change-blocked):
      “Politely explain that the shipping address can no longer be changed because changeblockerreason.Iftheorderisalreadyintransit,mentionthattheycanrefusedelivery(thecarrierwillreturnittous,andwe′llrefundorreshiptothenewaddress)orcontactthecarrierdirectlywiththeirtrackinglink({change_blocker_reason}. If the order is already in transit, mention that they can refuse delivery (the carrier will return it to us, and we'll refund or reship to the new address) or contact the carrier directly with their tracking link (). Apologize for the inconvenience.”
Channel notes. The Form node renders inside the Fini widget. On email-only deploys (most Salesforce, Zendesk-email-only), use four sequential Read + Reply pairs instead of the Form (one field per pair). The Rulebook reference’s address-change example shows the email-channel pattern.
7

Build the disambiguation fallback

Add the final child to the Fallback root. This child fires only when all three intent Checks above failed (the LLM routed to this rule but the Topic tag didn’t match any of the three values). Pick Reply:
“You routed here without a clear intent. Acknowledge that you can help with orders and ask what they’d like to do: check status, cancel, or change shipping address. Mention their recent orders ($) if available to help them give you context.”
This is the safety valve. In normal operation it almost never fires (the Planner’s routing is accurate enough that the three intent Checks usually catch the message); when it does, you’ve avoided a silent failure.
8

Assign to your agent

Under Assigned Bots, pick the same agent you attached Customer Identity to. A rule with no agents assigned never runs.
9

Test each branch before publishing

Open ▶ Test Run. Test all three branches plus the blocked variants:Status branch, in_transit order:
Cancel branch, eligible order:
Cancel branch, blocked (already shipped):
Change-address branch, eligible:
Disambiguation fallback (no Topic match):
A successful trace for each branch:
AI Steps panel showing five execution traces stacked. Trace 1, status branch on order 1002, four green nodes ending in a Reply that interpolates tracking_url and expected_delivery. Trace 2, cancel branch eligible, all six green nodes ending in Cancel Order success and a refund confirmation Reply. Trace 3, cancel branch blocked, the eligible_for_cancel Check shows red, the Fallback advances to the cancel-blocked Reply with a green dot interpolating cancel_blocker_reason. Trace 4, change-address branch eligible, six green nodes ending in Update Shipping Address success and a Reply with new_expected_delivery. Trace 5, disambiguation Fallback, three intent Checks show red and the final Reply fires green.
What to verify for each trace:
  1. The right branch’s intent Check is the only one of the three that passes (Test 5 shows all three red, and the final Reply fires instead).
  2. Lookup Order runs successfully on every test where an intent matched.
  3. The eligibility Checks pass or fail correctly based on the test order’s state.
  4. Destructive Tools (Cancel Order, Update Shipping Address) only fire when their eligibility Check passes.
  5. Replies interpolate the right variables for the right path.
10

Publish

Save commits the draft; Publish promotes it to live.
  • The Status branch fires on cancel intents (or vice versa): your Topic tag group has overlapping values, or the AI Instructions on the Topic tag group aren’t specific enough about “how to distinguish a status check from a cancel request”. Refine the Topic tag’s AI Instructions, not the Rulebook description.
  • The disambiguation fallback fires too often: the LLM is routing to your rule on borderline messages where the customer didn’t really ask about orders. Tighten the rule’s Description with more negative examples. The Disambiguation Reply is your safety net, but it should be a rare path, not a common one.
  • The Cancel Tool runs on already-shipped orders: the eligibility Check is missing or wired against the wrong field. The Check should read eligible_for_cancel Equals True. If your OMS doesn’t compute this server-side, the Check would have to combine multiple raw fields, which is error-prone, fix it upstream in your OMS.
  • The Form pre-fill shows blank fields: the Form’s pre-fill bindings reference fields the Lookup Order Action doesn’t expose. Either extend Lookup Order to return shipping_street, shipping_city, etc., or drop the pre-fill and accept the customer retyping the whole address.
For the full reference on Fallback semantics, nested composites, and tree variables, see Automations → Rulebook.

Step 4: Optional Reply Behavior gating

Most e-commerce deployments don’t gate this rule’s replies, the customer-facing replies are safe to send directly. The complication when you do want to gate is that Reply Behavior’s condition builder offers three field categories only: System fields, Tag Groups, User Attributes. There’s no “Tree Variables” category, the values your Lookup Order Tool returns (total, eligible_for_cancel, etc.) live only inside the Rulebook tree for that one execution; Reply Behavior runs as a separate decision layer and can’t see them. So the only way to gate on order-specific properties via Reply Behavior is to surface them via a User Attribute that fetches the customer’s most-recent-order context on every message (or via a tag group that the LLM populates based on the order). Two patterns that work:
  • Customer-type gating. Expose customer_type (e.g., b2c, b2b, wholesale) on your Customer Identity attribute. Configure under Automations → Reply Behavior → Internal Comment: condition Tag Groups → Type of Issue Equals Cancel Order AND User Attributes → customer_type Equals b2b. Every B2B cancellation routes to internal review regardless of order details.
  • Pre-emptive tagging. Configure a custom tag group called Order Risk whose AI Instructions tell the model to apply high_value when the conversation references an order over your threshold (the model can infer from the customer’s wording or from prior messages). Gate Internal Comment on that tag instead of on total.
The Reply Behavior gate doesn’t stop the destructive Action from firing, the cancel or address change still goes through. Reply Behavior only controls whether the customer sees the agent’s confirmation. If you need to hold the Action itself for human approval, split the rule into two as covered in the Card replacement walkthrough’s Reply Behavior step.

Verify the flow end-to-end

With everything published, run the five scenarios above as real conversations on a deploy channel that supports Forms (the widget). Use a test customer whose recent orders cover all the shipping states you need (one not_shipped, one in_transit, one delivered). The five tests:
  1. Status, in_transit order. “where is my order #1002” → direct reply with tracking link and expected delivery.
  2. Cancel, eligible order (not_shipped). “cancel #1003” → cancel Tool fires; reply confirms the refund amount and ETA.
  3. Cancel, ineligible order (in_transit). “cancel #1002” → cancel-blocked Reply explains the order has already shipped and offers the return path; no Tool fires.
  4. Change address, eligible. “change shipping address on #1003” → Form renders; customer fills it; address-update Tool fires; reply confirms new ETA.
  5. Change address, ineligible (out_for_delivery). “redirect my package #1002” → change-blocked Reply explains the carrier handoff and offers refuse-delivery; no Tool fires.
Verify in the AI Steps trace. Open each test conversation in the Inbox. Each trace should show only the relevant branch’s nodes green; the other two branches’ intent Checks should never have run (the Fallback short-circuits once a branch succeeds).

Lock it down with a regression test

Multi-intent rules are uniquely fragile: a Description tweak that helps one intent can hurt another. Add this rule to your Test Suite with at least three scenarios per branch (eligible, ineligible, and one edge case per intent). Bind Tool use correctness as the judge for “did the right Tool fire on the right input?”, or pair it with Goal resolution to also grade whether the customer ended up with their stated outcome. Scenarios run as simulated multi-turn conversations; write expected outcomes that describe the arc, not verbatim wording. Re-run the set every time you change the Description, the routing tag’s AI Instructions, or any Action’s Data Step.
Test Suite runs hit your real APIs. Each scenario replay actually invokes the Tools, the Cancel Order and Update Shipping Address Tools will fire your OMS for real on whatever test customer the scenario uses. Point the Actions at a sandbox OMS while iterating on the test set, or accept that each run produces real cancels and address changes.
After a week in production, open Analytics → Overview and find your three intents in the Category breakdown table (Analytics doesn’t expose a generic tag filter; intent-level metrics live in the Category breakdown view on each page). Each row shows volume, AI resolve rate, and escalated rate, the answers will tell you which branch deserves the next refinement. Click into a row to spot-check conversations on that intent. If escalations are climbing on a specific intent, the most-likely Escalation Reason tags to filter for are Missing API Access (when the OMS rejected a Tool that should have succeeded) and Ambiguous or Unclear Input (when the disambiguation Reply fired too often).
Knowledge handles the fall-through paths. When all three intent Checks fail (the disambiguation Reply fires), or when eligible_for_cancel / eligible_for_change is false and the blocked-Reply path runs, the agent leans on Knowledge to back up the prose. For this walkthrough to feel complete, make sure your Articles cover: the return policy your cancel-blocked Reply mentions, the carrier-redirect process the change-blocked Reply offers, and the general “what statuses mean” customer-facing explanations. Background AI watches conversations and proposes Articles for gaps it detects, check the Review queue weekly.

What to vary

Common adaptations once the basic rule is in place:
  • Add a Return Request branch. Extend the Fallback root with a fourth Steps for Topic Equals Return Request. Look up the order, check eligible_for_return (a new output from Lookup Order), and either spawn a Form to collect the return reason and items or post an internal comment for the returns team.
  • Add a Reorder branch. “Order the same thing again” is a common intent. Add a fifth Steps that looks up the order, presents a “Confirm reorder?” Form summarizing the items, and posts a new order via a Create Order Action.
  • Pre-emptive proactive notifications. Add a Check on expected_delivery against today’s date. If the order is overdue by more than 2 days, escalate to Internal Comment so a human investigates with the carrier before the customer escalates themselves.
  • Multi-order disambiguation. When the customer asks “cancel my orders” (plural) and recent_orders has more than one, the Read won’t extract a single id. Add a Fallback at the top of each destructive branch: if the Read fails, fall back to a Reply that lists the order ids and asks the customer to specify (the customer’s next message goes through the rule again with the chosen order_id populated).
  • Tag the ticket in Gorgias. If your support deployment is on Gorgias, configure Tag Sync so the Cancel Order / Change Order Fini tags map to Gorgias tags (e.g., cancelled-via-ai, address-changed-via-ai). Your Gorgias views and macros can then triage off them. Tag Sync is currently a Gorgias-only feature; other integrations will get it as it rolls out.
  • Carrier-aware change-blocker messaging. Some carriers (UPS, FedEx) let you redirect a package mid-transit via their own portal even after the OMS no longer accepts changes. Extend the change-blocked Reply with carrier-specific instructions: “Your package is with UPS now. You can redirect it through UPS My Choice using the tracking number above.”
The pattern (single Lookup Action shared across branches → gated destructive Tools → blocked-path Replies that surface the actual blocker reason) generalizes to most e-commerce workflows. Returns, exchanges, partial refunds, and re-shipments all follow the same shape: lookup, check eligibility, branch into do-it or explain-why-not.

Troubleshooting

Stages where this walkthrough commonly gets stuck.
Your Description or the Topic tag’s AI Instructions overlap with adjacent rules. Tighten both. The Description’s “do not use this rule for” section should explicitly call out refunds and returns by name.
The Topic tag for this conversation isn’t set to the value the Check reads. Topic tagging happens after the model classifies the conversation; on synthetic tests, you must inject the Topic in the Custom JSON. In production, check the Output Tag Selection panel of AI Steps, if Topic isn’t being applied as expected, refine the Topic tag’s AI Instructions (not the rule).
The customer didn’t quote an order number, and the fallback to recent_orders[0] isn’t working. Two possible causes: (1) recent_orders is empty for this customer, the fallback to “most recent” gives nothing; (2) your Read instructions don’t reference the recent_orders attribute. Edit the Read’s instructions to explicitly say “if no order id was mentioned, return the first element of recent_orders”, and verify the attribute is fetched and visible at this point in the tree.
Click into the Check in the AI Steps trace, it shows the actual value it read. eligible_for_cancel may be coming back as false when you expected true, which means the OMS computed the policy differently than you expected. The fix is on the OMS side, not in Fini, refine the policy logic where it actually lives.
The Tool returned 200 but the side effect didn’t happen, usually means your OMS’s cancel endpoint is async and returns success on enqueue. Confirm with your OMS’s docs: are cancels synchronous or eventual? If eventual, the agent’s confirmation is “your cancel request was received”, not “your order has been cancelled”. Adjust the Reply wording.
The pre-fill is reading from a cached version of Lookup Order. Attributes and Actions don’t share a cache, but if you have a User Attribute exposing the order’s shipping address (instead of using the Action’s output), that attribute may be stale on the same message. Switch the pre-fill to read from the Tool’s output (Tool: Lookup Order → shipping_address.*) rather than a User Attribute.
None of the three intent Checks are passing. Three likely causes: (1) the Topic tag values in your tag group don’t match the Checks (case-sensitive: Order Status vs order status); (2) the AI Instructions on the Topic tag aren’t classifying customer messages into your three buckets; (3) the rule’s Description is overly broad so the Planner routes off-topic messages to this rule.

Cancellation flow

The simpler single-intent template, refunds, subscription cancellations, account closures.

Card replacement

The fintech companion: chained destructive Tools, Form for shipping, and a fraud-gating Reply Behavior.

Rulebook

Full reference for Fallback semantics, tree variables, and rule selection.

Actions

Designing read-only vs destructive Actions, chaining, and the workspace-vs-rule scoping model.

Attributes

Sources, fields, switches, and the per-message attribute chain.

Tags

Configure the Topic tag group whose three values gate the three branches.

Inbox

Open any conversation and read the AI Steps trace to see exactly which branch fired.

Test Suite

Regression-test all three branches before publishing changes; multi-intent rules are especially sensitive.