By Sagar Shankaran, Founder of CallSphere
Learn how to build an AI tutoring agent that assesses student knowledge, adapts difficulty in real time, and uses scaffolding techniques to guide learners through complex topics.
Key takeaways
Traditional educational software serves the same content to every student regardless of their current understanding. A student who already grasps algebra fundamentals gets the same explanation as one who is struggling with basic variables. This one-size-fits-all approach wastes time for advanced learners and frustrates beginners.
An adaptive tutoring agent solves this by continuously assessing what the student knows, adjusting the difficulty of questions and explanations, and providing scaffolded support that meets each learner exactly where they are. The core loop is simple: assess, explain, practice, reassess.
A tutoring agent operates on a continuous feedback cycle with four stages:
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
Here is the data model that tracks a student's progress through this loop:
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
class Mastery(Enum):
NOVICE = "novice"
DEVELOPING = "developing"
PROFICIENT = "proficient"
ADVANCED = "advanced"
@dataclass
class TopicState:
topic: str
mastery: Mastery = Mastery.NOVICE
attempts: int = 0
correct: int = 0
last_misconception: Optional[str] = None
@property
def accuracy(self) -> float:
if self.attempts == 0:
return 0.0
return self.correct / self.attempts
@dataclass
class StudentProfile:
student_id: str
topics: dict[str, TopicState] = field(default_factory=dict)
difficulty_level: int = 1 # 1-5 scale
preferred_explanation_style: str = "analogy"
def update_mastery(self, topic: str, was_correct: bool,
misconception: Optional[str] = None):
if topic not in self.topics:
self.topics[topic] = TopicState(topic=topic)
state = self.topics[topic]
state.attempts += 1
if was_correct:
state.correct += 1
if misconception:
state.last_misconception = misconception
# Update mastery level based on rolling accuracy
if state.attempts >= 3:
if state.accuracy >= 0.9:
state.mastery = Mastery.ADVANCED
elif state.accuracy >= 0.7:
state.mastery = Mastery.PROFICIENT
elif state.accuracy >= 0.4:
state.mastery = Mastery.DEVELOPING
else:
state.mastery = Mastery.NOVICE
The agent uses the student profile to generate appropriately leveled explanations and questions. The key insight is that the system prompt changes dynamically based on the student's current mastery:
from agents import Agent, Runner, function_tool
import json
def build_tutor_instructions(profile: StudentProfile,
current_topic: str) -> str:
state = profile.topics.get(current_topic, TopicState(current_topic))
mastery = state.mastery.value
scaffolding_rules = {
"novice": (
"Use simple vocabulary. Break every concept into small steps. "
"Provide concrete real-world analogies. Ask one question at a time."
),
"developing": (
"Use moderate vocabulary. Introduce formal terminology alongside "
"plain language. Include two-step problems."
),
"proficient": (
"Use standard academic language. Present multi-step problems. "
"Encourage the student to explain their reasoning."
),
"advanced": (
"Challenge with edge cases and synthesis questions. "
"Ask the student to connect concepts across topics."
),
}
misconception_note = ""
if state.last_misconception:
misconception_note = (
f"\nIMPORTANT: The student previously showed this "
f"misconception: {state.last_misconception}. "
f"Address it proactively in your explanation."
)
return f"""You are a patient, encouraging tutor teaching {current_topic}.
Student mastery level: {mastery}
Student accuracy so far: {state.accuracy:.0%} over {state.attempts} attempts
Scaffolding approach: {scaffolding_rules[mastery]}
{misconception_note}
Always end your explanation with a practice question appropriate to the
student's level. Format the question clearly so it can be extracted."""
The agent needs a tool to evaluate student responses and update their profile:
student_db: dict[str, StudentProfile] = {}
@function_tool
def evaluate_student_response(
student_id: str,
topic: str,
student_answer: str,
correct_answer: str,
is_correct: bool,
misconception: str = "",
) -> str:
"""Evaluate a student response and update their mastery tracking."""
profile = student_db.get(student_id)
if not profile:
profile = StudentProfile(student_id=student_id)
student_db[student_id] = profile
profile.update_mastery(topic, is_correct, misconception or None)
state = profile.topics[topic]
return json.dumps({
"mastery": state.mastery.value,
"accuracy": f"{state.accuracy:.0%}",
"attempts": state.attempts,
"recommendation": _get_recommendation(state),
})
def _get_recommendation(state: TopicState) -> str:
if state.accuracy < 0.4 and state.attempts >= 3:
return "revisit_fundamentals"
elif state.accuracy >= 0.9 and state.attempts >= 5:
return "advance_to_next_topic"
else:
return "continue_practice"
Tie everything together into a session loop that continuously adapts:
import asyncio
async def tutoring_session(student_id: str, topic: str):
profile = student_db.get(
student_id, StudentProfile(student_id=student_id)
)
student_db[student_id] = profile
tutor = Agent(
name="Adaptive Tutor",
instructions=build_tutor_instructions(profile, topic),
tools=[evaluate_student_response],
)
# Initial assessment question
result = await Runner.run(
tutor,
f"Start by asking a diagnostic question about {topic} "
f"to assess what the student already knows.",
)
print(f"Tutor: {result.final_output}")
# Interactive loop
while True:
student_input = input("Student: ")
if student_input.lower() in ("quit", "exit"):
break
# Rebuild instructions with updated profile
tutor = Agent(
name="Adaptive Tutor",
instructions=build_tutor_instructions(profile, topic),
tools=[evaluate_student_response],
)
result = await Runner.run(tutor, student_input)
print(f"Tutor: {result.final_output}")
asyncio.run(tutoring_session("student-1", "fractions"))
The agent rebuilds its instructions each turn so the scaffolding adapts as the student's mastery changes. A student who answers three fraction questions correctly will see the tutor shift from basic analogies to multi-step word problems automatically.
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.
The mastery tracking system monitors rolling accuracy over a minimum number of attempts. Once a student reaches 70% accuracy over at least three attempts, their mastery level increases, which changes the scaffolding rules in the system prompt. This prevents premature advancement from a single lucky answer.
Yes. The tutoring loop pattern is subject-agnostic. For language learning you would track vocabulary mastery, for history you would track understanding of events and causal relationships. The key is defining what "mastery" means for each topic and what misconceptions are common.
The system prompt explicitly instructs the agent to use scaffolding — guiding the student toward the answer rather than stating it directly. You can reinforce this by adding a guardrail tool that flags when the agent's response contains a direct answer to its own practice question, then asks the agent to rephrase as a hint instead.
#AITutoring #AdaptiveLearning #EducationAI #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.
The 2026 desktop AI agent landscape — ServiceNow Project Arc, Anthropic Claude offerings, OpenAI agents, and Google Mariner. A buyer's map.
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.
An agentic-AI perspective on Anthropic Skills system, covering orchestration patterns, tool use, and how agent tooling fits production agent stacks.
Enterprise CIO Guide perspective on Comet's general-availability launch put an agentic browser in front of millions of consumers, and it works better than the demos suggested.
Enterprise CIO Guide perspective on Harvey AI's enterprise rollout numbers show legal agents have moved past the pilot stage at AmLaw 100 firms.
Enterprise CIO Guide perspective on Hippocratic AI's deployment numbers show healthcare voice agents are moving from pilot to production across major US health systems.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco