Skip to main content

Banking: the agent that asks permission

Track: Banking · In the room: Swedbank · Seed use case: dispute triage with a mandatory human approval gate.

Who this is for

Swedbank — Nordic retail and corporate banking, millions of card transactions a day, and a compliance function that has to be able to explain any automated decision to a regulator years after it was made.

Your current Cloudflare developer-platform footprint is essentially zero: no Workers, no D1, no Durable Objects, no AI products. That is not a gap to apologise for — it is the reason this track is the cleanest one in the room. There is no existing edge estate to fit around and no legacy deployment pipeline to negotiate with. What you deploy in the next hour is greenfield, runs in a sandbox account, and can be thrown away.

Synthetic data only

Everything below uses invented customers, invented card numbers and invented merchants. This is a shared Cloudflare account. Do not paste anything real into it — not a customer name, not a transaction, not an internal policy document.

Why this track is the one that matters

The event's theme is: agents don't fail on capability, they fail on accountability.

Banking is where that stops being a slogan. Nobody in a bank seriously doubts that a language model can read a dispute narrative and guess the right chargeback reason code. The blocker is the next question, and it is always the same question: who approved the refund, on what evidence, and can we reconstruct that decision eighteen months from now?

So in this track the approval gate is not a bonus point. It is the product. An agent that prepares a decision perfectly and then hands it to a named human, with its reasoning attached, is deployable. An agent that decides autonomously is a research project.

This is worth marks

Human approval gate is one of the five scored rubric dimensions: does your agent have a human approval gate? Every other track can earn those points as a bonus. In this track you cannot score industry relevance without them — a dispute agent with no gate is not a banking agent. Build the gate first and the rest of the hour is decoration.

Three agent use cases

1. Card dispute triage — the seed use case

  • Trigger. A customer files a dispute on a card transaction through the app. A webhook posts the claim, the transaction record, and the customer's free-text narrative.
  • What the agent decides. The dispute category (fraud, non-delivery, duplicate charge, subscription the customer forgot about), whether the evidence supports the claim, whether it falls inside the network's chargeback window, and a recommended outcome: refund now, request more evidence, or decline with a reason.
  • What it does — the tool. prepare_dispute_case assembles a case file: the extracted reason code, a plain-language summary, the specific evidence it relied on, the confidence score, and a draft customer message. Then it stops and waits.
  • Human in the loop? Mandatory, and this is the whole point. A dispute resolution moves money and creates a regulatory record. The agent does the ninety percent that is tedious — reading, categorising, checking windows, drafting — and a human does the one thing only a human can do, which is take responsibility. The approve/decline click and the approver's identity are written to the case file alongside the agent's original recommendation, so you can later audit not just what happened but where human judgement diverged from the model.

2. Fraud-alert first responder

  • Trigger. A transaction-monitoring rule fires overnight. Historically a queue that a human works through at 08:00, by which time the money has moved.
  • What the agent decides. Whether the alert pattern matches a known false-positive shape (the customer travels every March; this merchant always double-authorises) or looks genuinely novel. It ranks the queue instead of leaving it first-in-first-out.
  • What it does — the tool. triage_alert writes a priority and a one-paragraph rationale to each alert, and for the top band sends a single push notification asking the customer to confirm the transaction.
  • Human in the loop? Yes, but asymmetrically. Deprioritising an alert needs no approval because nothing irreversible happens. Blocking a card does, because a wrongly blocked card at a checkout is a worse customer experience than the fraud you prevented. Design the gate around irreversibility, not around risk appetite.

3. Complaint-to-policy researcher

  • Trigger. A complaint arrives that references an obligation — a fee that shouldn't have applied, a promised rate, a term the customer believes was breached.
  • What the agent decides. Which internal policy or product-terms document actually governs the complaint, and whether the customer's reading is defensible.
  • What it does — the tool. find_governing_policy retrieves the relevant clauses from an indexed corpus of product terms and returns them with citations, so the human handler reads the source rather than the model's paraphrase of it.
  • Human in the loop? Yes — and note that here the human is the consumer of the agent's work, not its approver. The agent never answers the customer. It makes a person faster. That is often the easiest agent to get past a risk committee, and a good second project.

The 60-minute cut

Build use case 1, and cut it down honestly. In sixty minutes you are building the decision-and-approval spine, not a dispute system.

In scope:

  1. Three or four hardcoded synthetic disputes in an array. No database of transactions, no webhook, no ingestion. A POST /dispute route with a JSON body is enough.
  2. One tool the agent can call that classifies a dispute and produces a case file.
  3. A Workflow that runs the classification, then pauses on step.waitForEvent().
  4. Two URLs — /approve/:id and /decline/:id — that send the event and unblock it. A plain HTML page with two buttons beats a nice UI you don't finish.
  5. Persist the decision and who made it.

