booking-agent
Appointments booked by chatting: the assistant finds and holds the time, the customer confirms it with a button, and PostgreSQL makes double booking impossible.
Conversational booking in .NET 10 with Claude Haiku 5.5: narrow tools the assistant cannot use to confirm, holds that run out, reminders through an outbox, sign-in with an emailed code, and a live panel showing why each booking was made.
- Role:
- Design and development
- Year:
- 2026
- eval conversations ending as expected
- 28/28
- bookings confirmed or cancelled by the assistant
- 0
- simultaneous requests for one slot: only one gets it
- 1 of 50
- model cost per conversation
- USD 0.0024
Demo
Screenshots
The problem
Clinics and salons lose hours on the phone booking appointments. A chatbot that books by itself trades that problem for others: double bookings, appointments nobody asked for, and made-up answers. The goal was a useful assistant that cannot do harm: one that understands "el lunes en la mañana", searches the real calendar, and leaves the final decision to the customer.
Constraints
- The assistant never confirms or cancels: it proposes, and the person acts with a button
- No double booking, even when fifty people ask for the same slot at once
- Spanish relative dates resolved on the server, on the business's time zone
- Notices and reminders that are never lost, and never announce what was not saved
- A visible cost per conversation, capped per conversation and per day
How it works
- Eight tools, each a thin adapter over a use case the booking form also uses: the assistant can do nothing the form cannot, and an architecture test enforces it.
- Holding a slot takes it for five minutes in PostgreSQL, under an exclusion constraint on time ranges. The confirmation token goes to the chat's card, never to the model.
- Notices are written to an outbox in the same transaction as the booking, and a dispatcher sends them by email and to an in-app inbox, with retries.
- Customers sign in with a six-digit code sent by email to see, move or cancel their appointments; the session lives in an HttpOnly cookie.
- Every booking change is announced over SignalR, and the clinic's panel reloads the week at once. Each booking the assistant made shows its conversation and steps, with tokens and cost.
Key decisions
The person confirms, not the assistant
Having a model decide whether "sí, pero…" means yes is a risk and a cost. A button with a one-time token is deterministic.
Trade-off: Someone who types "sí" still has to press the button; the assistant reminds them.
Double booking is prevented by PostgreSQL
Check-then-insert loses the race; an exclusion constraint does not, whether the booking comes from the chat, the form or a bug.
Trade-off: Saves for the same professional are queued, under a transaction lock.
Evaluated like software, on the database
28 scripted conversations are scored on what ends up stored, not on what the assistant says: whether it held the right time, and whether it changed anything it should not.
Trade-off: A run against the real model costs about USD 0.07, so it is started by hand; CI runs a free oracle.
Results
- With Claude Haiku 5.5, all 28 eval conversations end as expected: bookings, conflicts, moves, cancellations and manipulation attempts.
- No booking confirmed or cancelled by the assistant, and no slot held that the calendar had not offered first.
- Fifty simultaneous requests for one slot: one gets it and the other 49 receive the nearest free times, in about a second.
- The first run scored 0.76: the transcripts showed "mañana" was not resolved in a container without culture data. It was fixed and is now covered by the tests.
- An end-to-end Playwright test walks the chat, the live panel, the emailed code and a cancellation on every push, with no API key and no cost.
Stack
- .NET 10
- C#
- PostgreSQL
- NodaTime
- SignalR
- Aspire
- Claude Haiku 5.5
- Next.js
- TypeScript
- Playwright


