Skip to content
Learn Agentic AI11 min read0 views

AI Agent for Fitness Studios: Class Booking, Membership Inquiries, and Trial Signups

Build an AI agent that handles class bookings, answers membership questions, manages trial signups, and drives retention for fitness studios — from yoga studios to CrossFit boxes.

Fitness Studios Live and Die by Their Front Desk

A fitness studio's revenue depends on two things: getting new members in the door and keeping existing members coming back. Both start at the front desk — answering calls about class schedules, explaining membership options, signing up trial visitors, and rebooking members who are about to lapse. An AI agent handles all of these conversations simultaneously, never puts a caller on hold, and can nudge lapsed members back to class with a well-timed follow-up.

Studio Data Model

Fitness studios revolve around classes, instructors, memberships, and attendance. We model these relationships to give the agent the context it needs.

from dataclasses import dataclass, field
from datetime import datetime, date, time, timedelta
from enum import Enum
from typing import Optional


class MembershipTier(Enum):
    TRIAL = "trial"
    BASIC = "basic"        # 4 classes/month
    UNLIMITED = "unlimited" # unlimited classes
    PREMIUM = "premium"     # unlimited + perks


class ClassStatus(Enum):
    OPEN = "open"
    FULL = "full"
    WAITLISTED = "waitlisted"
    CANCELLED = "cancelled"


@dataclass
class FitnessClass:
    id: str
    name: str
    instructor: str
    day_of_week: str
    start_time: time
    duration_minutes: int
    capacity: int
    enrolled: int = 0
    difficulty: str = "all levels"
    description: str = ""

    @property
    def spots_left(self) -> int:
        return max(0, self.capacity - self.enrolled)

    @property
    def status(self) -> ClassStatus:
        if self.enrolled >= self.capacity:
            return ClassStatus.FULL
        return ClassStatus.OPEN


@dataclass
class Membership:
    tier: MembershipTier
    monthly_price: float
    classes_per_month: Optional[int]  # None = unlimited
    perks: list[str] = field(default_factory=list)
    contract_months: int = 0  # 0 = month-to-month


@dataclass
class Member:
    id: str
    name: str
    phone: str
    email: str
    membership: MembershipTier
    classes_this_month: int = 0
    join_date: Optional[date] = None
    last_class_date: Optional[date] = None

Class Schedule and Booking Engine

WEEKLY_SCHEDULE: list[FitnessClass] = [
    FitnessClass("c1", "Morning Vinyasa", "Lisa", "monday", time(6, 30), 60, 20, 18),
    FitnessClass("c2", "HIIT Burn", "Marcus", "monday", time(17, 30), 45, 25, 25),
    FitnessClass("c3", "Beginner Yoga", "Lisa", "tuesday", time(9, 0), 60, 15, 8),
    FitnessClass("c4", "Spin & Core", "Jade", "wednesday", time(6, 0), 45, 20, 14),
    FitnessClass("c5", "Power Sculpt", "Marcus", "thursday", time(18, 0), 50, 25, 22),
    FitnessClass("c6", "Restorative Yoga", "Lisa", "friday", time(10, 0), 75, 12, 6),
    FitnessClass("c7", "Weekend Warrior HIIT", "Marcus", "saturday", time(8, 0), 45, 30, 28),
]

MEMBERSHIP_TIERS = {
    MembershipTier.TRIAL: Membership(
        MembershipTier.TRIAL, 0, 2,
        perks=["2 free classes", "Locker rental included"],
    ),
    MembershipTier.BASIC: Membership(
        MembershipTier.BASIC, 59, 4,
        perks=["4 classes/month", "10% retail discount"],
    ),
    MembershipTier.UNLIMITED: Membership(
        MembershipTier.UNLIMITED, 99, None,
        perks=["Unlimited classes", "Free mat rental", "15% retail discount"],
    ),
    MembershipTier.PREMIUM: Membership(
        MembershipTier.PREMIUM, 149, None,
        perks=["Unlimited classes", "1 guest pass/month",
               "Free retail item/quarter", "Priority booking"],
        contract_months=6,
    ),
}

waitlist: dict[str, list[str]] = {}  # class_id -> list of member names


class BookingEngine:
    def book_class(self, member: Member, class_id: str) -> dict:
        fitness_class = next(
            (c for c in WEEKLY_SCHEDULE if c.id == class_id), None
        )
        if not fitness_class:
            return {"success": False, "message": "Class not found."}

        # Check membership class limit
        tier = MEMBERSHIP_TIERS[member.membership]
        if tier.classes_per_month and member.classes_this_month >= tier.classes_per_month:
            return {
                "success": False,
                "message": (
                    f"You have used all {tier.classes_per_month} classes "
                    f"this month. Upgrade to Unlimited for more."
                ),
            }

        if fitness_class.status == ClassStatus.FULL:
            waitlist.setdefault(class_id, []).append(member.name)
            position = len(waitlist[class_id])
            return {
                "success": False,
                "message": f"Class is full. Added to waitlist (position {position}).",
            }

        fitness_class.enrolled += 1
        member.classes_this_month += 1
        return {
            "success": True,
            "message": (
                f"Booked: {fitness_class.name} with {fitness_class.instructor} "
                f"on {fitness_class.day_of_week.title()} at "
                f"{fitness_class.start_time.strftime('%I:%M %p')}. "
                f"Spots remaining: {fitness_class.spots_left}."
            ),
        }

