By Sagar Shankaran, Founder of CallSphere
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.
Key takeaways
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.
Fitness studios revolve around classes, instructors, memberships, and attendance. We model these relationships to give the agent the context it needs.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
flowchart LR
CALLER(["Member or Lead"])
subgraph TEL["Telephony"]
SIP["Twilio SIP and PSTN"]
end
subgraph BRAIN["Fitness Studio 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(["Class booked"])
O2(["Trial signup captured"])
O3(["Coach 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, 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
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 conversion is where fitness studios make or break their growth. The agent should make signing up frictionless and highlight what the prospect will experience.
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"],
}
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],
)
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.
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."
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
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

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.
Discover how AI voice and chat agents automate class booking, waitlist promotion, and cancellation handling to maximize fitness studio capacity.
Fitness studios and gyms deploy CallSphere AI voice agents for class booking, membership inquiries, and retention call campaigns.
Real AI coding interview questions from Anthropic, Meta, and OpenAI in 2026. Includes implementing attention from scratch, Anthropic's progressive coding screens, Meta's AI-assisted round, and vector search — with solution approaches.
Vocode-core wires phone, browser, and Zoom voice into one Python agent class. Build a Twilio inbound bot with GPT-4o and Azure TTS — code + pitfalls.
Complete LangGraph tutorial covering state machines for agents, conditional edges, human-in-the-loop patterns, checkpointing, and parallel execution with full code examples.
Master Playwright's interaction API for AI agents — learn how to click buttons, fill forms, select dropdowns, use keyboard and mouse actions, and implement reliable waiting strategies.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco