By Sagar Shankaran, Founder of CallSphere
Build an AI agent for tutoring centers that matches students with the right tutors based on subject, level, and learning style, schedules sessions, tracks progress, and communicates with parents.
Key takeaways
Running a tutoring center means juggling dozens of variables: which tutor teaches which subjects, student schedules, parent preferences, session frequency, progress tracking, and makeup sessions. A center with 10 tutors and 50 students creates hundreds of scheduling combinations. Most centers manage this with spreadsheets and phone calls, which breaks down as they grow. An AI agent handles the matching, scheduling, and parent communication that consumes staff hours every week.
The matching engine needs rich profiles for both students and tutors to make intelligent pairings.
flowchart LR
CALLER(["Student or Parent"])
subgraph TEL["Telephony"]
SIP["Twilio SIP and PSTN"]
end
subgraph BRAIN["Education 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(["Enrollment captured"])
O2(["Tour scheduled"])
O3(["Counselor callback"])
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 Subject(Enum):
MATH_ALGEBRA = "algebra"
MATH_CALCULUS = "calculus"
MATH_GEOMETRY = "geometry"
PHYSICS = "physics"
CHEMISTRY = "chemistry"
ENGLISH = "english"
SAT_PREP = "sat_prep"
ACT_PREP = "act_prep"
SPANISH = "spanish"
COMPUTER_SCIENCE = "computer_science"
class GradeLevel(Enum):
ELEMENTARY = "elementary" # K-5
MIDDLE_SCHOOL = "middle_school" # 6-8
HIGH_SCHOOL = "high_school" # 9-12
COLLEGE = "college"
class LearningStyle(Enum):
VISUAL = "visual"
HANDS_ON = "hands_on"
VERBAL = "verbal"
MIXED = "mixed"
@dataclass
class Tutor:
id: str
name: str
subjects: list[Subject]
grade_levels: list[GradeLevel]
teaching_style: LearningStyle
hourly_rate: float
availability: dict[str, list[tuple[time, time]]] = field(default_factory=dict)
max_students: int = 15
current_students: int = 0
rating: float = 5.0
bio: str = ""
@dataclass
class Student:
id: str
name: str
grade_level: GradeLevel
subjects_needed: list[Subject]
learning_style: LearningStyle
parent_name: str
parent_phone: str
parent_email: str
assigned_tutor_id: Optional[str] = None
sessions_completed: int = 0
notes: str = ""
@dataclass
class TutoringSession:
id: str
student_id: str
tutor_id: str
subject: Subject
date_time: datetime
duration_minutes: int = 60
status: str = "scheduled"
progress_notes: str = ""
homework_assigned: str = ""
The matching engine scores tutor-student compatibility based on subject overlap, grade level match, learning style alignment, and tutor capacity.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
class MatchingEngine:
def __init__(self, tutors: list[Tutor]):
self.tutors = {t.id: t for t in tutors}
def find_matches(
self, student: Student, subject: Subject
) -> list[dict]:
candidates = []
for tutor in self.tutors.values():
score = self._calculate_match_score(student, tutor, subject)
if score > 0:
candidates.append({
"tutor": tutor,
"score": score,
"reasons": self._explain_match(student, tutor, subject),
})
candidates.sort(key=lambda c: c["score"], reverse=True)
return candidates[:3] # top 3 matches
def _calculate_match_score(
self, student: Student, tutor: Tutor, subject: Subject
) -> float:
score = 0.0
# Subject match (required)
if subject not in tutor.subjects:
return 0
score += 30
# Grade level match
if student.grade_level in tutor.grade_levels:
score += 25
# Learning style alignment
if student.learning_style == tutor.teaching_style:
score += 20
elif tutor.teaching_style == LearningStyle.MIXED:
score += 10
# Capacity (prefer tutors with fewer students)
utilization = tutor.current_students / tutor.max_students
score += (1 - utilization) * 15
# Rating bonus
score += tutor.rating * 2 # max 10 points
return round(score, 1)
def _explain_match(
self, student: Student, tutor: Tutor, subject: Subject
) -> list[str]:
reasons = [f"Teaches {subject.value}"]
if student.grade_level in tutor.grade_levels:
reasons.append(f"Experienced with {student.grade_level.value} students")
if student.learning_style == tutor.teaching_style:
reasons.append(f"Teaching style matches ({student.learning_style.value})")
if tutor.rating >= 4.5:
reasons.append(f"Highly rated ({tutor.rating}/5.0)")
return reasons
Parents want to know their child is improving. The agent should be able to report on session history and progress trends.
class ProgressTracker:
def __init__(self):
self.sessions: list[TutoringSession] = []
def add_session(self, session: TutoringSession):
self.sessions.append(session)
def get_student_summary(self, student_id: str) -> dict:
student_sessions = [
s for s in self.sessions if s.student_id == student_id
]
completed = [s for s in student_sessions if s.status == "completed"]
subjects_covered = set(s.subject.value for s in completed)
recent = sorted(completed, key=lambda s: s.date_time, reverse=True)[:3]
return {
"total_sessions": len(completed),
"subjects_covered": list(subjects_covered),
"recent_sessions": [
{
"date": s.date_time.strftime("%B %d"),
"subject": s.subject.value,
"notes": s.progress_notes,
"homework": s.homework_assigned,
}
for s in recent
],
}
from agents import Agent, Runner, function_tool
# Initialize with sample data
tutors = [
Tutor(
"t1", "Dr. Sarah Kim",
[Subject.MATH_ALGEBRA, Subject.MATH_CALCULUS, Subject.SAT_PREP],
[GradeLevel.HIGH_SCHOOL, GradeLevel.COLLEGE],
LearningStyle.VERBAL, 65.0,
availability={"tuesday": [(time(15, 0), time(19, 0))],
"thursday": [(time(15, 0), time(19, 0))]},
current_students=8, rating=4.9,
),
Tutor(
"t2", "Mike Torres",
[Subject.MATH_ALGEBRA, Subject.MATH_GEOMETRY, Subject.PHYSICS],
[GradeLevel.MIDDLE_SCHOOL, GradeLevel.HIGH_SCHOOL],
LearningStyle.HANDS_ON, 55.0,
availability={"monday": [(time(16, 0), time(20, 0))],
"wednesday": [(time(16, 0), time(20, 0))]},
current_students=6, rating=4.7,
),
Tutor(
"t3", "Emily Park",
[Subject.ENGLISH, Subject.SAT_PREP, Subject.ACT_PREP],
[GradeLevel.HIGH_SCHOOL],
LearningStyle.VISUAL, 60.0,
availability={"monday": [(time(15, 0), time(18, 0))],
"friday": [(time(14, 0), time(18, 0))]},
current_students=10, rating=4.8,
),
]
matching_engine = MatchingEngine(tutors)
progress_tracker = ProgressTracker()
STUDENTS_DB = {
"ethan-williams": Student(
"s1", "Ethan Williams", GradeLevel.HIGH_SCHOOL,
[Subject.MATH_ALGEBRA, Subject.SAT_PREP],
LearningStyle.HANDS_ON,
"Diana Williams", "555-0401", "diana@email.com",
assigned_tutor_id="t2", sessions_completed=8,
),
}
@function_tool
def find_tutor_match(
student_name: str, subject: str
) -> str:
"""Find the best tutor matches for a student and subject."""
key = student_name.lower().replace(" ", "-")
student = STUDENTS_DB.get(key)
if not student:
return f"Student '{student_name}' not found. Please register first."
try:
subj = Subject(subject.lower())
except ValueError:
available = [s.value for s in Subject]
return f"Subject not found. Available: {', '.join(available)}"
matches = matching_engine.find_matches(student, subj)
if not matches:
return f"No tutors available for {subject} at the {student.grade_level.value} level."
lines = []
for i, m in enumerate(matches, 1):
t = m["tutor"]
reasons = "; ".join(m["reasons"])
lines.append(
f"{i}. {t.name} (${t.hourly_rate}/hr, {t.rating}/5.0 rating)\n"
f" Why: {reasons}\n"
f" Match score: {m['score']}/100"
)
return "\n".join(lines)
@function_tool
def schedule_session(
student_name: str, tutor_name: str,
subject: str, date_time: str
) -> str:
"""Schedule a tutoring session."""
return (
f"Session scheduled:\n"
f"Student: {student_name}\n"
f"Tutor: {tutor_name}\n"
f"Subject: {subject}\n"
f"Date/Time: {date_time}\n"
f"Duration: 60 minutes\n"
f"Confirmation sent to parent."
)
@function_tool
def get_progress_report(student_name: str) -> str:
"""Get a progress summary for a student."""
key = student_name.lower().replace(" ", "-")
student = STUDENTS_DB.get(key)
if not student:
return "Student not found."
return (
f"Progress report for {student.name}:\n"
f"Sessions completed: {student.sessions_completed}\n"
f"Subjects: {', '.join(s.value for s in student.subjects_needed)}\n"
f"Current tutor: {student.assigned_tutor_id or 'Not assigned'}\n"
f"Learning style: {student.learning_style.value}"
)
@function_tool
def register_student(
name: str, grade: str, subjects: str,
learning_style: str, parent_name: str, parent_phone: str
) -> str:
"""Register a new student at the tutoring center."""
return (
f"Student registered: {name}\n"
f"Grade level: {grade}\n"
f"Subjects: {subjects}\n"
f"Parent: {parent_name} ({parent_phone})\n"
f"Next step: We will match {name} with the best available tutor."
)
tutoring_agent = Agent(
name="Tutoring Center Assistant",
instructions="""You are a helpful assistant for BrightMinds Tutoring Center.
1. For new families, use register_student to sign up, then
find_tutor_match to recommend tutors.
2. Present tutor matches with their qualifications, rates, and
match scores. Let the parent choose.
3. Once a tutor is selected, use schedule_session to book.
4. For existing students, use get_progress_report to share updates.
5. Always address the parent by name and refer to the student
by their first name.
6. Recommend session frequency based on goals: test prep needs
2-3x/week, maintenance needs 1x/week.""",
tools=[find_tutor_match, schedule_session, get_progress_report, register_student],
)
Before confirming a match, the scheduling tool checks the tutor's availability dictionary against the requested time. If a tutor is the best match but unavailable at the preferred time, the agent presents alternative time slots from that tutor's schedule. If no times work, it suggests the next-best match who has availability at the preferred time.
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.
Add a cancel_session tool that marks the session as cancelled and a reschedule_session tool that finds the next available slot with the same tutor. Many tutoring centers have a 24-hour cancellation policy — encode this as a check in the cancellation tool that warns the parent if they are cancelling within the policy window.
The agent already communicates with parents during calls. For asynchronous communication, add a send_parent_update tool that sends SMS or email summaries after each session. The tutor fills in progress notes and homework assignments, and the agent formats and sends a parent-friendly summary within an hour of session completion.
#Tutoring #StudentMatching #SessionScheduling #EducationTech #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.
A how-to for Colombian education and tutoring SMBs to answer parents and students instantly, book trial classes 24/7 in Spanish and English, and grow enrollment with a CallSphere AI agent.
Step-by-step build of a working agent with the OpenAI Agents SDK — Agent class, tools, handoffs, tracing — plus an eval pipeline that catches regressions before merge.
Smolagents lets agents write Python instead of JSON. Why code-as-action reduces tool errors and where the security trade-offs are for production deployments.
Modal turns a Python function into autoscaling serverless compute with optional GPU. Deploy a LiveKit Agent with one command and get pay-per-second billing.
Pydantic AI's April release tightens the typed-agent loop and adds structured tool definitions. Why type-safe agents reduce production bugs and speed iteration.
Khan Academy admits only 15% of students with Khanmigo access use it regularly. Here is what 108M interactions teach us about education chat agents in 2026.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco