Skip to main content

Retail and Commerce: the agent that notices

Track: Retail and Commerce · In the room: IKEA, Boozt, Electrolux · Seed use case: delivery-exception agent — detect, decide, compensate, notify.

Who this is for

  • IKEA — the deepest Cloudflare developer-platform footprint in the room: Workers, KV and Stream in real production use. You already know how to ship a Worker, so today is not about the platform. It is about the fact that none of that estate has an agent in it yet.
  • Boozt — Workers in production, a fashion e-commerce operation where the delivery promise is the brand, and returns volume that makes every exception expensive.
  • Electrolux — barely any developer-platform usage today, but appliance delivery is the hardest logistics problem on this list: heavy goods, two-person delivery slots, installation appointments, and a customer who has taken a day off work.

The shared pain is not "we cannot detect a late parcel". Everybody's dashboards detect late parcels. The pain is that detection produces a queue, the queue is worked by humans in office hours, and by the time somebody gets to row 400 the customer has already tweeted.

Three agent use cases

1. Delivery-exception agent — the seed use case

  • Trigger. A scheduled sweep, every few minutes, over orders whose promised delivery window has passed without a delivered scan. No human opens a dashboard.
  • What the agent decides. Whether this is a real exception or noise (a carrier scan that is merely late, a weekend, a pickup-point collection the customer hasn't made yet); how bad it is; and what remedy fits — reship, refund the shipping fee, issue a goodwill credit, or simply tell the customer the truth early.
  • What it does — the tools. check_carrier_status reads the current tracking state, and resolve_exception applies the chosen remedy and drafts the customer message.
  • Human in the loop? Selectively, and on the money. Telling a customer their sofa is late needs no approval — being slow to say it is the actual failure. Issuing a goodwill credit above a threshold does. Put the gate on the irreversible action and let the rest run.

2. Returns-fraud triage

  • Trigger. A return request arrives, or a refund is claimed for an item marked delivered.
  • What the agent decides. Whether the pattern is ordinary (wrong size, changed mind) or looks like abuse — serial high-value returns from one address, a claim of non-delivery against a photographed doorstep handover, a returned box whose weight doesn't match.
  • What it does — the tool. assess_return produces a risk band with the specific signals it relied on, and routes low-risk returns straight to auto-approval.
  • Human in the loop? Yes, one-sided. Auto-approving a low-risk return is safe and is where all the volume is. Refusing a return accuses a customer of dishonesty and must be a human decision, every time, with the agent's evidence in front of them.

3. Product-content gap filler

  • Trigger. A new supplier catalogue lands, or a product page is published missing dimensions, materials, energy label or care instructions.
  • What the agent decides. Which attributes are missing, which can be recovered from the supplier's own published material, and which genuinely need a human to source.
  • What it does — the tool. enrich_product retrieves the supplier's public spec page, extracts the missing attributes, and writes them back as a draft with a source URL attached to each field.
  • Human in the loop? Yes, as a merchandiser review queue. Wrong dimensions on a wardrobe cause a failed two-person delivery, so nothing publishes unreviewed. Note the shape: the agent's output is a diff for a human to accept, which is a pattern you will reuse.

The 60-minute cut

Build use case 1. Cut it to the loop: detect on a clock, decide with a tool, gate the money, notify.

In scope:

  1. Six synthetic orders in an array, each with an order id, item, promised delivery date, a carrier tracking state, and an order value in SEK. Deliberately mix them: two fine, two genuinely late, one late-but-actually-at-a-pickup-point, one very high value.
  2. A scheduled sweep using the Agents SDK this.schedule() that runs the check. Set it to every 60 seconds so it visibly fires during your demo, and say out loud that production would be every 15 minutes.
  3. One tool that decides the remedy and drafts the customer message.
  4. A Workflow that applies the remedy, pausing for human approval when the credit exceeds a threshold you pick.
  5. A one-page view listing exceptions, remedies and their approval state.

Out of scope, and say so: real carrier integrations, order-management writes, sending actual email or SMS (log the message and render it), authentication, and multi-market logic. If you are writing a date-parsing library for carrier scan formats, you have lost the hour.

The demo that wins. Say nothing and let the schedule fire on stage. An exception appears without anyone clicking anything, the agent has already drafted the apology, and the high-value one is sitting in PENDING APPROVAL. Approve it live. The audience understands the whole architecture in fifteen seconds because they watched it happen by itself.

Primitives — exactly three

  1. Schedules (this.schedule() on the Agents SDK Agent class). Delivery exceptions are discovered by looking, not by being told — there is no webhook for "nothing happened". A scheduled task inside the agent keeps the detection logic next to the state it reads, so the agent that noticed is the agent that remembers. A separate cron Worker would work and would also split your state across two places.
  2. Browser Rendering. This is the honest retail primitive: the carrier's tracking page exists, the carrier's API does not, or not for you, or not this quarter. Rendering the page and reading it is not a hack, it is the shortest path to the data — and a screenshot of the tracking page attached to the case is evidence a human can check in one glance. Plain fetch gets you a JavaScript shell with no tracking state in it.
  3. Workflows. Compensate-then-notify is a multi-step action with money in the middle, and it must be exactly-once. step.do() checkpoints each step so a retry cannot double-credit a customer, and step.waitForEvent() holds the run open while a human decides on the big ones. A try/catch around three awaits gives you none of that.

Skip AI Search here — you are reasoning over order state, not over documents. Skip persistent Durable Object state as a named primitive, because you get it anyway by building on the Agent class; spend your third choice on something the audience can see.

Paste-ready starter prompt

Scaffold the starter first:

npm create cloudflare@latest -- team-retail-delivery-exception --template cloudflare/agents-starter
cd team-retail-delivery-exception

Then paste this into your coding agent:

I'm working in a fresh clone of Cloudflare's agents-starter template. The agent code is in
src/server.ts and the React client is in src/client.tsx. I have 60 minutes. Keep the
existing chat agent working — extend it, don't replace it.

Build a delivery-exception agent for an online retailer.

1. Add a hardcoded array of 6 synthetic orders to src/server.ts. Each has: orderId, item
description, orderValueSek, promisedDate, and carrierState (one of in_transit,
at_pickup_point, delayed, delivered). Make two of them clearly overdue, one that looks
overdue but is actually waiting at a pickup point, and one overdue order worth more than
15000 SEK. Invented data only.

2. Add a tool called assess_exception. Given an order, it uses a Workers AI model to decide:
isException (boolean), severity (low, medium, high), remedy (one of none, notify_only,
refund_shipping, goodwill_credit), a creditAmountSek number, and a two-sentence draft
message to the customer in English. Ask the model for JSON. An order sitting at a pickup
point is NOT an exception — the customer just hasn't collected it.

3. Use this.schedule() on the Agent to run a sweep every 60 seconds. The sweep calls
assess_exception for every order past its promised date that is not delivered, and stores
the result in the Agent's SQLite storage via this.sql, keyed by orderId. Make it
idempotent: re-assessing the same order must update the row, not add a second one.

4. Add a Cloudflare Workflow called ExceptionRemedy in a new file, with steps as step.do():
a) apply_remedy — record the remedy that was applied
b) await_approval — ONLY if creditAmountSek is over 1000, use step.waitForEvent() with
a generous timeout to wait for an event of type "remedy-decision". Below the
threshold, skip this step entirely and proceed.
c) notify — log the customer message (do not actually send email)
Import WorkflowEntrypoint, WorkflowStep and WorkflowEvent from "cloudflare:workers", and
add the workflow binding to wrangler.jsonc.

