A tutor that can't skip ahead. It decomposes a topic into a prerequisite graph and refuses to release the next element until you've demonstrated mastery of the ones it depends on.
Python · SQLite · MCP · SM-2 · Docker · GitHub Actions
Ask a model to teach you something across several sessions and it collapses the plan. It lays out a sensible curriculum, then — because you sounded confident, or the conversation drifted — delivers step six during step two and agrees you’ve understood. Everything that makes teaching work lives in the prompt, and the prompt is a suggestion.
If the next step is unavailable until a prerequisite is marked mastered in a database, the model can’t skip it however the conversation goes.
| Interface | 7 MCP tools — start, research, decompose, next step, drill, grade, review |
| Schema | 7 tables: topics, concepts, elements, element_edges, mastery, drills, sessions |
| Scheduling | SM-2 — per-element ease factor, repetition count, next-due date |
| Tests | 5 modules covering the scheduler, the mastery gate, decomposition, the server surface, the CLI |
The gate is enforced server-side, not prompted. The cheap version is a system prompt saying “don’t advance until they understand.” It works most of the time, which is the problem — the failure is invisible and arrives exactly when you’re tired and want to move on. Here the next-step tool returns nothing for a locked element. The model can be as agreeable as it likes; it has no unlocked content to hand over.
A prerequisite DAG instead of an ordered list. A linear syllabus is simpler and encodes a false claim — that one valid order exists. A DAG says what actually depends on what, so several elements can be legitimately available at once. It also makes the failure mode detectable: a cycle is a decomposition bug, where a bad linear ordering just silently teaches things confusingly.
SM-2 rather than a fixed review interval. A fixed schedule spends equal effort on what you’ve known for a month and what you got wrong yesterday. SM-2 is decades old, well understood, and needs three columns. I took a known algorithm because the interesting problem here is the gate, not the spacing curve.
test_scheduler.py pins the SM-2 interval math against known sequences. test_mastery.py covers the gate itself — a locked element stays locked, satisfying the last inbound edge unlocks it, a failed drill moves state backwards. Those are the two places where a bug produces a plausible-looking session that teaches the wrong thing in the wrong order.