A self-hosted training platform. One engine renders and grades many topics; a topic is a content pack on disk, not a code change.
Grading is deterministic — no model is called while a learner works. The only
function that talks to a model is review(), for handwritten and typed
deliverables, and only on an explicit learner action.
Windows / PowerShell
cd runtime
docker compose up -d --build
./first-run.ps1macOS / Linux / Git Bash
cd runtime
docker compose up -d --build
./first-run.shThen open http://localhost:8000. That is the whole setup.
No API key is needed. Model review (for handwritten and typed deliverables) is
the only feature that wants one; everything else grades deterministically. To
enable it later, copy .env.example to .env and put a key in it.
Content packs ship drafted, and the record of what has been validated lives in
a Docker volume rather than in git. A fresh clone starts with an empty one, so
the app boots perfectly and every course shows 0 tasks. That looks like a
broken install and is not.
first-run validates and publishes every topic it finds on disk. It genuinely
executes each task's grader — the reference solution is run, the known-bad
solutions are run and must fail — so allow several minutes per topic. Nothing
reaches a learner unchecked.
If PowerShell blocks the script:
powershell -ExecutionPolicy Bypass -File ./first-run.ps1An edit changes a task's fingerprint, which un-publishes it until it is re-validated. Re-publish just the topic you touched:
./refresh-content.sh applied-cryptographyOr re-run first-run to do all of them.
Compose names a project after its directory, and this one is always runtime.
Two clones therefore share container names, image names and the workspace
volume — the second up takes over the first's containers. Give a second copy
its own project name, and change the host port in docker-compose.yml:
docker compose -p mytest up -d --buildruntime/ the engine — FastAPI + React, topic-agnostic
backend/ grader, validator, persistence, answer-key seal
frontend/ React + Vite + TS + Tailwind, Monaco
verify-all.sh every gate in one command
refresh-content.sh rebuild → restart → validate → publish
*-trainer-pack/ content packs; one directory per topic
AUTHOR-A-COURSE.prompt.md how to write your own course
Read AUTHOR-A-COURSE.prompt.md. It is written to be handed to a coding
agent with this repo open, and it encodes the rules the existing packs learned
by breaking first.
The short version:
- A task declares a renderer (how it is worked) and a check (how it is graded). Thirteen renderers exist; none of them are topic-specific.
- Every module runs expose → practice → reinforce. The content gate fails a module missing a beat.
- Ground truth is sealed — expected answers, quiz keys and explanations never reach the browser until a task is retired.
- A saved file is not a live task. Editing content changes its fingerprint,
which revokes publication until the validator clears it again. Run
./refresh-content.sh <topic>.
cd runtime
./verify-all.sh # engine suites, content gates, frontend build
./verify-all.sh --browser # also drives a real browser
./verify-all.sh --full # also re-runs the whole validator (slow)The browser suites exist because the API being correct does not mean the app renders. Both of the render bugs found so far passed every server-side check.
Copy runtime/.env.example to runtime/.env. A model API key is only needed for
writeup and canvas review; everything else grades without one. .env is
git-ignored and has never been committed.