Stage

A tutor that draws while it talks.

Stage is a live whiteboard. You give it a document or a subject; it tells you the story first, draws the ideas as it explains them, and asks you to do something before moving on.

Moments from a real lesson, "why the quadratic formula works": the board on the right, the words being spoken on the left. Not rehearsed; the tutor wrote and drew all of it live.

The idea

A board, not a chat

Most AI tutors reply with paragraphs. Stage draws: equations that build up, graphs, diagrams, molecules, maps, code. It points at what it is talking about, and the whole lesson is a sequence of small, replayable steps.

Story first

A lesson starts with why the subject exists and where you have met it, then a map of what is coming, and only then the first idea, introduced from an everyday scene rather than a definition.

Prove it before moving on

Every objective ends with a check you answer on the board. The tutor does not move on because you said "ok"; it moves on because you showed you got it. Spaced review brings the idea back later.

Your documents are the spine

Upload a book or notes and Stage plans from them: what is taught and what is checked comes from your pages, with the page numbers. The wider story around it comes from general knowledge and is never used to grade you.

What it looks like

The quadratic formula board: x squared plus 6x drawn as a square with a corner missing, worked equations beside it, and a your-turn card
Geometry first: the equation is drawn as a square with a corner missing, then it's your turn.
A step-by-step derivation with one line highlighted as it is explained
A derivation, one highlighted line at a time as it is spoken.
The finished derivation with the quadratic formula shown large
The payoff: the formula, after you've seen where each piece comes from.

How it is built

A Spring Boot server holds the lesson and the tutor; a browser board draws what it is told. They talk over one WebSocket, in one small language of "beats".

The model explains. Code decides.

The model chooses how to explain and draw. Plain code chooses which objective comes next, asks the plan's own check, records the grade and completes the lesson. This split was added because cheaper models skipped checks.

A session is a list of beats

Each beat is a sentence plus drawing operations, stored with a sequence number. A board that reconnects, or opens an old lesson, rebuilds itself from the list.

One schema, two languages

The beat format is defined once in TypeScript. The server validates every beat against the same schema before the learner sees it, so a bad drawing is retried, not shown.

What talks to what

flowchart LR
  subgraph Browser["Learner's browser (apps/stage)"]
    Board["Board: queue, player, scene"]
    Panels["Panels: topics, plans, usage, history"]
  end
  subgraph App["robimoller.com (Spring Boot)"]
    WS["StageSocketHandler
/stage/ws"] API["StageApiController
/stage/api/*"] Core["Services: Hub, Tutor, LessonDriver,
Planner, Plans, Materials, Learners"] end DB[("Postgres
15 tables")] S3[("S3
PDFs, narration clips")] LLM["Claude or DeepSeek
tutor and planner"] Small["Jev, Haiku or Mercury
small judgements, section maps"] Voice["ElevenLabs
speech and word timings"] Emb["OpenAI
embeddings"] Pics["Iconify, Wikimedia Commons
pictures and images"] Board <-->|"stage:* events"| WS Panels -->|"HTTP"| API WS --> Core API --> Core Core --> DB Core --> S3 Core --> LLM Core --> Small Core --> Voice Core --> Emb Core --> Pics

One tutor turn

sequenceDiagram
  autonumber
  participant B as Board
  participant S as SocketHandler
  participant T as StageTutor
  participant D as LessonDriver
  participant R as Retrieval and Plans
  participant M as Claude or DeepSeek
  participant V as StageProtocol
  participant H as StageHub
  B->>S: stage:ask (text, askId, where it cut in)
  S-->>B: stage:turn received
  S->>T: ask(owner, text, askId, at, kind)
  T->>T: abort the previous turn, drop beats not yet heard
  T-->>B: stage:turn thinking
  T->>R: search the topic's passages, status note
  T->>D: classify(topic, kind, text)
  D-->>T: a Step (type and instruction) or nothing for a free turn
  T->>M: stream (system prompt, history, this turn's context)
  loop every tool call
    M-->>T: tool_use beat
    T->>V: normalise, then validate
    alt invalid
      T-->>M: tool_result error, fix and resend
    else valid
      T->>H: post(beat)
      H-->>B: stage:live (numbered entry)
      T-->>M: tool_result queued
    end
  end
  T->>T: contract check, corrective round if the step fell short
  T->>D: finishStep: ask the check, mark the step told
  D->>H: post the check card
  B-->>H: stage:ack (seq, board snapshot, layout)
    

The lesson driver: what comes next

flowchart TD
  S["Message or event
kind: lesson, resume, fresh, restart, or typed text"] --> R{"restart?"} R -->|"yes"| R1["clear the topic's position and what was told"] R1 --> Q R -->|"no"| Q{"A check is pending?"} Q -->|"yes, typed answer"| Q1["probe -> familiarity answer
warm-up -> grade the warm-up
check -> ANSWER step, model must call grade"] Q -->|"expired"| Q2["drop it, keep awaitingNext"] Q2 --> O Q -->|"no"| O{"Right after a lesson opening?"} O -->|"again"| O1["OPEN again, said differently"] O -->|"affirmative"| L O -->|"something else"| FREE["Free turn: the model answers, the lesson waits"] O -->|"not applicable"| P{"Partway up a ladder?"} P -->|"again"| P1["same ladder step again"] P -->|"affirmative"| P2["next ladder step"] P -->|"something else"| FREE P -->|"not applicable"| W{"A lesson event, or an affirmative
with something waiting?"} W -->|"no"| FREE W -->|"yes"| L{"orientedOn empty?"} L -->|"yes"| L1["ORIENT: story, map, familiarity card"] L -->|"no"| L2{"prologue never told
and the plan has one?"} L2 -->|"yes"| L3["PROLOGUE: the story, briefly"] L2 -->|"no"| L4{"reviews due?"} L4 -->|"yes"| L5["REVIEW: one quick check"] L4 -->|"no"| L6{"next objective's lesson
not yet opened?"} L6 -->|"yes"| L7["OPEN: scene, question, preview"] L6 -->|"no"| L8{"next objective exists?"} L8 -->|"yes"| L9["TEACH: ladder step 0 or the step to resume"] L8 -->|"no"| L10["COMPLETE: recap and offer the next lesson"]

Seen from the code, in object-oriented terms: a StageHub holds each learner's live session; a StageTutor runs one turn at a time against a model; a StageLessonDriver classifies what the learner just did and picks the next step; a StagePlanner turns documents into a plan; a StageProtocol validates every beat.

Want to try it?

Stage is by invitation while I tune it, because every lesson costs real tutor time. Ask and I will set you up with your own account: your topics, documents and progress stay yours.

Ask for access