By Sagar Shankaran, Founder of CallSphere
Learn how to build an AI agent that manages restaurant reservations, handles capacity logic, confirms and cancels bookings, and operates an intelligent waitlist with automatic promotion.
Key takeaways
Restaurants lose an estimated 20 percent of potential bookings because phone lines go unanswered during peak hours. A human host can handle one call at a time, but an AI reservation agent manages unlimited concurrent conversations across phone, web chat, and messaging platforms simultaneously.
Beyond simple booking, a well-built reservation agent handles capacity optimization, waitlist management, confirmation reminders, and cancellation recovery — turning a cost center into a revenue multiplier.
Before an agent can book tables, it needs to understand the restaurant's physical constraints. This means modeling table inventory, seating configurations, and time-slot availability.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent for hospitality in your browser — 60 seconds, no signup.
flowchart LR
CALLER(["Guest or Prospect"])
subgraph TEL["Telephony"]
SIP["Twilio SIP and PSTN"]
end
subgraph BRAIN["Hotel Concierge AI Agent"]
STT["Streaming STT<br/>Deepgram or Whisper"]
NLU{"Intent and<br/>Entity Extraction"}
TOOLS["Tool Calls"]
TTS["Streaming TTS<br/>ElevenLabs or Rime"]
end
subgraph DATA["Live Data Plane"]
CRM[("CRM and Notes")]
CAL[("Calendar and<br/>Schedule")]
KB[("Knowledge Base<br/>and Policies")]
end
subgraph OUT["Outcomes"]
O1(["Reservation confirmed"])
O2(["Room service order"])
O3(["Front desk handoff"])
end
CALLER --> SIP --> STT --> NLU
NLU -->|Lookup| TOOLS
TOOLS <--> CRM
TOOLS <--> CAL
TOOLS <--> KB
NLU --> TTS --> SIP --> CALLER
NLU -->|Resolved| O1
NLU -->|Schedule| O2
NLU -->|Escalate| O3
style CALLER fill:#f1f5f9,stroke:#64748b,color:#0f172a
style NLU fill:#4f46e5,stroke:#4338ca,color:#fff
style O1 fill:#059669,stroke:#047857,color:#fff
style O2 fill:#0ea5e9,stroke:#0369a1,color:#fff
style O3 fill:#f59e0b,stroke:#d97706,color:#1f2937
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from enum import Enum
class TableStatus(Enum):
AVAILABLE = "available"
RESERVED = "reserved"
OCCUPIED = "occupied"
BLOCKED = "blocked"
@dataclass
class Table:
table_id: str
capacity: int
section: str
status: TableStatus = TableStatus.AVAILABLE
@dataclass
class TimeSlot:
start: datetime
duration: timedelta = timedelta(hours=1, minutes=30)
@property
def end(self) -> datetime:
return self.start + self.duration
@dataclass
class Restaurant:
name: str
tables: list[Table] = field(default_factory=list)
reservations: list[dict] = field(default_factory=list)
default_dining_duration: timedelta = timedelta(hours=1, minutes=30)
def available_tables(self, party_size: int, requested_time: datetime) -> list[Table]:
slot_end = requested_time + self.default_dining_duration
reserved_table_ids = set()
for res in self.reservations:
if res["status"] == "confirmed":
if res["start"] < slot_end and res["end"] > requested_time:
reserved_table_ids.add(res["table_id"])
return [
t for t in self.tables
if t.capacity >= party_size
and t.table_id not in reserved_table_ids
and t.status != TableStatus.BLOCKED
]
def best_fit_table(self, party_size: int, requested_time: datetime) -> Table | None:
candidates = self.available_tables(party_size, requested_time)
if not candidates:
return None
# Pick the smallest table that fits to maximize capacity utilization
return min(candidates, key=lambda t: t.capacity)
The best_fit_table method applies a bin-packing heuristic: it assigns the smallest table that accommodates the party. This prevents a party of two from claiming a table for eight during a busy evening.
With the capacity model in place, the agent exposes booking tools that the language model can invoke during conversation.
from agents import Agent, Runner, function_tool
from datetime import datetime, timedelta
restaurant = Restaurant(
name="Trattoria Bella",
tables=[
Table("T1", 2, "patio"),
Table("T2", 2, "patio"),
Table("T3", 4, "main"),
Table("T4", 4, "main"),
Table("T5", 6, "private"),
Table("T6", 8, "private"),
],
)
@function_tool
def check_availability(date: str, time: str, party_size: int) -> str:
requested = datetime.strptime(f"{date} {time}", "%Y-%m-%d %H:%M")
tables = restaurant.available_tables(party_size, requested)
if not tables:
return f"No tables available for {party_size} guests at {time} on {date}."
sections = set(t.section for t in tables)
return f"{len(tables)} tables available in: {', '.join(sections)}."
@function_tool
def create_reservation(
guest_name: str, phone: str, date: str, time: str, party_size: int
) -> str:
requested = datetime.strptime(f"{date} {time}", "%Y-%m-%d %H:%M")
table = restaurant.best_fit_table(party_size, requested)
if not table:
return "UNAVAILABLE: No suitable table found for this time and party size."
reservation = {
"guest": guest_name,
"phone": phone,
"table_id": table.table_id,
"party_size": party_size,
"start": requested,
"end": requested + restaurant.default_dining_duration,
"status": "confirmed",
}
restaurant.reservations.append(reservation)
return (
f"Reservation confirmed for {guest_name}, party of {party_size}, "
f"at {time} on {date}. Table {table.table_id} ({table.section} section). "
f"A confirmation SMS will be sent to {phone}."
)
@function_tool
def cancel_reservation(guest_name: str, date: str, time: str) -> str:
requested = datetime.strptime(f"{date} {time}", "%Y-%m-%d %H:%M")
for res in restaurant.reservations:
if res["guest"] == guest_name and res["start"] == requested:
res["status"] = "cancelled"
return f"Reservation for {guest_name} at {time} on {date} has been cancelled."
return f"No reservation found for {guest_name} at {time} on {date}."
reservation_agent = Agent(
name="Reservation Agent",
instructions="""You are the reservation agent for Trattoria Bella.
Help guests book, modify, or cancel reservations. Always confirm the
date, time, and party size before booking. If unavailable, suggest
alternative times within 30 minutes of the requested slot.""",
tools=[check_availability, create_reservation, cancel_reservation],
)
When all tables are booked, a smart agent does not simply reject the guest. It offers waitlist placement and automatically promotes guests when cancellations open up capacity.
from collections import deque
waitlist: deque[dict] = deque()
@function_tool
def add_to_waitlist(
guest_name: str, phone: str, date: str, time: str, party_size: int
) -> str:
entry = {
"guest": guest_name,
"phone": phone,
"date": date,
"time": time,
"party_size": party_size,
"added_at": datetime.now().isoformat(),
}
waitlist.append(entry)
position = len(waitlist)
return (
f"{guest_name} added to waitlist at position {position}. "
f"We will notify {phone} if a table opens up."
)
def promote_from_waitlist(date: str, time: str) -> str | None:
requested = datetime.strptime(f"{date} {time}", "%Y-%m-%d %H:%M")
for i, entry in enumerate(waitlist):
entry_time = datetime.strptime(
f"{entry['date']} {entry['time']}", "%Y-%m-%d %H:%M"
)
if abs((entry_time - requested).total_seconds()) <= 1800:
table = restaurant.best_fit_table(entry["party_size"], requested)
if table:
promoted = waitlist[i]
del waitlist[i]
return f"Promoted {promoted['guest']} from waitlist to table {table.table_id}."
return None
Same-day reservations query real-time table status (including currently occupied tables and their expected turnover times), while advance reservations only check the future booking calendar. The agent adjusts its availability calculation based on whether the requested time is within the current service period or a future date.
Still reading? Stop comparing — try CallSphere live.
See the hospitality AI agent handle a real call — complete, industry-specific, and live in your browser. No signup.
The agent treats modifications as a cancel-and-rebook operation atomically. It first verifies availability for the new time and party size, creates the new reservation, and only then cancels the original. This prevents the guest from losing their slot if the new time is unavailable.
The promote_from_waitlist function runs through the best_fit_table method, which checks all existing confirmed reservations before assigning a table. Since confirmed reservations are added to the restaurant's reservation list immediately upon booking, the availability check is always current.
#RestaurantAI #ReservationSystem #AgenticAI #Hospitality #Python #LearnAI #AIEngineering

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
Hotels, event venues, and professional-services firms in Erbil serve guests and clients in Kurdish, Arabic, and English. CallSphere answers every call and message 24/7 and books directly.
Equatorial Guinea shops, restaurants and hotels serve a mix of local and international customers who call at all hours in several languages. See how CallSphere answers every one 24/7 and books the sale or table.
A 2026 look at Austrian SMBs and the tourism-driven hospitality sector in Vienna, Graz and the alpine regions. How CallSphere AI voice and chat agents handle multilingual bookings 24/7, DSGVO-compliant, in German, English and more.
Namib desert lodges, Swakopmund guesthouses, and tour operators use CallSphere AI voice and chat agents to answer international guests 24/7 in German, French, and English, and lock in bookings across time zones.
Hotels, guesthouses, and maritime-services businesses in Djibouti City use CallSphere AI voice and chat agents to handle bookings and enquiries in French, English, Somali, and Arabic 24/7, from $50 a month.
Chuuk Lagoon holds the greatest concentration of WWII wrecks on earth, drawing divers worldwide. See how CallSphere AI voice and chat agents help Micronesian dive operators answer overseas enquiries and fill boats 24/7.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco