Inspiration

Explaining something out loud is the fastest way to find out you don't actually understand it. While researching ideas for this hackathon I came across "teachable agents", a line of education research where students learn by teaching a virtual pupil. The best-known system is Betty's Brain from Vanderbilt: students teach Betty by building a causal diagram, she takes a quiz, and her wrong answers show the student what they haven't understood. I loved that mechanic and had never seen it in an app I could just open and use, with a topic of my choosing, where I could teach by explaining in my own words instead of drawing diagrams.

This app drives inspiration from Betty's Brain. GPT-5.6 can play a convincing confused student. It can hold a wrong belief, defend it the way a real kid would ("but heavier things hit harder, so shouldn't they fall faster?"), and only change its mind when your explanation earns it. It will also happily learn your mistakes, which is what makes grading it, indirectly yourself, possible.

What it does

You pick a topic and a level. There are six curated topics, or you can type your own. Milo, the AI pupil, shows up already holding three misconceptions about it. You teach him in chat. A sidebar tracks what he currently believes: got it, shaky, still thinks. If you teach him something wrong, it records that too, and doesn't warn you.

After at least three explanations, Milo takes a five question quiz. He only knows what you told him. Each answer comes with his reasoning, and he quotes you. Teach him well and he passes. Skip something and he guesses, and admits he's guessing because you never covered it. Teach him something wrong and he fails the question and blames you. This is from my own test run:

"The teacher told me, 'The space station is so high up that Earth's gravity can't reach it anymore,' so I think astronauts float because there is no gravity there."

Milo's score is your grade. The report quotes the sentences you explained well, quotes the exact sentence behind each miss, and sets up a re-teach where Milo reopens the chat asking about his weakest gap. The attempt history shows whether your second explanation beat your first.

Gravity is a good example of why explaining is harder than knowing. Everyone "knows" heavy things don't fall faster. But try teaching it and you have to say why. Gravity does pull harder on the heavier object; the heavier object is also harder to accelerate; the two cancel:

$$a = \frac{F}{m} = \frac{mg}{m} = g$$

If your explanation skips something, Milo's next question finds the hole.

How I built it

I wrote a product plan first, then had Codex on GPT-5.6 build the implementation from it: scaffolding, engine, UI, and the Docker and GitHub Actions pipeline, as separate Codex tasks working off the same plan.

The main design decision is that the chat is never the source of truth. If you just ask a model to roleplay a student over a long conversation, sooner or later it uses knowledge nobody taught it, or folds when the teacher says "trust me, you're wrong". So the state lives in a small typed belief ledger in SQLite, and four separate GPT-5.6 calls do four separate jobs:

  1. The lesson builder generates three misconceptions for any topic, a concept checklist, and a five question quiz where the wrong options are exactly what a still-confused Milo would pick.
  2. Milo streams short in-character replies over SSE. He sees his current beliefs plus a private misconception list he is told to act on but never quote as knowledge.
  3. The belief tracker reruns after every exchange and rebuilds the ledger. A misconception only becomes corrected when the explanation addresses the intuition behind it. Assertions without reasons leave it shaky. Wrong teaching gets stored as wrongly_taught along with the teacher quote that caused it.
  4. The quiz call answers from the ledger and transcript only, no outside knowledge allowed, and has to cite the stored quote whenever a wrongly taught belief costs a point.

Structured calls go through the OpenAI Responses API with Zod schemas (responses.parse with zodTextFormat). A response that fails validation is retried once with the validation error included in the retry. The whole engine is about 300 lines. Most of the behavior lives in the prompts and that one typed state.

The stack is React 18 with Vite and Tailwind on the frontend and Express with TypeScript on the backend, with SQLite for storage. In production it runs as one container where Express serves the built SPA, and GitHub Actions builds the image, pushes it to GHCR.

Challenges I ran into

  • Milo's quiz answers have to reflect the teaching, mistakes included, or the grade means nothing. No amount of prompt tweaking made that reliable on its own. Moving the state out of the chat did. The quiz call never sees "Milo the character", only stored beliefs and the transcript.
  • The tracker has to tell an explanation from an assertion. "You're just wrong" shouldn't fix a misconception, but a short correct explanation shouldn't be punished either. Finding that line took play-testing on all six seeded topics.
  • Milo must not tutor, must not fix the teacher's errors, and must not leak his misconception list, even when users poke at him.

What I learned

  • Getting a model to play dumb convincingly is harder than getting it to be smart. Milo pushing back with the intuition behind a misconception, instead of just being wrong, is what makes teaching him feel like teaching a person.
  • My own first gravity lesson scored 4/5 because I taught the astronaut part wrong. So the app works on its author, at least.

What's next

I want to add a voice mode so younger kids can teach Milo by talking, and a teacher view that shows which misconceptions a whole class shares. I'd also like to add the ability to upload pdf files and more so that users can try teaching about any topic that the model isn't already knowledgeable enough to evaluate.

Built With

Share this project:

Updates