Building a Study Planner Agent: Personalized Learning Schedules with Spaced Repetition
Create an AI study planner agent that builds personalized schedules using spaced repetition algorithms, optimizes review timing, and adapts to individual learning pace and availability.
The Science Behind Spaced Repetition
Research in cognitive science consistently shows that distributing practice over time produces dramatically better retention than massing it into a single session. The spacing effect, first documented by Hermann Ebbinghaus in 1885, demonstrates that memories decay exponentially but each successful review extends the retention interval. A study planner agent leverages this principle to schedule reviews at optimal intervals — just before the student would forget.
The challenge is that optimal intervals vary per student and per topic. An AI agent can personalize these intervals by tracking individual performance and adjusting the schedule dynamically.
The Forgetting Curve Model
Start with a mathematical model of memory decay that the agent uses to predict when a student will forget a given topic:
import math
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from typing import Optional
@dataclass
class StudyItem:
item_id: str
topic: str
subject: str
difficulty: float = 0.3 # 0.0 = easy, 1.0 = hard
stability: float = 1.0 # days until 90% recall probability
last_reviewed: Optional[datetime] = None
review_count: int = 0
easiness_factor: float = 2.5 # SM-2 style factor
consecutive_correct: int = 0
def recall_probability(self, now: Optional[datetime] = None) -> float:
"""Estimate probability the student remembers this item."""
if self.last_reviewed is None:
return 0.0
now = now or datetime.now()
days_elapsed = (now - self.last_reviewed).total_seconds() / 86400
# Exponential decay model
return math.exp(-days_elapsed / self.stability)
def next_review_date(self, target_recall: float = 0.85) -> datetime:
"""Calculate when recall probability drops to the target."""
if self.last_reviewed is None:
return datetime.now()
# Solve: target = exp(-days / stability) for days
days_until_target = -self.stability * math.log(target_recall)
return self.last_reviewed + timedelta(days=days_until_target)
def update_after_review(self, quality: int):
"""Update stability and easiness after a review.
quality: 0-5 scale (SM-2 convention)
0-2 = incorrect/difficult, 3 = correct with hesitation,
4 = correct, 5 = perfect
"""
self.review_count += 1
self.last_reviewed = datetime.now()
if quality >= 3:
self.consecutive_correct += 1
# Increase stability (longer intervals)
if self.consecutive_correct == 1:
self.stability = 1.0
elif self.consecutive_correct == 2:
self.stability = 6.0
else:
self.stability *= self.easiness_factor
else:
self.consecutive_correct = 0
self.stability = 1.0 # Reset to short interval
# Update easiness factor (SM-2 formula)
self.easiness_factor = max(
1.3,
self.easiness_factor
+ 0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02),
)
Schedule Optimization
Given a student's available time slots and a set of items due for review, the agent needs to build an optimal daily schedule:
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
@dataclass
class TimeSlot:
start: datetime
end: datetime
energy_level: str = "medium" # low, medium, high
@property
def duration_minutes(self) -> int:
return int((self.end - self.start).total_seconds() / 60)
@dataclass
class ScheduleEntry:
item: StudyItem
scheduled_time: datetime
duration_minutes: int
priority: float
@dataclass
class StudySchedule:
date: datetime
entries: list[ScheduleEntry] = field(default_factory=list)
total_minutes: int = 0
def build_daily_schedule(
items: list[StudyItem],
available_slots: list[TimeSlot],
max_new_items: int = 5,
minutes_per_review: int = 5,
minutes_per_new: int = 15,
) -> StudySchedule:
"""Build an optimized study schedule for today."""
now = datetime.now()
schedule = StudySchedule(date=now)
# Separate overdue reviews from new items
overdue = [
item for item in items
if item.last_reviewed and item.recall_probability(now) < 0.85
]
new_items = [
item for item in items if item.last_reviewed is None
][:max_new_items]
# Sort overdue by urgency (lowest recall first)
overdue.sort(key=lambda i: i.recall_probability(now))
# Priority: overdue reviews first, then new items
# High-energy slots get difficult or new material
available_slots.sort(
key=lambda s: {"high": 0, "medium": 1, "low": 2}[s.energy_level]
)
slot_idx = 0
remaining_minutes = (
available_slots[0].duration_minutes if available_slots else 0
)
for item in overdue:
if slot_idx >= len(available_slots):
break
schedule.entries.append(ScheduleEntry(
item=item,
scheduled_time=available_slots[slot_idx].start,
duration_minutes=minutes_per_review,
priority=1.0 - item.recall_probability(now),
))
remaining_minutes -= minutes_per_review
schedule.total_minutes += minutes_per_review
if remaining_minutes <= 0:
slot_idx += 1
if slot_idx < len(available_slots):
remaining_minutes = (
available_slots[slot_idx].duration_minutes
)
# Add new material in high-energy slots
for item in new_items:
if slot_idx >= len(available_slots):
break
schedule.entries.append(ScheduleEntry(
item=item,
scheduled_time=available_slots[slot_idx].start,
duration_minutes=minutes_per_new,
priority=0.5,
))
remaining_minutes -= minutes_per_new
schedule.total_minutes += minutes_per_new
if remaining_minutes <= 0:
slot_idx += 1
if slot_idx < len(available_slots):
remaining_minutes = (
available_slots[slot_idx].duration_minutes
)
return schedule
The Study Planner Agent
The agent uses these scheduling algorithms as tools and communicates the plan in natural language:
from agents import Agent, Runner, function_tool
import json
@function_tool
def get_todays_schedule(
student_id: str,
available_hours: float,
energy_pattern: str,
) -> str:
"""Generate today's optimized study schedule."""
# In production, load from database
items = load_student_items(student_id)
slots = parse_availability(available_hours, energy_pattern)
schedule = build_daily_schedule(items, slots)
return json.dumps({
"total_minutes": schedule.total_minutes,
"review_count": len([
e for e in schedule.entries if e.item.review_count > 0
]),
"new_count": len([
e for e in schedule.entries if e.item.review_count == 0
]),
"entries": [
{
"topic": e.item.topic,
"type": "review" if e.item.review_count > 0 else "new",
"duration": e.duration_minutes,
"recall_probability": f"{e.item.recall_probability():.0%}",
}
for e in schedule.entries
],
})
study_planner = Agent(
name="Study Planner",
instructions="""You are a study planning assistant that creates
personalized schedules based on spaced repetition science.
When presenting a schedule:
1. Start with a brief overview of what is due today and why
2. List each study block with topic, duration, and purpose
3. Explain WHY certain topics are prioritized (low recall probability)
4. Suggest the best order based on energy levels
5. End with encouragement and what to expect tomorrow
Be specific about timing. Instead of 'review math', say 'review
quadratic equations — your recall has dropped to 40%, so this
needs attention today to prevent forgetting.'""",
tools=[get_todays_schedule],
)
FAQ
How does the spaced repetition algorithm handle topics a student finds consistently easy?
The easiness factor in the SM-2 algorithm increases for items the student consistently rates highly, which extends the stability value exponentially. An item rated "perfect" five times in a row might not appear for review for several months. The system naturally concentrates review time on difficult material while letting easy items fade into long-interval maintenance reviews.
What happens if a student misses several scheduled review sessions?
When a student returns after a gap, the recall probability for overdue items will have dropped significantly. The scheduler prioritizes these by urgency — items with the lowest recall probability appear first. However, it also respects the student's available time, so it will not schedule four hours of reviews just because everything is overdue. Instead, it triages the most critical items and spreads the backlog across several days.
Can the agent account for exam dates and deadlines?
Yes. You extend the scheduling logic with a deadline parameter that overrides the normal spaced repetition intervals. When an exam is approaching, the scheduler increases review frequency for exam-relevant topics and front-loads new material that needs to be learned before the deadline, while still maintaining minimum review intervals for retention.
#SpacedRepetition #StudyPlanning #EducationAI #Python #LearningScience #AgenticAI #LearnAI #AIEngineering
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.