Inspiration

Every community tech event has an EO, an event organizer, usually a volunteer. They pay the venue deposit on their own card, chase sponsors for invoices, collect receipts from other volunteers and pay everyone back at two in the morning, with a spreadsheet as the only record. We wanted to hand that work to an AI agent, but an agent that moves money is only useful if the organizer can trust it completely. So we set ourselves one rule: the agent can propose anything, but it can never break the organizer's rules, and PayPal itself enforces the stop.

The name comes from the Leonids, the meteor shower that falls from the constellation Leo. The highlighted "eo" in l*eo*nid is the event organizer, and Leo, our agent, is the EO's best friend.

What it does

Leonid runs an event's money on PayPal, from sponsors in to people paid out:

  • Leo, the agent, plans the event in a chat. Ask for a venue for 200 people and Leo works out what to book, scouts venues that fit, and turns your pick into a plan with a total in US dollars.
  • Rules are code, not prompts. The organizer sets a max spend. Every payment passes a deterministic rules check before any money moves, and the result and reason go to an audit log.
  • The agent wallet pays. Within the rules, the plan is paid from the organizer's vaulted PayPal wallet, with a PDF invoice. Over the max spend, it is blocked, with the reason on screen.
  • A PayPal-enforced kill switch. One click deletes the vault payment token at PayPal, so the next payment fails at PayPal itself. There is no fallback path.
  • Money in: sponsor invoices through PayPal Invoicing, marked paid only by PayPal's webhooks, with reminders sent by a Render Workflow.
  • Money out: volunteers snap a receipt on their phone and are paid back through PayPal Payouts, within a daily cap; prize winners in several countries are paid in one batch.
  • One plan: a Bryntum Calendar where each purchase names the sponsor that funds it and whether they have paid.
  • A command center built on AG Studio, with a dashboard agent you can ask "Where are we overspending?"
  • A sponsor report for each sponsor, built by a Render Workflow and checked against PayPal's own records.

How we built it

A pnpm monorepo in TypeScript: a React + Vite web app, an Express 5 API, Postgres (Supabase) and a shared package of zod schemas that is the single contract between the four of us.

The heart is one pure function, check(spend, rules, budget), which returns allow, needs_approval or block. It has no I/O, no clock and no model, so any decision can be replayed from its audit row. The max-spend rule, for example, is simply

$$ \text{committed} + a > M \;\Rightarrow\; \texttt{block} $$

where $a$ is the new payment and $M$ the organizer's limit. Every money-moving call (vault charge, order capture, payout) is reachable only through an executor that runs this check first.

PayPal APIs we use: Log in with PayPal, Vault (the agent wallet), Orders, Invoicing v2, Payouts, Transaction Search and verified webhooks, split across two sandbox apps so the treasury and the agent's spending never share credentials. Leo runs a tool loop through OpenRouter; the model can call planning and drafting tools but holds no money tools. Background jobs run on Render Workflows, and we used the APIMatic Context Plugin to check our PayPal request shapes against the spec.

The sponsor report attributes spend with code, not a model. A purchase funded by one sponsor counts in full; everything else is paid from the pooled income, each sponsor $s$ paying its share

$$ \text{attributed}_s = \text{spend} \times \frac{\text{cleared}_s}{\sum_k \text{cleared}_k} $$

with cents split by the largest-remainder method. On staging, Harbor Cloud's $\$3{,}000.00$ was $98.9\%$ of $\$3{,}033.21$ cleared, so it was attributed $\$1{,}384.67$ of $\$1{,}400.00$ spent, which we checked by hand against the ledger.

Challenges we ran into

  • Keeping a model away from money. Receipts, product pages and sponsor replies are untrusted text. A receipt is read into a fixed schema with the photo passed as data, so nothing written on it can change who is paid or how much.
  • Paying exactly once. Retries, double clicks and crashed requests must never invoice or charge twice. Each spend carries an idempotency key derived from what it pays for, and an invoice that was created but not sent is finished, never recreated.
  • Truth from PayPal, not from us. "Paid" comes only from webhooks. Transaction Search can lag about three hours, so we use it for reconciliation only. Its first live run found a real bug: three purchases were stored under their order id while PayPal lists the capture id.
  • Changing the product mid-build. Twice we removed a gate we had built: spending limited to cleared sponsor money, then a separate approval step. Each time the rules engine stayed the single place where limits live, which made the change safe.
  • Two Bryntum products in one app threw "loaded multiple times", which we solved with the thin packages.

Accomplishments that we're proud of

  • The kill switch works the way we promised: proven on staging, a payment succeeds while the wallet is active and is refused by PayPal after we revoke it.
  • A rules engine that a model cannot argue with, and an audit log that records input, reason, rule result and PayPal ID for every decision.
  • Sponsor reports that PayPal's own records confirm, purchase by purchase.
  • A product that feels finished: a design system, Leo as a mascot, motion that respects reduced-motion settings, and a mobile flow for volunteers.

What we learned

  • Put determinism where the money is and let the model do the language. The best agent features came from narrowing what the model is allowed to touch.
  • PayPal details matter: custom_id is the field that reconciles an order with your own records, an order opened without the PayPal button needs a return_url, and webhooks, not polling, are the source of truth.
  • A shared typed contract lets four people build in parallel without breaking each other.

What's next for Leonid

  • Rules in plain English: the organizer types a rule, the model drafts it, the code enforces it.
  • Agent purchases for swag, prizes and supplies, through the same rules and wallet.
  • Late-delivery recovery: tracking that flags a late order and lets Leo refund, reorder and replan.
  • More currencies, and live PayPal keys after a security review.

Built With

Share this project:

Updates

Submission history