5. Add a tool called check_carrier_status that uses Cloudflare Browser Rendering to load a
public tracking-style page and capture a screenshot. Use https://example.com as the URL so
it works without a real carrier. Store the screenshot key with the exception. If Browser
Rendering fails for any reason, catch it, carry on, and mark the exception
"screenshot unavailable" — this step must never break the sweep.

6. Add routes:
GET /exceptions HTML page listing every exception: order, severity, remedy,
credit amount, and approval state. Show PENDING APPROVAL
prominently for anything waiting.
POST /decide/:orderId sends the "remedy-decision" event with approved true or false

Requirements: TypeScript. Route every Workers AI call through AI Gateway by passing
{ gateway: { id: "agenthack" } } as the options argument. Run `npx wrangler types` after
changing wrangler.jsonc. Then deploy with `npx wrangler deploy` and tell me the URL and how
to watch the schedule fire.

The Worker name team-retail-delivery-exception follows the shared-account rule <team_prefix>-<slug>. Eight teams deploy into one account today, so if the Scoping agent issued you a different prefix, use that one instead.

What "production" looks like

  1. Replace the array with your real event source, and keep the agent stateful per order. One agent instance per order — or per shipment — gives you a durable place to accumulate what happened, which is exactly what your support team currently reconstructs by hand across four systems. The interesting output is not the remedy, it is the timeline.
  2. Make the compensation policy data, not prose. Thresholds, remedy ladders and market-specific rules belong in a table the business can edit, with the agent choosing within the policy rather than inventing it. That is also the difference between a finance function that blocks this and one that sponsors it: they get to set the envelope.
  3. Close the loop on carrier performance. Every exception is a data point about a lane and a carrier. Aggregate them and the agent stops being a customer-service tool and becomes a procurement argument — which is where the money actually is, and it costs one extra table.

Next: the Level Up page explains each primitive, and the cheat sheet has the snippets. Both are in the sidebar.