Inspiration

While building SearchCrucible, I kept getting stuck on subscription tiers. AI features cost money every time someone uses them, so I can't put them in every plan without risking a loss, but full usage-based pricing doesn't feel like a good deal from the customer's side either. Charging more protects the business; it also asks customers to pay for things they'll never touch. No fixed set of plans fits every use case.

That's the gap Let's Haggle fills. Solopreneurs selling software and digital services negotiate deals all the time, informally, because there's no finance team to consult and a good customer rarely fits neatly into a pricing tier: they might need fewer seats, one feature from a bigger package, or a smaller initial commitment. A pricing page can't cover every combination, and a contact form turns a five-minute conversation into a full sales process.

Let's Haggle makes that negotiation live and structured instead. Customer and owner trade complete offers in real time, each one appearing instantly for both sides. The owner can accept, counter, or decline while the customer is still there, either person can step away and pick the conversation back up later, the owner's limits stay private, and both sides keep a clear record of what they agreed to.

My hypothesis, untested so far: haggling doesn't just produce a better price, it produces a more invested customer. Someone who negotiated their own deal has a psychological stake in it working out, and starting a subscription on a successful negotiation could mean higher satisfaction and retention than starting on a price you just accepted off a page.

What it does

Let's Haggle adds live, turn-based negotiation to an existing merchant site. An owner connects Stripe, imports Products and Prices, builds an offer, sets which items, quantities, and amounts customers may change, and embeds it on their own site.

A customer opens the offer, enters an email, and sends a complete proposal. Each counteroffer is a new snapshot rather than an edit, so both sides always see a clear history, whose turn it is, and exactly what's on the table.

When one side accepts the other's exact proposal, the server seals that version of the agreement, then either creates a Stripe-hosted Checkout Session from it or hands the same sealed agreement to the merchant's own payment system.

Owners get a Stripe-backed offer editor, a live inbox of active negotiations, accept/counter/decline controls, and an API for sealed agreements. Customers get an embedded negotiation dialog, live updates, reconnect support, an emailed resume link, and Checkout for the exact package both sides agreed to.

How we built it

I had the idea for Let's Haggle on a Thursday morning and primarily built it in evenings after my daughter went to bed. Codex ran the entire build, from planning through implementation and testing, working autonomously while I set direction and reviewed results.

Design first, code second

I started with a product brief, constraints, and a reference architecture borrowed from SearchCrucible, another project of mine. Before writing any implementation, I had Codex map every feature as a command, domain event, projection, and external effect. That produced an event-sourcing flow document and turned each feature into a small, independently reviewable contract: the plan for the whole build, before a line of it existed.

Key slices

The build moved in clear phases, each one a separate Codex session with a distinct focus:

  • Core mechanics: One session built the vertical slice, offer model, negotiation protocol, owner desk, customer widget, realtime flow, with Stripe mocked at the boundary so the work could concentrate on getting the core mechanics right.
  • Real integration: A separate session swapped those mocks for a genuine Stripe App integration, with Nango handling OAuth and proxying authenticated Product, Price, account, and Checkout calls.
  • Polish and proof: I added a deterministic demo mode, cut leftover complexity, and spent extended time on manual testing, making the two-party negotiation legible without any narration.

Architecture

The control plane is Rust, compiled to WebAssembly, running on Cloudflare Workers. Offers live in an Organisation Durable Object, and every negotiation gets its own HaggleSession Durable Object: one ordered event stream, one place to coordinate hibernatable WebSockets, one authority for every decision. If the customer and owner act at the same time, their commands are decided in order against the same state instead of racing through separate application servers.

The result is natural horizontal sharding, twice over: every organisation is its own unit of compute and storage, and every active Haggle splits off into its own object again. A busy negotiation doesn't wake or block the merchant's whole organisation, and unrelated Haggles don't share its serialized execution path. As usage grows, the system creates more small, isolated objects instead of pushing more traffic through one shared database or WebSocket server, and Cloudflare handles the routing, placement, and connection hibernation that would otherwise mean running multi-region app servers, replicated state, and distributed locking myself.

The owner desk is Astro with a React island system. The customer widget is TypeScript on its own Worker. Clerk handles owner identity, Cloudflare Turnstile guards public session entry, Nango manages the Stripe OAuth connection, and Stripe itself owns Products, Prices, and Checkout inside the merchant's account.

A strict execution path

Every action takes the same route through the system. A command reaches an aggregate inside an Organisation or HaggleSession Durable Object. The aggregate makes the decision and commits events, then those events update the view both participants see.

If the decision needs to trigger something outside the object, such as an email or a Stripe call, the system records that work as an effect and passes it to a queue or workflow. The result comes back as another command, so it joins the same history as the decision that caused it.

The result of splitting projections and effects is that rebuilding a read model cannot quietly send an email or touch a payment. The business decision is already committed before outside work starts, and every retry or failure leaves a trail. When something goes wrong, I can follow one chain instead of piecing the story together across the application, a queue, and an external provider.

Challenges we ran into

The biggest constraint was time. This was built solo, primarily in evenings around a full-time job and a toddler's bedtime, which meant every day came with a hard cutoff rather than a flexible one. That forced two disciplines I'd otherwise have been tempted to skip.

The first was deciding what not to build. Full-scale negotiation could sprawl into disputes, multi-party deals, approval chains, and a dozen other directions that are all reasonable features and all out of scope for a first version. I kept cutting back to the smallest version of the core loop: propose, counter, accept, pay. Anything that didn't serve that loop directly got parked, not built.

The second was manual setup. Wiring a real Stripe App through Nango, a Cloudflare Worker, and a Durable Object network isn't something an agent can fully verify on its own. Every credential, webhook, and OAuth redirect needed a human in the loop to click through and confirm end to end, and that verification took real wall-clock time no amount of autonomous coding could shortcut.

Accomplishments that we're proud of

The result I'm most proud of isn't the product, it's what building it proved about the process. Event sourcing gave the codebase an architecture that's obvious from its own shape: a command comes in, an aggregate decides, events commit, a read model updates, effects go out. That structure meant Codex could work autonomously across multiple sessions, days apart, without me re-explaining the system each time or the agent quietly duplicating logic or introducing regressions in code it hadn't touched before.

That's a bigger result than the app itself. It suggests that an architecture designed to be legible to a human reviewer is also the architecture that lets an agent operate safely on a codebase with minimal context. Good software design and good agent-readable design turned out to be the same thing.

What we learned

Making a system's own structure the primary source of context for an agent works better than any amount of upfront prompting. Every feature slice, mapped as a command, event, projection, and effect before implementation, gave Codex a contract it could satisfy without needing me to re-explain the surrounding system on day two or day three. The discipline of the event-sourced execution path, where side effects can only ever be recorded rather than performed inline, also turned out to be exactly the kind of constraint that keeps an autonomous agent from taking shortcuts that would look fine in the moment and cause quiet damage later.

What's next for Let's Haggle

The immediate next step is finding out whether this problem resonates beyond my own experience building SearchCrucible. I want to put Let's Haggle in front of other solopreneurs selling software and digital services and see whether the appeal is real or whether it was just my own pricing headache in disguise.

If there's a genuine market, the plan is to take it from hackathon project to a real SaaS product: proper multi-tenant onboarding, usage limits, billing for the app itself, and enough polish that a merchant can set it up without anyone walking them through it by hand.

Built With

Share this project:

Updates