← All Build Logs
AI AgentsFailures & FixesFailed → Fixed

Fixing a Voice Booking Agent That Double-Booked Slots

I built a voice agent for appointment bookings. It kept double-booking the same slot.

The idea was simple: let callers book an appointment over the phone without a human picking up. Check availability, offer a slot, confirm it, write it to the calendar. It worked cleanly in every test I ran — until two real callers booked the same slot four minutes apart, and the second one showed up to find someone already there.

The Build

A voice agent (speech-to-text → LLM → text-to-speech) that checks a bookings table for open slots, offers the caller the nearest one, and writes a confirmed row once they accept. Straightforward on paper.

Why It Worked in Testing

In testing, I was the only caller. One request, one check, one write — no way for two things to collide when there's only ever one thing happening at a time. The bug had no way to show up until there were two simultaneous callers to expose it.

What Actually Happened

Two callers rang in close together. Both got routed to the same available slot, because both check-availability calls ran before either booking was actually written. Caller A's "is this slot free?" check ran, came back yes. Caller B's check ran a few seconds later, also came back yes — because Caller A's booking hadn't been written to the table yet. Both got confirmed. Classic race condition: the gap between checking and writing was wide enough for two callers to fall into it.

The Fix

The fix isn't smarter availability logic — it's making the check-and-write a single atomic operation instead of two separate steps with a gap between them:

Before (separate check, then separate write — a gap either caller could land in):

SELECT * FROM bookings WHERE slot = '2026-08-14 14:00' AND status = 'confirmed';
-- (if empty, elsewhere in the code) INSERT INTO bookings (...) VALUES (...);

After (the write itself enforces uniqueness, no gap to fall into):

INSERT INTO bookings (slot, caller_id, status)
VALUES ('2026-08-14 14:00', $1, 'confirmed')
ON CONFLICT (slot) WHERE status = 'confirmed' DO NOTHING
RETURNING id;

With a unique constraint on slot for confirmed bookings, the second caller's insert simply fails to return a row — which the agent reads as "that slot just got taken, let me offer you the next one" instead of confirming a slot that's no longer free.

What I Learned

This is the kind of bug that's genuinely invisible with a single tester and only shows up under real concurrent load — no amount of solo testing would have caught it. Anything that checks availability before committing to something (a slot, a stock item, a discount code) needs to treat "check" and "write" as one atomic step, not two, the moment more than one caller can realistically hit it at the same time.

Want something like this built for your business?

See AI Agents services

Related Build Logs

Comments

No comments yet — be the first.