Inspiration

AI agents are starting to buy things. But no small business owner will give an agent unlimited spending power, and an agent that can't pay is only a recommendation engine. Banks and card networks are asking the same question from the other side: when an agent pays, who authorized it, and under which rules?

We wanted to build that missing layer: spending rules the agent can't override, the owner pulled in only above a risk level they choose, and a record for every payment showing exactly why it was allowed. Our test user is a Nairobi plantain chip maker restocking packaging, plantain and cooking oil from a few regular suppliers. It's a small business where a bad order hurts, and where nobody has time to approve every purchase by hand.

What it does

When stock runs low, Shauriana's shop agent and a supplier agent (two Gemini models) negotiate the restock in structured offers over up to four rounds. Each side knows only its own limits, and plain code rejects any offer outside them. When they agree, the supplier sends a real PayPal invoice.

The shop agent then asks to pay. Its only payment tool sends the request through a policy engine: plain code with no AI, reading the owner's rules. It checks seven rules in order:

  • the payee is approved
  • the invoice matches the deal
  • the deal isn't already paid
  • the price is no more than 20% above the last one
  • the order is under $150
  • the last 7 days stay under $400
  • the owner approves anything over $80

Then one of three things happens:

  • Approved: a PayPal payout goes to the supplier, and the supplier marks its invoice paid.
  • Held: the owner sees an Approve / Decline card. No answer in 24 hours counts as no.
  • Blocked: no money moves, and the app says why in plain words.

Every offer, decision, approval and payment goes into an audit log. Every payment gets an authorization record: the rules in force, every check and its result, whether the policy or the owner approved it, and the PayPal IDs. The app works like a delivery tracker. You see what's happening right now, and the agents' chat and the policy checks stay folded away until you want them.

How we built it

  • PayPal sandbox, two business accounts. The supplier uses Invoicing v2 to create and send invoices and record payments. The shop uses Payouts v1 to pay and to poll the payout status. The deal ID is the payout's sender_batch_id, so PayPal itself refuses to pay the same deal twice, on top of our own duplicate rule.
  • Gemini (gemini-3.5-flash-lite, falling back to gemini-3.5-flash when it's busy) via the google-genai SDK. A Pydantic schema makes every agent turn a validated offer, never free text.
  • The policy engine is a small, pure Python function, tested at every boundary: $150.01 blocked and $150.00 allowed, 21% blocked and 20% allowed, $80.01 held and $80.00 approved, and supplier text saying "ignore your rules" changing nothing.
  • FastAPI, SQLite, plain JavaScript and AG Grid Community, hosted on Render, with a site-wide rate limit protecting the API key on the public demo.
  • 85 offline tests with fake PayPal and model clients, plus live sandbox runs for every feature.
  • Built solo by Nathan Achar, with AI coding assistance (Claude Code). The policy rules and their tests were reviewed by hand.

Challenges we ran into

  • Making "the agent can't override the rules" actually true. The payment decision and a "payment reserved" record are saved together, so even two simultaneous requests can't pay a deal twice. We also left out PayPal's idempotency header on purpose: with it, a repeated payout returns the original instead of being rejected.
  • The agents were too good at their jobs. In our price-spike scenario, the shop agent refused the supplier's new price, so the run ended with no deal and never reached the policy. Telling it in the prompt to accept on the last round worked only sometimes, because the model could ignore it. The fix was in code: the final round now allows only accept or walk away. The agent still decides, but it agrees a price its own limits allow, and the owner's rule still catches the 35% jump. That's the whole point of the project.
  • A paid order that turned into "blocked". A rejected repeat payment request was overwriting the result of a finished order. We caught it in live testing.
  • A quantity bug found while testing. With oil at 0, the shop asked for 20 L while needing at least 25 L, so no deal could ever pass its own limits.
  • The real world: the newest Gemini models returned "high demand" errors on build day, so we added a model fallback list. Antivirus HTTPS scanning broke Python's certificate checks, so we verify against the OS trust store instead of switching checks off. And a cached stylesheet made the new logo fill half the screen, so the page assets now carry version fingerprints.

Accomplishments that we're proud of

  • Three outcomes that happen the same way on every run (paid, held for the owner, blocked), against the real PayPal sandbox, with two live AI agents negotiating each time.
  • An authorization record for every payment, blocked ones included, answering "who authorized this, and under which rules?"
  • A clean separation of roles: the model negotiates, and code decides whether money moves. No message can change that.
  • An interface that stays out of the way. Things happen in the background, and details appear only when you ask.

What we learned

  • Keep the model and the money apart. Negotiating suits a model. Deciding whether to pay should be a deterministic function with tests at every edge.
  • Prompts are suggestions; code is a guarantee. Anything that must always happen, like closing in the final round or rejecting an out-of-limits offer, belongs in code.
  • PayPal details matter. A recorded invoice payment shows MARKED_AS_PAID, not PAID. The sender_batch_id is a free duplicate guard. A payout to a non-sandbox email ends as UNCLAIMED.
  • Live testing finds what unit tests miss: race conditions, buttons redrawn mid-click, and AI agents behaving sensibly in ways that break a demo.

What's next for shauriana

  • Real business payments: replace Payouts (our sandbox stand-in) with a vaulted, merchant-initiated payment the owner sets up once.
  • PayPal Agent Toolkit for the supplier's invoice tools.
  • More suppliers, so the shop agent can compare quotes before negotiating.
  • Approvals where the owner already is, such as a message on their phone instead of a web page.
  • Webhooks instead of polling, and rules the owner can edit with a full change history in the audit log.

Built With

Share this project:

Updates

Submission history