Inspiration

Software agents are starting to buy things for people. When one buys the wrong thing, the buyer disputes the charge and the store owner has to work out who was at fault: the agent, the store, or nobody. Nothing exists today to answer that from evidence. Warrant is my attempt at that referee.

What it does

A buyer tells a shopping agent what it may buy, in plain English: "Buy USB-C cables from Test Store, up to $25, for 30 days." Warrant turns that into a signed instruction. The agent checks every cart against it, buys through PayPal's Orders API and signs a receipt. If the buyer opens a dispute, Warrant reads the signed instruction, the receipt and the store's record, and reaches one of four rulings:

  • Within mandate: send PayPal the signed instruction and receipt as evidence.
  • Agent overstepped: offer the buyer a refund.
  • Merchant at fault: accept the claim and refund the buyer.
  • Needs a person: hand the case to a person.

The store owner sees a queue of disputes in an AG Grid table, approves or overrides each recommendation, and keeps an audit trail. The buyer sees a plain-language view of what their agent did. A developer can download an evidence packet and check whether anyone edited it. Warrant recommends and PayPal decides.

How I built it

Ordinary code decides the ruling: limits, approved stores, dates, running totals and signatures. A small language model (NVIDIA Nemotron) does two reading jobs and never writes a ruling. It turns the buyer's sentence into limits and stores, and the buyer confirms that reading before anything is signed. It also says what kind of item a cart line is, such as a cable or a subscription. If it isn't confident, the case goes to a person. Both jobs fall back to fixed code when no model is available, and the screen says which was used. The sentence explaining each ruling comes from a fixed list tied to reason codes, so the app can't give a reason the rules didn't find.

The backend is Python and FastAPI with SQLite, and instructions and receipts are signed with ES256. The console is React, TypeScript and AG Grid. It runs from one Docker image on Google Cloud Run. It talks to PayPal's sandbox Orders and Disputes APIs, and it also ships an MCP server with three tools: decide_dispute, get_receipt and explain_dispute.

Challenges I ran into

PayPal's sandbox behaves in ways its documentation doesn't say, and I recorded what I found in docs/PAYPAL_FINDINGS.md. A new dispute has no response deadline for the first few minutes. The buyer's transaction ID is different from the seller's capture ID. A dispute moves to "under review" after you act on it, which at first made the app change its own recommendation, and I fixed that. The other challenge was keeping the language model honest. A model alone agreed with my labels only 11 and 13 times out of 24 and never handed a case to a person, which is why it only reads and never rules.

Accomplishments that I'm proud of

  • The whole loop runs on real PayPal sandbox disputes, one for each ruling, and a hosted copy shows recordings of them with no keys needed.
  • A published scorecard on 36 scenarios, tuned on 12 and reported on the other 24. Rules alone agreed with my labels 21 times. Rules plus model agreed 23 times (Nemotron 30B) and 24 times (Nemotron 4B). No arbiter made an unsafe refund. The scenarios and labels are mine, not ground truth from a real dispute system, and each model was run once.
  • 36 automated tests, an open evidence-packet spec (v0.1) and the labelled scenario set, which others can reuse.

What I learned

Put the model where language is the problem and keep it away from decisions that move money. Signed records make a dispute checkable, not just arguable.

What's next for Warrant

Run the labelled scenarios against more arbiters, test with more models, and take the evidence-packet spec to people working on agent payment protocols. docs/RELATED_WORK.md says how Warrant relates to AP2, the Agent Commerce Kit and the Agentic Commerce Protocol.

Built With

Share this project:

Updates

Submission history