SessionBook / Engineering case study
A voice asks. The backend decides.
An appointment-booking backend for a voice agent. Availability, booking and confirmation stay application decisions.
- My contribution
- Backend & integration engineering
- Project state
- Implemented MVP
- Implementation
- HTTP tools + agent configuration in source
- Deployment
- No live calls verified
Stack FastAPI / SQLAlchemy 2.0 (async) / asyncpg / Postgres 16 / Pydantic / Alembic
02 / Project visual
One exchange, one path.
No product screenshot exists: the repository has no captured call and no deployed URL. The exchange is illustrative; the path below is in source.
Callervoice agentHTTP toolFastAPI servicebooking decisionspoken reply
Tinted steps run in the backend.
01Caller asks for availabilityContracts defined and routes wired.
A caller asks for availability for a date. Pydantic contracts are defined for this boundary — AvailabilityRequest(date), BookingRequest with length-bounded fields and service-level phone validation — and HTTP tool routes are wired to serve the voice agent.
Source. schemas.py defines the contracts; tool routes verified in the repository.
02Slots queried by dateTimezone-aware, unbooked only.
check_availability queries slots for the requested date that are still unbooked. Datetimes are timezone-aware and rendered for speech in Africa/Accra via ZoneInfo. Note: the query filters by date and booked state; it does not filter by provider.
IMPLEMENTATION EXCERPT result = await db.execute( select(Slot).where( Slot.start_time >= datetime.combine(target_date, datetime.min.time(), PROVIDER_TZ), Slot.start_time < datetime.combine(target_date, datetime.max.time(), PROVIDER_TZ), Slot.is_booked == False, ) )Source. booking_service.check_availability; PROVIDER_TZ = Africa/Accra.
03Slot exists and is freeLock first, then check inside the transaction.
Inside a transaction, book_slot locks the slot row with SELECT … FOR UPDATE, then confirms the slot exists and is still free. The existence check happens after the lock is held, so two concurrent callers cannot both pass the gate.
Source. book_slot: with_for_update(), then None / is_booked checks.
04Race: two callers, one slotConcurrent book request for the same slot.
Two callers can attempt to book the same slot at almost the same time. Without protection, both reads see `is_booked = false` and both writes succeed — the system double-books.
Source. A failure mode the implementation is designed to prevent.
05Booking confirmed, code returnedCaller can read the code back.
Booking returns a confirmation with `confirmation_code` (6–10 chars, no 0/O/1/I) and a `spoken_confirmation` sentence the agent can say. The caller can read it back, and the system can look it up.
Implementation excerpt { "confirmation_code": "A3F9K2", "spoken_confirmation": "You're booked for Saturday, September 5 at 9:00 AM. Your confirmation code is A3F9K2." }Source. BookingConfirmation schema; _generate_confirmation_code implementation.
03 / Problem
The conversation can be flexible. The booking can’t.
A voice agent can ask what time works. It shouldn’t decide whether a slot exists, or whether someone just took it.
The agent owns
- Transcription and turn-taking
- Prompting and the decision to call a tool
- Speech — how the answer is said
The backend owns
- Which slots are actually free
- Reserving one without double-booking
- Timezones, confirmation codes, what to say when it fails
04 / Contribution
The service behind the boundary.
I built the FastAPI service behind that boundary: the tools the agent calls, availability queries, the booking transaction, the spoken responses.
- Four HTTP tools —
get_today,check_availability,book_slotandconfirm_booking, mounted asPOST /toolsroutes the agent calls itself. - The booking service —
app/services/booking_service.py: availability, the locked booking transaction, confirmation codes and spoken labels. - The contracts and agent document —
schemas.pybounds the request fields and validates phone numbers;voice_agent/agent.jsonpoints four tool definitions at the routes.
AssemblyAI owns the conversation. SessionBook owns the agent document and the backend behind it. Nothing here streams or persists a socket.
05 / Key decisions
Correctness decided in the backend.
- Lock the slot before checking it
book_slottakesSELECT … FOR UPDATEfirst, then checks the slot is still free. A unique constraint is the second line: the loser fails loudly.- Keep the clock provider-local
- Times are timezone-aware and spoken in Africa/Accra — the provider’s clock, not the caller’s.
- Make the confirmation sayable
- Codes run six to ten characters with no 0/O or 1/I, and come with a spoken sentence so a caller can read them back.
- Give failures something to say
- Failures come back as whole sentences — 409 “That slot was just taken, would you like another time?” — so the agent can ask again.
06 / Result & status
Written is a state. Proven is another.
Four HTTP tools, a mounted router, a row-locking booking service and the agent configuration are present at commit 1264d9e.
- Implementation
- In source at commit 1264d9e. Four tool routes, a row-locking booking service, the contracts and the agent document.
- Live calls
- Unverified. No call was made or replayed, and no deployment exists.
In source
- FastAPI app, service layer, models and Pydantic contracts are written
- SELECT … FOR UPDATE plus an IntegrityError fallback guards double-booking
- Datetimes are timezone-aware, anchored to Africa/Accra (ZoneInfo)
07 / Limitations
What has not been proven.
Not verified
The repository verifies written implementation, not a working phone call.
- Live deployment and live calls unverified; no retained latency or success measurements
- tests/test_booking.py is empty — no tests are written
- No migrations generated (Alembic configured, no versions)
- Date interpretation for busy or closed times still needs call testing; phone normalization and 7–15 digit validation exist in source
- Tool authentication has not been reviewed
Corrected here: an earlier version said short phone numbers passed validation. The service requires 7–15 digits.
08 / Sources
Pinned source, not a demo.
- Source repositoryGitHubFastAPI service, agent configuration and setup notes.
- Source at 1264d9e2026-09-22The commit this page was read against.
- AssemblyAI HTTP tools tutorialReferenceReference for the integration shape, not a SessionBook feature.
No demo is linked. Setup instructions and an agent ID file don’t establish a deployment.