Building offSchool: AI Adaptive Learning + Knowledge Graphs
offSchool builds personalized study plans with spaced repetition and knowledge graphs on a Next.js + Go + Python stack. Here's the architecture and design decisions.
offSchool is an AI-powered adaptive learning platform that generates personalized study plans using knowledge graphs and spaced repetition. Unlike generic flashcard apps, offSchool understands which concepts you know, which you're shaky on, and — critically — which concepts are blocking your understanding of others.
The core insight: learning has a dependency graph
Every field of knowledge is a directed graph:
Linear Algebra
├── Vector Spaces → requires Matrix Operations
├── Eigenvalues → requires Linear Independence
├── Matrix Operations → requires Scalar Multiplication
└── ...
Most learning apps treat content as a flat list. offSchool treats it as a graph. If you can't factor polynomials, the platform knows not to show you calculus — and it knows exactly why.
Knowledge graph data model
CREATE TABLE concepts (
id UUID PRIMARY KEY,
subject TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT,
difficulty INT CHECK (difficulty BETWEEN 1 AND 10),
embedding vector(1536) -- for semantic similarity search
);
CREATE TABLE prerequisites (
concept_id UUID REFERENCES concepts(id),
prerequisite_id UUID REFERENCES concepts(id),
strength FLOAT DEFAULT 1.0, -- 0.5 = soft prereq, 1.0 = hard
PRIMARY KEY (concept_id, prerequisite_id)
);
CREATE TABLE user_mastery (
user_id UUID,
concept_id UUID REFERENCES concepts(id),
score FLOAT DEFAULT 0.0, -- 0.0 = unknown, 1.0 = mastered
next_review TIMESTAMPTZ, -- SM-2 spaced repetition
reviews INT DEFAULT 0,
PRIMARY KEY (user_id, concept_id)
);
The prerequisites table with strength values enables soft and hard prerequisites. A hard prerequisite (strength: 1.0) blocks the concept entirely. A soft prerequisite (strength: 0.5) adjusts difficulty weighting but doesn't block.
Spaced repetition with SM-2
offSchool uses the SM-2 algorithm for review scheduling:
def sm2_update(quality: int, repetitions: int, easiness: float, interval: int):
"""
quality: 0-5 (0=blackout, 5=perfect recall)
Returns: (new_repetitions, new_easiness, new_interval_days)
"""
if quality < 3:
repetitions = 0
interval = 1
else:
if repetitions == 0:
interval = 1
elif repetitions == 1:
interval = 6
else:
interval = round(interval * easiness)
repetitions += 1
easiness = max(1.3, easiness + 0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02))
return repetitions, easiness, interval
The next_review timestamp is set based on now() + interval days. Items due for review surface at the top of the study queue.
Personalized study plan generation
The study plan algorithm combines three signals:
- Prerequisite satisfaction — which concepts are unlocked given current mastery?
- SM-2 due queue — which concepts need review today?
- Learning velocity — what's the fastest path to a user-stated goal?
func (s *StudyPlanner) GeneratePlan(ctx context.Context, userID, goalConceptID uuid.UUID) (StudyPlan, error) {
mastery, err := s.db.GetUserMastery(ctx, userID)
// Find all unmastered prerequisites on path to goal
path := s.graph.ShortestPath(mastery.Mastered(), goalConceptID)
// Get SM-2 due items
dueReviews, _ := s.db.GetDueReviews(ctx, userID, time.Now())
// Merge: reviews first (SM-2 efficiency), then new concepts
plan := StudyPlan{}
plan.Sessions = append(plan.Sessions, dueReviews...)
for _, concept := range path {
if !mastery.IsMastered(concept) {
plan.Sessions = append(plan.Sessions, concept)
}
}
// Trim to 45-minute session (~8-10 items)
return plan.Trim(10), nil
}
The shortest path algorithm uses Dijkstra with edge weights = prerequisite strength. Stronger prerequisites are traversed first.
AI content generation
When a user encounters a concept they don't understand, offSchool generates an explanation tailored to their current mastery level:
EXPLANATION_PROMPT = """
User knows: {known_concepts}
User struggles with: {weak_concepts}
Concept to explain: {concept_name}
Generate a 200-word explanation that:
1. Builds on what the user already knows
2. Uses analogies from their strongest known concepts
3. Ends with one concrete worked example
4. Avoids prerequisite concepts they haven't mastered yet
"""
The explanation is generated once and cached per (user_mastery_snapshot, concept) pair. Cache key includes a hash of the user's mastery vector — similar mastery profiles share cached explanations.
Stack
- Frontend: Next.js 15 App Router + TypeScript + Tailwind
- API: Go + Chi router + sqlc
- ML/AI: Python FastAPI service (SM-2, path finding, embedding generation)
- DB: PostgreSQL with pgvector extension
- Deployment: AWS ECS Fargate (Go + Python services), Cloudflare Pages (Next.js)
FAQ
What is offSchool? offSchool is an AI adaptive learning platform that builds personalized study plans using knowledge graphs (concept prerequisites) and spaced repetition (SM-2 algorithm) to help learners reach their goals efficiently.
What is spaced repetition? Spaced repetition is a learning technique that schedules reviews at increasing intervals based on recall quality. The SM-2 algorithm, used by Anki and offSchool, calculates optimal review timing to maximize long-term retention.
How does offSchool use knowledge graphs? offSchool models each subject as a directed graph where concepts depend on prerequisites. The platform identifies which concepts are currently accessible given a learner's mastery, avoiding presenting content that requires unlearned foundations.
What subjects does offSchool support? Mathematics, computer science, physics, and chemistry. Each subject's knowledge graph is curated manually and refined by tracking which prerequisites actually correlate with mastery.
Written by Shihab Shahriar Antor — AI Engineer & Founder of Shahriar Labs. See also: Building Context-Heavy: Knowledge-Graph API for AI Agents · Building QuantumSketch: AI + Manim for STEM Video.