Banking: the agent that asks permission
Track: Banking · In the room: pre-assigned teams · Seed use case: dispute triage with a mandatory human approval gate.
Who this is for
Teams from Nordic retail and corporate banking — millions of card transactions a day, and a compliance function that must be able to explain any automated decision to a regulator years later. Your Cloudflare developer-platform footprint is essentially zero: no Workers, D1, Durable Objects, or AI products. That's not a gap — no existing edge estate to fit around, no legacy pipeline to negotiate with. What you deploy this hour is greenfield, runs in a sandbox account, and can be thrown away.
Everything below uses invented customers, card numbers, and merchants. This is a shared Cloudflare account — do not paste anything real: no customer name, transaction, or internal policy document.
Why this track is the one that matters
The event's theme: agents don't fail on capability, they fail on accountability.
Banking is where that stops being a slogan. Nobody doubts a model can read a dispute narrative and guess the right chargeback reason code. The blocker is always the same question: who approved the refund, on what evidence, and can we reconstruct that decision eighteen months from now?
So the approval gate isn't a bonus point — it's the product. An agent that prepares a decision perfectly and hands it to a named human, reasoning attached, is deployable. One that decides autonomously is a research project.
Human approval gate is one of the five scored rubric dimensions: does your agent have a human approval gate? Other tracks earn those points as a bonus; here you can't score industry relevance without one — a dispute agent with no gate isn't a banking agent. Build the gate first; the rest of the hour is decoration.
Three agent use cases
1. Card dispute triage — the seed use case
- Trigger. Customer files a dispute in-app; a webhook posts the claim, transaction record, and free-text narrative.
- Decides. Dispute category (fraud, non-delivery, duplicate, forgotten subscription), whether evidence supports the claim, whether it's inside the chargeback window, and a recommended outcome — refund, request more evidence, or decline with a reason.
- Tool.
prepare_dispute_caseassembles a case file: reason code, plain-language summary, evidence relied on, confidence score, draft customer message. Then 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 tedious ninety percent — reading, categorising, checking windows, drafting — a human takes responsibility. The approve/decline click and approver identity are written to the case file next to the agent's recommendation, so you can audit where human judgement diverged from the model.
2. Fraud-alert first responder
- Trigger. A transaction-monitoring rule fires overnight — historically a queue worked at 08:00, by which time the money has moved.
- Decides. Whether the alert matches a known false-positive shape (customer travels every March; merchant always double-authorises) or looks genuinely novel. Ranks the queue instead of leaving it first-in-first-out.
- Tool.
triage_alertwrites a priority and one-paragraph rationale per alert; for the top band, sends a push notification asking the customer to confirm. - Human in the loop? Yes, asymmetrically. Deprioritising needs no approval — nothing irreversible happens. Blocking a card does: a wrongly blocked card is a worse experience than the fraud prevented. Gate around irreversibility, not risk appetite.
3. Complaint-to-policy researcher
- Trigger. A complaint references an obligation — a fee that shouldn't have applied, a promised rate, a term the customer believes was breached.
- Decides. Which internal policy or product-terms document actually governs the complaint, and whether the customer's reading is defensible.
- Tool.
find_governing_policyretrieves relevant clauses from an indexed corpus of product terms with citations, so the human reads the source, not a paraphrase. - Human in the loop? Yes — 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. Often the easiest agent to get past a risk committee, and a good second project.
The 60-minute cut
Build use case 1: the decision-and-approval spine, not a dispute system.
In scope:
- Three or four hardcoded synthetic disputes in an array. No transaction database, webhook,
or ingestion — a
POST /disputeroute with a JSON body is enough. - One tool that classifies a dispute and produces a case file.
- A Workflow running the classification, then pausing on
step.waitForEvent(). - Two URLs —
/approve/:idand/decline/:id— that send the event and unblock it. A plain HTML page with two buttons beats an unfinished nice UI. - Persist the decision and who made it.
Out of scope, and say so: authentication, real chargeback window logic, network reason-code mapping, notifications, any kind of ledger.
The demo that wins. File a dispute, show the case file pending, decline the one the agent recommended approving — then show the record with both the recommendation and your override. Sixty seconds, and it answers the only question your risk function will ask.
Primitives — exactly three
- Workflows. The approval gate must survive a human going to lunch.
step.waitForEvent()pauses a Workflow for up to a year at no cost; every completedstep.do()is checkpointed so a retry never re-refunds a customer. AsetTimeoutor cron poller only approximates this, and loses state on restart. - Durable Object state (the Agents SDK
Agentclass). The case file is the audit trail and must be strongly consistent — two handlers must never see different versions of the same dispute. AnAgentinstance is single-threaded per dispute with embedded SQLite viathis.sql, giving serialisability for free; KV's eventual consistency is the wrong trade for a money decision. - AI Search over your own documents. Grounding the classification in your own (synthetic) policy is what makes output auditable — the case file cites a clause instead of asserting a conclusion. A larger model can't substitute: you can't cite what you can't retrieve.
Skip Browser Rendering (no merchant sites to scrape) and schedules (your trigger is an event, not a clock) — right calls elsewhere, not here.
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 demand them.
- Identity on the approval, not just a boolean. Put Cloudflare Access in front of the
review route so approval carries a verified IdP identity, recorded in the case file.
"Approved by a human" is worth nothing to an auditor; "approved by a named person in a
specific role at a specific time" is the artefact. A second approver above a threshold
is just a second
waitForEvent(), not a rewrite. - A real corpus, indexed properly. Replace synthetic policy text with actual dispute-handling procedures and product terms; require every case file to cite the clause relied on. Sample a hundred decisions, compare agent recommendation against human decision, and track divergence over time — that's your business case and your early warning when policy changes and the agent doesn't notice.
- Reversibility as an architectural property. Classify every action as reversible or irreversible and gate only the irreversible ones. Prioritising, drafting, requesting evidence need no gate; moving money and blocking cards always do. An explicit boundary lets you widen automation later without reopening the governance conversation.
Next: Level Up explains each primitive, the cheat sheet has the snippets.