Inspiration
Haggling is a social sport. Marketplace buyers and sellers also get burned: a deposit goes out, the item never shows up, or the goods arrive and the seller says the money never came. We wanted to see two AI agents do the bargaining while you watch, with the money held safely until the goods actually arrive.
What it does
- Haggle puts an AI buyer and an AI seller in a live haggle over an item. You set the item, the buyer's budget, the seller's floor and the opening ask, then watch the transcript stream in turn by turn with a price-gap meter and a "face" score for each side. Rude lowballs and insults cost face. Lucky and unlucky numbers (like 8 and 4) nudge prices under Malaysian house rules.
There is also a bidding war: one seller against two to four buyers, with a sealed final round.
When a deal closes, the Kena Scam Shield takes over. The buyer's money is authorized through PayPal but not captured. It is only released after delivery is confirmed. A missed deadline voids the hold and the buyer keeps their money. Only the person who started the match can move its money. Everyone else can watch.
A "Check real prices" box (Channel3) turns an item name into real products and prices, can pre-fill the ask, floor and budgets, and shows a line like "deal is 30% below retail" on the match.
How we built it
The principle is: code decides, the LLM talks. Prices, face, lucky numbers, budget caps and payment state are deterministic, unit-tested Python. The LLM only proposes a move and writes the banter, and a referee in code validates or overrules every proposal. When an agent tries a price outside the rules, the commentary shows "Agent tried X. Ruled Y."
- Backend: FastAPI, a LangGraph negotiation loop, SQLAlchemy and Alembic on Postgres (an append-only event log), Redis, server-sent events.
- Frontend: Next.js, TypeScript, Tailwind, shadcn/ui and Framer Motion.
- Payments: PayPal Orders v2 and Payments v2 over REST, with our own PayPal-Request-Id on every money call so a retry cannot move money twice. The Shield is a state machine (negotiated, authorized, shipped, delivered, captured, refunded or voided) that validates a transition before it touches the gateway. - The buyer and seller agents have no payment tools at all.
- Hosted on Render from a Blueprint (API, web, Postgres, Key Value).
Challenges we ran into
Hold now, pay later. PayPal's easy path captures at once. We needed authorize-only, capture-after-delivery, void and refund, and the sandbox taught us the details: orders need a return_url or the buyer's approval is never recorded, the buyer must be the sandbox personal account, and tracking is refused on an authorisation and only accepted after capture.
Trusting an LLM near money. Our first Claude runs lowballed and named insulting numbers because the prompt never listed the face rules. We fixed the prompt, and the referee clamps whatever the model proposes.
Bugs found only in live testing. A tie in the bidding war's final round went to whoever was listed first, so the buyer who led all game lost. We changed the rule and fixed a header that still named the old leader. A deploy also pointed the web app at someone else's service, because Render service names are global. We caught it, fixed it and corrected our deploy guide.
A public demo that is safe to leave open. Owner tokens (only a hash is stored), per-IP rate limits and a daily cap on Claude matches.
Accomplishments that we're proud of
- The Shield's capture, void and refund endings ran against the real PayPal sandbox, not only a mock.
- Budget caps never appear in the public stream, and tests enforce the invariants.
- Over 200 backend tests plus frontend tests, with lint and type checks clean.
- It is live, and a whole match from the form to "paid to the seller" runs in the browser.
What we learned
- Putting deterministic code between the model and anything that matters makes the model's mistakes cheap.
- Prompts need the rules written down. The model will not guess your house rules.
- Real-service testing finds what mocks hide, and so does clicking through your own deployed site.
What's next for Haggle
- Real life delivery confirmation from a carrier instead of a buyer button.
- Accounts, so a match survives a cleared browser.
- Run many more Claude matches before trusting the averages. We checked only a few so far.
- Revisit PayPal's MCP server if it gains authorize, void and capture-later tools.
- Tune the Shield deadlines to PayPal's real authorization window, and add more cultures and house rules.
Built With
- alembic
- anthropic
- channel3
- claude
- fastapi
- framer-motion
- langgraph
- next.js
- paypal
- paypal-sandbox
- postgresql
- pytest
- python
- react
- redis
- render
- server-sent-events
- shadcn-ui
- sqlalchemy
- tailwind-css
- typescript
- vitest