Inspiration
AI agents are being handed spending power faster than anyone has built a way to check what they actually did with it. The current model is binary: either the agent asks you before every transaction — useless at any real volume — or it holds a card and you have no idea what it will buy, why, or under what authority. Both failure modes share one missing piece: there is no artifact that says what was allowed, what was bought, and how the money left, in a form a human can actually verify. MandatePay started as an attempt to close that trust gap — not with better AI, but with a legible contract between an agent and its principal.
What it does
MandatePay is a company card for AI agents. You state constraints in plain English — "two hundred a month for SaaS, over fifty ask me first, only Notion and Figma" — and the agent parses them into a structured policy object: rolling monthly budget, per-transaction threshold, vendor allowlist, category allowlist. Every purchase request is then judged by a deterministic constraint engine (a pure function, fixed check order, three possible verdicts: AUTO_APPROVE, REQUIRE_HUMAN, REJECT) — the LLM is never in the decision chain.
Below the threshold, the agent buys and explains. Above it, the request waits on an approval desk showing all three Mandate layers, and no order reaches PayPal until a human clicks through. Every transaction produces a three-layer audit record under one trace_id: Intent (what the agent was allowed to buy), Cart (what it chose, for how much), Payment (how the money left, and under whose approval — including the PayerID). The budget dashboard shows spent, remaining, and month-end projection, all computed live.
How we built it
- Stack: FastAPI + LangGraph + PostgreSQL (Neon), deployed on Azure App Service (B1, Always On). Five static pages: constraints, approval desk, audit log, budget dashboard, payment test.
- Three PayPal integration paths, deliberately kept separate: REST Orders v2 (the web checkout), the official PayPal Agent Toolkit (the LangGraph agent's native path), and PayPal's MCP server. The MCP path is gated by a read-only allowlist plus an explicit denylist of nine money-moving tools, each annotated with why it's blocked — because MCP executes tool calls immediately with no policy layer, so raw access would let the agent capture orders or cancel subscriptions without the constraint engine ever seeing it.
- The constraint engine is the part that must be right. It's a pure function with a fixed check order; everything else (intent parsing, insight generation, explanation) is allowed to be fuzzy because it can only propose. The LLM endpoint is fully swappable, and the agent degrades to rule-based parsing when it's unreachable — with degradation counters, so a verification run can never report success while the AI path was silently down.
- The dashboard is code, not a drag-and-drop artifact: the AG Studio budget view is defined programmatically (
createStudio()+setState()), reviewable in a diff, with automatic fallback to a classic view if the runtime fails to load.
Challenges we ran into
- PayPal's tooling is fragmented. The Agent Toolkit's
create_orderpayload shape differs from REST'spurchase_units[].amount.value, and its defaultreturn_urlis an IANA-reserved example domain — orders silently redirected to a documentation page until we passed explicit return/cancel URLs. We kept all three integration paths rather than collapsing them, because each has a different failure surface and the constraint check has to sit in front of all of them. - We found a live security hole in our own engine. When a request carried no category field, the category allowlist was silently skipped — the constraint was effectively void. 26.15% of stress cases hit this. It's a security failure, not a cosmetic bug, and no test written after the fact would have caught it.
- Correctness had to be proven independently. "The engine agrees with itself" is meaningless, so we built three oracle implementations against the written spec — spec-literal, pre-fix, and user-intuition — and ran 650 randomized cases (550 adversarial) plus hand-enumerated ground truth frozen into the repo before any run.
- Demo production is harder than it looks. Our first screen recording missed two of the most important shots — the cross-vendor price-comparison result and the refusal-explanation screen — which the narration depended on. We had to audit every frame of the raw footage and rebuild the script around what was actually captured.
Accomplishments that we're proud of
- The hole is closed, and provably so: after the fix, 0 false rejects and 0 false allows across the full stress suite; 60/60 correct verdicts on 20 natural-language sentences × 3 runs, with zero degradations.
- A real, completed sandbox loop — natural-language constraint → agent decision → PayPal order → human approval → capture → audit record — verified end-to-end against the live sandbox, with the completed order visible in the PayPal Developer Dashboard, not just self-reported.
- Three integration paths with one policy layer: adding a capability and adding risk are separate, reviewable decisions — the MCP gate makes that concrete with nine annotated blocked tools.
- A demo a stranger can run in fifteen minutes: public live instance, no login, sandbox buyer credentials in the README, and a Chinese/English toggle so judges can read it in either language.
What we learned
- The default behavior on a missing field is the security policy. Skipping a check because data is absent is the default of almost every implementation — and when the missing field is a policy boundary, that default is a vulnerability.
- Guardrails belong before execution, not after. A protocol like MCP executes immediately; the only safe place for a policy gate is in front of it, rejecting blocked tools before any API call is ever made.
- Evaluation must be independent of the thing being evaluated. Three oracles that can disagree with each other — and with the engine — is what separates "tested" from "verified."
- Demo evidence has to be checked against the script, not the other way around. We rebuilt our entire demo plan from a frame-by-frame audit of the raw footage, and the script is now derived from what the screen actually shows.
What's next for MandatePay
- Multi-tenancy: per-user or per-department constraint sets, turning the single global policy into a real product surface.
- Approval integration: pushing
REQUIRE_HUMANitems to Slack/email so the human checkpoint works outside the demo UI. - Broader spend domains: the three-layer Mandate contract — Intent / Cart / Payment — is not specific to PayPal; it applies to anything an agent is allowed to spend on: cloud credits, API quotas, ad budgets, licence seats.
- Audit export and retention: making the Mandate records consumable by real finance and compliance tooling rather than a dashboard view.
Built With
- ag
- azure
- fastapi
- html
- javascript
- langgraph
- neon
- paypal
- postgresql
- python