Skip to content
Learn Agentic AI14 min read0 views

Building a Restaurant Reservation Agent: AI-Powered Booking and Waitlist Management

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.

Why Restaurants Need AI Reservation Agents

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.

Modeling Restaurant Capacity

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.

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.

Building the Reservation Agent

With the capacity model in place, the agent exposes booking tools that the language model can invoke during conversation.

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

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],
)

Implementing the Waitlist

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

FAQ

How does the agent handle same-day vs. advance reservations differently?

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.

What happens when a guest wants to modify an existing reservation?

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.

How does the waitlist promotion logic avoid double-booking?

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

Share this article
C

CallSphere Team

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.