Explicitly out of scope, and say so in your demo: authentication, real chargeback window logic, network reason-code mapping, notifications, and any kind of ledger. If you find yourself writing card-network rules, you have lost the hour.

The demo that wins. File a dispute. Show the agent's case file appearing in a pending state. Decline the one the agent recommended approving — then show the record containing both the agent's recommendation and your override. Sixty seconds, and it answers the only question your risk function will ask.

Primitives — exactly three

  1. Workflows. The approval gate needs to survive a human going to lunch. step.waitForEvent() pauses a Workflow for up to a year while consuming nothing, and every completed step.do() is checkpointed, so a retry never re-refunds a customer. A setTimeout, a queue with a visibility timeout, or a cron poller all get you an approximation of this and all of them lose state when something restarts.
  2. Durable Object state (the Agents SDK Agent class). The case file is the audit trail, and it must be strongly consistent — two handlers must never see different versions of the same dispute. An Agent instance is single-threaded per dispute with embedded SQLite via this.sql, which gives you serialisability for free. KV would give you eventual consistency, which for a money decision is the wrong trade.
  3. AI Search over your own documents. Grounding the classification in your own (synthetic) dispute-handling policy is what makes the output auditable — the case file cites a clause rather than asserting a conclusion. A larger model with a longer prompt is not a substitute: you cannot cite what you cannot retrieve.

Skip Browser Rendering here (no merchant sites to scrape in an hour) and skip schedules (your trigger is an event, not a clock). Both are the right call in other tracks, which is the point of picking deliberately.

Paste-ready starter prompt

Scaffold the starter first:

npm create cloudflare@latest -- team-banking-dispute-triage --template cloudflare/agents-starter
cd team-banking-dispute-triage

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 card dispute triage agent with a mandatory human approval gate.

1. Add a hardcoded array of 4 synthetic disputes to src/server.ts. Each has: id, amount in
SEK, merchant name, transaction date, and a free-text customer narrative. Make them
deliberately varied: one obvious fraud claim, one non-delivery, one duplicate charge,
and one that is really a forgotten subscription. Invented data only.

2. Add a tool to the agent called prepare_dispute_case. It takes a dispute id, and returns
a structured case file: category (one of fraud, non_delivery, duplicate, subscription),
a recommended outcome (refund, request_evidence, decline), a confidence number from 0 to
1, a two-sentence plain-language summary, and a draft message to the customer. Use a
Workers AI model for the classification and ask it for JSON.

3. Add a Cloudflare Workflow called DisputeReview in a new file. Its steps, each a
step.do():
a) classify — call the same classification logic and store the case file
b) await_approval — use step.waitForEvent() with a generous timeout to wait for an
event of type "dispute-decision"
c) record — persist the final outcome, the agent's original recommendation, and the
decision that actually came back, so an override is visible next to the original
Import WorkflowEntrypoint, WorkflowStep and WorkflowEvent from "cloudflare:workers".
Add the workflow binding to wrangler.jsonc.

4. Add three HTTP routes:
POST /dispute/:id starts a DisputeReview workflow instance, returns instance id
GET /review/:id a plain HTML page showing the case file and two buttons
POST /decide/:id sends the "dispute-decision" event with approved true or false
The review page must clearly show that nothing has happened yet and that a human
decision is required. Show the confidence score.

5. Store the case file and the final decision in the Agent's SQLite storage using this.sql,
keyed by dispute id, and expose a GET /cases route that lists them with both the agent's
recommendation and the human decision side by side.

Requirements: TypeScript. Route every Workers AI call through AI Gateway by passing
{ gateway: { id: "agenthack" } } as the options argument. Never auto-approve — if no human
decision arrives, the workflow must time out without refunding anything. Run
`npx wrangler types` after changing wrangler.jsonc. Then deploy with
`npx wrangler deploy` and give me the URL plus a curl command that walks the whole flow.

The Worker name team-banking-dispute-triage 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

Three things you would add next, in the order a bank would actually demand them.

  1. Identity on the approval, not just a boolean. Put Cloudflare Access in front of the review route so the approve action carries a verified identity from your IdP, and record that identity in the case file. "Approved by a human" is worth nothing to an auditor; "approved by a named person holding a specific role at a specific time" is the artefact. Add a second approver above a value threshold — a Workflow makes that a second waitForEvent(), not a rewrite.
  2. A real corpus, indexed properly. Replace the synthetic policy text with your actual dispute-handling procedures and product terms, and require every case file to cite the clause it relied on. Then measure: sample a hundred decisions, compare the agent's recommendation against what the human decided, and track the divergence rate over time. That number is your business case, and it is also your early warning when a policy changes and the agent doesn't notice.
  3. Reversibility as an architectural property. Classify every action the agent can take as reversible or irreversible, and gate only the irreversible ones. Prioritising a queue, drafting a message and requesting evidence need no gate. Moving money and blocking cards always do. Getting that boundary explicit is what lets you widen automation later without reopening the governance conversation each time.

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