-
-
Let an AI buy for your team, inside rules it can’t break. A signed mandate is checked in the browser by the same rules engine the server.
-
The agent searches products and dry-runs the purchase against the mandate. It has no tool that moves money
-
Every payment gets a receipt: options compared, rules applied, PayPal IDs and a Paid stamp
-
Photograph a receipt, the treasurer approves, and PayPal Payouts pays 23 seconds after the photo.
-
Ask a question in plain English and it filters the dashboard, but it can’t approve or pay anything.
Inspiration
Volunteer teams run on small purchases: water bottles, folding tables, a generator for the beach cleanup. Someone pays out of pocket, emails a photo of the receipt, and waits weeks to be repaid. Treasurers can't see what was bought or why.
AI shopping agents make that trust gap bigger. If an agent can spend, who approved it, and how would anyone prove it afterwards? PayPal already moves the money safely. The missing piece is the authorization layer between the agent and the payment. Quartermaster is that layer. Volunteer teams are the demo, not the ceiling: the same model fits field teams, student clubs, franchise managers, and parents and kids.
What it does
- A treasurer signs a spending mandate: a budget cap, allowed categories and vendors, auto-approve limits, and an expiry date.
- A volunteer asks in chat: "Buy folding tables for the beach cleanup."
- The agent searches products and dry-runs the purchase. It can read prices and check the mandate. It has no tool that moves money.
- A rules engine decides: allow, hold for the treasurer, or block. The decision names the rule in plain words, e.g. "M7: total spend stays within the cap."
- Approved purchases are paid through PayPal. The mandate is checked again before the order is created and again before capture.
- Every payment gets a trust receipt: the request, the options compared, the rule decision, the PayPal IDs, and a stamp (Paid, Refunded, Blocked).
- Volunteers who already paid snap a receipt photo and are reimbursed through PayPal Payouts. The same photo can't be claimed twice.
- Treasurers refund from the receipt, and the stamp changes to Refunded.
- The treasurer asks the dashboard questions ("Which events are above 80% of budget?") and an AI assistant builds or filters the widget.
- Any MCP agent gets the same rules. Claude Desktop connects through an MCP server with seven tools, and is blocked by the same engine.
What makes it different
- The AI cannot move money. Payments run on a separate code path that re-checks the mandate. A model can propose a purchase; it cannot execute one.
- The rules are code, not prompts. The engine is pure TypeScript in integer cents with no network or database calls. The exact same function runs in the tests, in the browser, and on the server.
- Every decision is explainable. A block shows the rule that stopped it, never "the AI said no."
- Built to degrade, not fail. Every external service has a feature flag, a working fallback, and a row on
/status, so judges can see what is live right now. - Idempotent and replay-safe. Every mutating PayPal call sends a
PayPal-Request-Id. Webhooks are signature-verified and stored by event ID. - Traceable. Every agent tool call, from chat or MCP, is recorded with a trace ID, duration and outcome, and an "unusual activity" tile flags repeated blocked attempts.
How we built it
- Mandate engine (
src/lib/mandates/engine.ts): a pure functionevaluate(mandate, proposal, ledger, now)with 11 ordered rules: M0 well-formed, M1 active, M2 started, M3 not expired, M4 currency, M5 categories, M6 vendors, M7 total spend within the cap (including approved-but-unpaid), M8 unit price within the auto-approve limit, M9 order total within the auto-approve limit, M10 no split purchases. - Agent: Gemini 3.5 Flash through the Vercel AI SDK v6, with tools to list mandates, search policy, search products, dry-run and propose. Prices and quantities always come from stored data, never from model output.
- PayPal: Orders v2 and the JS SDK v6 for purchases, Payouts for reimbursements, Refunds, Subscriptions and Invoicing for dues and event funding, and signed webhooks.
- Treasurer dashboard: AG Studio with three custom widgets (budget envelopes, reimbursement queue, mandate health), our own theme, a saved layout, and a Treasurer agent with read-only tools. The AG Grid Community dashboard is the fallback.
- Search and traces: Elastic hybrid search and an ES|QL anomaly query, with pgvector and Postgres as fallbacks.
- Product data and price checks: Channel3 for product search, KERNEL's cloud browser for live price checks.
- Developer surface: an MCP server with seven tools and per-org hashed API keys, an OpenAPI 3.1 spec covering every route, a Postman collection generated from it, and a TypeScript client.
- Quality: 186 unit tests, 49 Postman requests run by newman in CI against a production build, and a Playwright smoke test of the full flow.
- Stack: Next.js 15, React 19, Tailwind v4, Drizzle ORM, Neon Postgres, deployed on Render with GitHub Actions CI.
Challenges we ran into
- Agents overspend. Early versions bought 12 packs when the team needed 12 tables. We added a dry-run tool and per-unit pricing, but the real fix is that the engine, not the model, has the final word.
- Gemini 3 thought signatures broke our first integration. Moving to AI SDK v6 preserved them.
- AG Studio has no Gemini adapter, so we wrote one that keeps the key on the server.
- CI ports and test data. End-to-end tests wrote mock payments that tripped our own split-purchase rule, which was a nice proof the rule works.
- Solo build. Every sponsor integration sits behind a flag with a fallback, so one missing key never breaks the demo.
Accomplishments that we're proud of
- Money movement that is provably limited by code, not by a prompt.
- A trust receipt for every payment that names the rule that allowed it.
- The same mandate rules enforced in our chat, our dashboard agent, and Claude Desktop over MCP.
- A treasurer dashboard you can ask questions of, built on AG Studio.
- An honest
/statuspage: the demo says what is live and what is running on a fallback.
What we learned
Restraint is a feature. Most agentic commerce demos show an agent spending. The valuable part is an agent that can't overspend, and proof that it couldn't. Keeping the model out of the money path made the system easier to test, easier to explain, and easier to trust.
What's next
- Production PayPal credentials and a pilot with a real volunteer group.
- Mandates for field teams, student clubs, and family allowances.
- Publishing the mandate engine as a standalone package any agent framework can call before it pays.
Built With
- ag-grid
- ag-studio
- apimatic
- channel3
- elastic
- gemini
- github-actions
- kernel
- mcp
- neon
- next.js
- openapi
- paypal
- paypal-webhooks
- postman
- react
- render
- tailwindcss
- typescript
- vercel-ai-sdk