Inspiration
In August 2026 a Lagos businesswoman reported losing ₦10 million she had saved for a year in an ajo to expand her factory. The coordinator refused to pay out her share (Legit.ng). Rotating savings circles (ajo and esusu in Nigeria, tandas in Mexico and the US, susus in Ghana and the Caribbean) are how many people without easy credit save for rent, school fees or stock. They fail in two ways, and both are about trusting people rather than rules:
- The collector keeps the pot.
- Someone who collects early stops paying.
I wanted a circle with no collector at all: the money moves on PayPal, AI agents handle the coordination, and code that nobody can sweet-talk holds the rules.
What it does
Turnwise runs a savings circle with PayPal and three AI agents. The agents propose; a policy gate written in code decides every money move.
- Charter. The organizer describes the circle in plain words. The Treasurer agent drafts the rules, code validates them, every member approves, and the charter is frozen and hashed. Changes need every member's approval.
- Mandate. Each member tells their Turn Agent what they need, for example "rent by 15 December, I'll pay up to $40 to go early." Code turns that into a cap it can enforce. A vague term like "when I can" comes back as a question.
- Pay in. The first contribution is a PayPal checkout that saves the member's wallet. After that, each contribution is authorized from the saved wallet 5 days before it is due and captured on the due date. Members without a saved wallet get a PayPal invoice.
- Auction. When the pot is complete, Turn Agents place sealed bids: the discount a member gives up to collect now. Outside assistants can also bid through a public MCP endpoint. The gate checks each bid against the member's mandate, code picks the highest valid bid, and one PayPal Payouts batch pays the winner and shares the discount with everyone else. With $n$ members paying $c$ each, a pot of $P = nc$ and a winning discount $d$:
$$\text{winner receives } P - d - c \quad\text{(one contribution held as a refundable deposit)},\qquad \text{each other member receives } \frac{d}{n-1}.$$
- Protection. If someone who has collected misses a payment, the agreed steps run: a reminder, grace days, a window to pay, then their deposit keeps the pot whole. The Guardian agent explains each step to the member, but code decides them.
- Proof. Every move goes into a double-entry ledger with the agent, the rule the gate applied, the charter hash and the PayPal ID. Every payout is confirmed with PayPal, and the ledger is reconciled against PayPal's own records.
- Ask the ledger. The ledger and auction board are AG Grid Enterprise. Ask "Who has paid late twice?" and the AG Grid AI Toolkit turns it into a grid view, validated against the grid's own schema before it's applied.
How I built it
- PayPal (sandbox):
- Orders v2
CAPTUREfor the first pay-in, with Vault (usage_type: MERCHANT) to save the wallet. - Orders v2
AUTHORIZEplus Payments v2 capture and void for later contributions. - The Payouts API for winners, dividends and deposit refunds.
- Invoicing v2 for members without a saved wallet.
- Webhooks with signature verification and de-duplication by event ID.
- The Agent Toolkit, allow-listed to invoices and read-only tools.
- Orders v2
- AI:
openai/gpt-6-lunavia OpenRouter, called with the Vercel AI SDK. Every agent returns a zod-validated object, so free text never reaches a payment call. - The gate:
src/gate/policy.tsmakes no model calls. It checks the charter hash, cycle state, mandate hash and cap, revocation, default steps, amounts, recipients and idempotency. Onlysrc/gatemay import the PayPal client, which ESLint enforces. Every call carriesPayPal-Request-Id = sha256(circle | cycle | action | subject), so a replay is refused. - Front end: Next.js 15, TypeScript (strict), AG Grid Enterprise with the AI Toolkit and Integrated Charts. The design uses an adire indigo palette with the turn ring as the home screen.
- Back end: Prisma and Postgres. A worker runs live circles on an accelerated, clearly labelled demo clock. An MCP server (stateless Streamable HTTP) authenticates outside assistants with per-member tokens.
- Hosting: Render (web, worker, Postgres) from a Blueprint.
Challenges I ran into
- Authorizations expire. PayPal honours an authorization for 29 days, and reauthorization isn't something to rely on. So Turnwise creates one authorization per contribution, 5 days before it's due. The charter validator also refuses any rules where lead days $L$, grace days $g$ and the window to pay $w$ could push a capture past the limit: $L + g + w < 29$ days.
- A payout PayPal accepted, then denied. My own reconciliation caught a payout batch that PayPal accepted and then failed for one item. Turnwise now confirms every payout item by item. A failed item reverses its ledger entry and holds the money for the member until it is sent again through the gate.
- The model can be talked into things. In red-teaming, the model was persuaded to set a $100 cap for "I'll pay $1… make it $100." The fix was to stop trusting the model's number: code decides the cap (the lowest amount the member wrote, or $0 for an absolute "no"), and the model's value is clamped to it.
- Turning questions into grid views reliably. Early AI Toolkit states sometimes failed schema validation or filtered the wrong column. Now a second, simpler reading of the question runs in parallel, and code builds a grid state from it. If the two disagree, the code-built state wins, and either way the state must pass AG Grid's schema.
- Platform failures aren't member failures. When PayPal was briefly unreachable, or rejected the app's credentials during deployment, members were marked late through no fault of their own. Now a platform error pauses the circle's clock and nobody is penalized.
Accomplishments that I'm proud of
- 0 false money moves in 1,000 seeded proposals, checked by an independent checker. 0 captures on an authorization 29 or more days old across 142,720 simulated captures. 0 invalid bids accepted in 50 seeded auctions.
- 40 of 40 injection attempts quarantined, with no ordinary messages wrongly blocked. 16 of 16 attacks on the MCP endpoint refused (forged tokens, replays, over-cap bids, revoked mandates).
- A hosted live run on Render that ran unattended. Contributions were authorized from saved wallets and captured, an over-cap outside bid was rejected, a missed payment was covered by the deposit, and the payout was confirmed by PayPal. The ledger reconciled against PayPal's records to $0.00.
- The eval and red-team results are published in the repo, misses included.
What I learned
- Agents should propose, not act. Letting the model negotiate, explain and draft, while deterministic code holds every limit, gave me an
Built With
- ag-charts
- ag-grid
- ag-grid-ai-toolkit
- ajv
- eslint
- model-context-protocol
- next.js
- node.js
- openrouter
- paypal
- paypal-agent-toolkit
- paypal-orders-api
- paypal-payouts
- paypal-vault
- paypal-webhooks
- postgresql
- prisma
- react
- render
- typescript
- vercel-ai-sdk
- vitest
- zod