Trial Signup and Conversion

Trial conversion is where fitness studios make or break their growth. The agent should make signing up frictionless and highlight what the prospect will experience.

See AI Voice Agents Handle Real Calls

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

trial_signups: list[dict] = []


def create_trial_signup(
    name: str, phone: str, email: str, interests: str
) -> dict:
    signup = {
        "name": name,
        "phone": phone,
        "email": email,
        "interests": interests,
        "signed_up_at": datetime.now().isoformat(),
        "classes_remaining": 2,
        "converted": False,
    }
    trial_signups.append(signup)
    return {
        "message": (
            f"Welcome, {name}! Your free trial includes 2 classes. "
            f"Based on your interest in {interests}, I recommend starting with "
            f"our Beginner Yoga on Tuesday at 9 AM or Morning Vinyasa on Monday "
            f"at 6:30 AM. Shall I book one for you?"
        ),
        "recommended_classes": ["c3", "c1"],
    }

Agent Tools and Assembly

from agents import Agent, Runner, function_tool

booking_engine = BookingEngine()

MEMBERS_DB = {
    "maria-garcia": Member("m1", "Maria Garcia", "555-0201", "maria@email.com", MembershipTier.UNLIMITED, 3, date(2025, 9, 1), date(2026, 3, 10)),
}


@function_tool
def get_class_schedule(day: str = "", class_type: str = "") -> str:
    """Get the class schedule, optionally filtered by day or type."""
    classes = WEEKLY_SCHEDULE
    if day:
        classes = [c for c in classes if c.day_of_week == day.lower()]
    if class_type:
        classes = [c for c in classes if class_type.lower() in c.name.lower()]
    if not classes:
        return "No classes found matching your criteria."
    lines = []
    for c in classes:
        lines.append(
            f"{c.name} ({c.difficulty}) - {c.day_of_week.title()} "
            f"{c.start_time.strftime('%I:%M %p')} with {c.instructor} "
            f"[{c.spots_left}/{c.capacity} spots]"
        )
    return "\n".join(lines)


@function_tool
def book_class(member_name: str, class_id: str) -> str:
    """Book a class for a member."""
    key = member_name.lower().replace(" ", "-")
    member = MEMBERS_DB.get(key)
    if not member:
        return f"Member '{member_name}' not found."
    result = booking_engine.book_class(member, class_id)
    return result["message"]


@function_tool
def get_membership_info(tier: str = "") -> str:
    """Get information about membership tiers and pricing."""
    if tier:
        t = MembershipTier(tier.lower())
        m = MEMBERSHIP_TIERS.get(t)
        if not m:
            return "Tier not found."
        perks = ", ".join(m.perks)
        return f"{t.value.title()}: ${m.monthly_price}/month - {perks}"
    lines = []
    for t, m in MEMBERSHIP_TIERS.items():
        if t == MembershipTier.TRIAL:
            continue
        perks = ", ".join(m.perks)
        contract = f" ({m.contract_months}-month commitment)" if m.contract_months else " (month-to-month)"
        lines.append(f"{t.value.title()}: ${m.monthly_price}/mo{contract} - {perks}")
    return "\n".join(lines)


@function_tool
def signup_trial(name: str, phone: str, email: str, interests: str) -> str:
    """Sign up a new visitor for a free trial."""
    result = create_trial_signup(name, phone, email, interests)
    return result["message"]


studio_agent = Agent(
    name="FitLife Studio Assistant",
    instructions="""You are an enthusiastic, encouraging assistant for FitLife Studio.

1. For class schedule questions, use get_class_schedule. Highlight classes
   with available spots.
2. To book a class, use book_class. If the class is full, mention the
   waitlist and suggest alternatives.
3. When someone asks about membership, use get_membership_info.
   Recommend Unlimited for people who want to come 3+ times per week.
4. For new visitors, use signup_trial to register them. Recommend
   classes based on their stated interests and fitness level.
5. Be energetic and supportive. Use the member's first name.
6. If a member has not visited in 2+ weeks, gently encourage them
   to get back to class.""",
    tools=[get_class_schedule, book_class, get_membership_info, signup_trial],
)

FAQ

How does the agent handle class cancellations and no-shows?

Add a cancel_booking tool that marks the member's enrollment as cancelled and decrements the class enrollment count. When a spot opens, check the waitlist for that class and automatically notify the first person in line. For no-shows, implement a policy (e.g., three no-shows results in a booking restriction) and have the agent enforce it during the booking flow.

Can the agent run promotions or discounts?

Yes. Add a promotions table with start dates, end dates, and discount rules. The get_membership_info tool checks for active promotions and includes them in the response. For example, "This week only: first month of Unlimited is $79 instead of $99."

How do I track trial-to-member conversion rates?

Log every trial signup with a timestamp, then track whether the trial member converts to a paid membership within 14 days. The agent can proactively follow up after the first trial class to ask about their experience and present membership options. Conversion analytics come from querying the signup log against the membership database.


#FitnessStudio #ClassBooking #MembershipAI #TrialConversion #Python #AgenticAI #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.