💫 Inspiration

Small businesses have finance work and often nobody to do it or have difficulty understanding the process. An invoice goes 30 days overdue, a vendor emails saying they changed banks, and the owner deals with it at night. We wanted AI agents to take that work on, but an agent that is confidently wrong can pay the wrong account.

So we built around one rule. Claude talks, code decides.

💰 What it Does

Backrooms is a 3D back office for a fictional company, Northfield Studio. Seven AI agents work the finance seats, and you can watch them pass cases between desks in real time. Dana is the controller and the only one who can approve anything outside policy.

The main demo is a real phone call.

  • Invoice 1042 is $4,950 overdue, so the Collections agent calls the client through a Cartesia voice agent.
  • It discloses that it is an AI and the call is recorded, then confirms identity before naming any amount.
  • The client asks for 7 payments with 15% up front. That is past the agent's limit, so it holds the call until Dana approves with her passkey.
  • The client pays the first $743 into a Capital One Nessie sandbox account, and the Reconciler matches it to the invoice.

Everything lands on the audit wall. Each decision records the rule that fired and its receipt, and the entries are hash chained so edits show. Ask why on any entry and Claude answers with citations to real entries. An answer that cites a fake entry gets rejected.

Payables and the Attack Lab. Vendor bills go through the inbox, and policy holds duplicates, missing POs, over-limit amounts and bank changes for Dana. In the Attack Lab, a lookalike email from acme-supp1y.com asks to change Acme Supply's bank account. Policy escalates it with LOOKALIKE_DOMAIN and BANK_CHANGE_HOLD, and zero payment attempts are made. Vendors can also be paid over Coinbase x402 with test USDC on Base Sepolia.

🔨 How We Built It

  • Agents. Claude Agent SDK, with seven roles sharing one dispatcher and one handoff store.
  • Policy. A pure TypeScript engine returns ALLOW, DENY or ESCALATE with rule ids like MAX_INSTALLMENTS and NO_PO. Emails, invoices and anything a caller says are treated as data and can never grant an approval.
  • Voice. Every tool call from the Cartesia agent hits our server, which checks it against policy and the call's state. Calls only go to an allowlisted number with recorded consent.
  • Approvals. WebAuthn passkeys verified on the server, so neither a model nor a voice saying yes can approve a plan.
  • State. SpacetimeDB holds the shared state, and the Next.js and React Three Fiber office subscribes to the same tables the agents write to. In hosted tests 20 of 20 updates arrived within a second.
  • Checks. CRS sandbox records and our Apify actor, Client Health Check, run before a call or payment. An NVIDIA GX10 runs local models for routine extraction.

We are four people, and most of the code was written by coding agents that coordinated on one GitHub issue we used as a message board.

📊 Challenges we Ran Into

  • A live call waiting on a human. Approval can mean close to a minute of silence, so we wrote our own hold message and had the agent check back every few seconds.
  • Every call failing on demo morning. The identity tool kept coming back "could not be reached." Our checks passed because they ran inside Tailscale, but Funnel had stopped taking outside connections after a network change. Now we test from outside before every take.
  • Ask why throwing out good answers. Claude cited [entry:33] while the validator expected [entry:audit:33]. We added an alias that still rejects citations to entries that do not exist.
  • Being honest about what is real. CRS business credit and OFAC were not in our sandbox, so they show as unavailable. Nessie is a sandbox and x402 runs on testnet, and the UI labels both.

🥇 Accomplishments that We're Proud of

A real call went end to end, from the AI disclosure to Dana's passkey to a paid first installment reconciled against invoice 1042. Our fixture evaluation matched the answer key on 34 of 34 scenarios, and our red-team cases never got a payment through. Connecting every piece of the system was a challenging endeavor but one we're proud of.

📦 What We Learned

Money decisions have to live in code. Claude was good at the conversation and at explaining decisions, and every time we let a model decide something about money we found a way it could go wrong.

🌎 What's Next for Backrooms

We want real bank accounts and payments behind the same policy engine and passkeys, and we want an owner to be able to connect their own inbox. We also noticed the Collections agent should ask for an end date when a client leaves it out.

Built With

+ 3 more
Share this project:

Updates

Submission history