A private, offline Python tutor that runs entirely on your own machine.
Lessons, an interactive code lab, and an AI mentor — powered by a local LLM (Gemma via Ollama). No accounts, no cloud, no telemetry. Open a browser, learn Python, write code, get feedback. Your code and your questions never leave the laptop.
Start here: https://stewalexander-com.github.io/python-tutor/ — the project's GitHub Pages start page with the three-command install and a 30-second visual tour. Source for the page lives in
site/.
┌─────────────────────────────────────────────────────────┐
│ Read a lesson → Run code in the lab → Ask tutor │
│ (all local, all offline) │
└─────────────────────────────────────────────────────────┘
| For… | It gives you |
|---|---|
| Self-learners | A guided Python curriculum with a chat tutor on demand. |
| Educators | A drop-in lab where students run code and get evidence-based hints. |
| Privacy-minded teams | A tutor that works on an air-gapped laptop — nothing phones home. |
| Tinkerers | A clean FastAPI + static-PWA stack to remix and extend. |
| Feature | What it does |
|---|---|
| 🧠 Local LLM tutor | Chat with a model running on your machine (default: gemma3:4b via Ollama). |
| 📓 Lesson library | A PWA you can install, with a Python-foundations curriculum. |
| 🧪 Inline code lab | Edit → Run → Evaluate. The tutor sees the real output, not a guess. |
| ✅ Graded exercises | Visible + hidden test cases, per-assertion pass/fail. |
| 📚 Official docs links | Answers cite docs.python.org and friends from a curated allowlist — no hallucinated URLs. |
| 🛡 Prototype-grade safety | Static AST scan, isolated subprocess, timeouts, rlimits, scrubbed env. |
| 🔌 Works offline | UI runs without the LLM; only chat/evaluate need Ollama up. |
The project's start page is published on GitHub Pages:
https://stewalexander-com.github.io/python-tutor/
It's the link to share with anyone who hasn't cloned the repo yet: dark / amber aesthetic, the local-first loop in four steps, the three-command install with copy buttons, and links to the repo, README, and issues.
Source lives in site/ and is deployed by
.github/workflows/pages.yml on every push
to main that touches site/. The Pages workflow runs independently of
the regular CI workflow.
To preview locally (pure static HTML + CSS, no build step):
cd site
python3 -m http.server 8080
# open http://localhost:8080/See site/README.md for what's in it and the asset layout.
A 30-second tour of the UI, lab, and tutor. Click any image to enlarge.
The screenshots above are produced by
scripts/capture-screenshots.jswith deterministic UI fixtures so they stay reproducible without Ollama running. Real model output will read differently — for the look of the UI, they're faithful. Seedocs/assets/screenshots/README.md.
flowchart LR
Student((You)) -->|reads, edits, asks| UI[PWA frontend]
UI -->|/api/run| Sandbox[Python sandbox<br/>subprocess + AST scan]
UI -->|/api/evaluate| Evidence[Evidence packet<br/>code + output + docs]
UI -->|/api/chat| Chat[Chat]
Evidence --> LLM[Local LLM<br/>Ollama / Gemma]
Chat --> LLM
LLM --> UI
Sandbox --> UI
The teaching loop:
flowchart LR
A[Read lesson] --> B[Edit code]
B --> C[Run]
C --> D{Worked?}
D -- yes --> E[Reflect / next lesson]
D -- no --> F[Evaluate]
F --> G[Hint-first feedback<br/>+ docs link]
G --> B
The LLM teaches, explains, and guides. The runtime verifies — the tutor never claims code works without running it.
Two commands. macOS, Linux, or Windows. Python 3.10+.
macOS / Linux
gh repo clone StewAlexander-com/python-tutor
cd python-tutor
./install.sh # sets up venv, then prompts y/N for any host-level step
./run.sh # serves UI + API at http://localhost:8001/Windows (PowerShell 5.1+ or PowerShell 7)
gh repo clone StewAlexander-com/python-tutor
cd python-tutor
.\install.ps1 # sets up venv, then prompts y/N for any host-level step
.\run.ps1 # serves UI + API at http://localhost:8001/If PowerShell blocks the script with an execution-policy error, run it once with:
powershell -ExecutionPolicy Bypass -File .\install.ps1(or set the per-user policy:Set-ExecutionPolicy -Scope CurrentUser RemoteSigned).
Open http://localhost:8001/ — you'll land on the lesson list with the code lab and floating "Ask tutor" panel.
install.sh/install.ps1only touches the repo on its own. Installing Ollama, starting the daemon, pulling the model, or launching the app are all opt-in y/N prompts. Press Enter and nothing changes on your host. On Windows the Ollama install path useswinget(App Installer) when you say yes; otherwise a manual https://ollama.com/download/windows link is shown.
Run ./install.sh --help or .\install.ps1 -Help (and the matching run
script) for every option. The most common shapes:
# macOS / Linux
./install.sh --yes # trusted host: install Ollama, pull model, launch
./install.sh --noninteractive # CI: never prompt, default everything to "no"
./install.sh --skip-ollama # set up Python only; skip every Ollama probe
./install.sh --model llama3.1:8b # use a different model than gemma3:4b
./run.sh --port 8042 # choose a different port
./run.sh --open-browser # open the URL once /api/health is green# Windows
.\install.ps1 -Yes # trusted host: install Ollama, pull model, launch
.\install.ps1 -NonInteractive # CI: never prompt, default everything to "no"
.\install.ps1 -SkipOllama # set up Python only; skip every Ollama probe
.\install.ps1 -Model llama3.1:8b # use a different model than gemma3:4b
.\run.ps1 -Port 8042 # choose a different port
.\run.ps1 -OpenBrowser # open the URL once /api/health is greenThe classic env vars (TUTOR_NONINTERACTIVE, PYTHON_TUTOR_ASSUME_YES,
TUTOR_SKIP_OLLAMA, TUTOR_MODEL, TUTOR_PORT, …) still work — the flags
are sugar on top of them.
Full env-var list and design rationale:
docs/install-runtime-workflow.md.
install.sh and run.sh are designed so the obvious failures fail
loudly with a concrete next step. The most common ones:
| Symptom | What to do |
|---|---|
| "Python 3.10+ is required and was not found" | brew install python@3.12 / apt install python3.12 / winget install -e --id Python.Python.3.12 and re-run. |
| Windows: "running scripts is disabled on this system" | One-shot: powershell -ExecutionPolicy Bypass -File .\install.ps1. Persistent (recommended): Set-ExecutionPolicy -Scope CurrentUser RemoteSigned. |
pip install fails on DNS / proxy / pypi |
The script detects this and prints offline/proxy/wheelhouse recipes. See install-audit.md. |
| "Port 8001 is already in use" | ./run.sh --port 8002 (probe uses /dev/tcp, no lsof needed). |
Ollama installed but daemon down on :11434 |
Answer y to "Start ollama serve now?" or run it yourself in another Terminal. |
gh repo clone fails with auth error |
gh auth status → gh auth login. Public clone via HTTPS also works. |
| Repo was moved after install -> "venv broken" | The script auto-rebuilds. Virtualenvs hard-code their own path; relocating is unsupported by Python itself. |
Detailed runbook and the audit that produced these mitigations:
docs/install-audit.md.
┌──────────────────────────┐ ┌──────────────────────────┐
│ frontend/ (static PWA) │◀────▶│ backend/ (FastAPI) │
│ lesson list • code lab │ │ /api/run /api/evaluate │
│ floating chat FAB │ │ /api/chat /api/exercises│
└──────────────────────────┘ └──────────┬───────────────┘
│
▼
┌──────────────────────────┐
│ Ollama (local LLM) │
│ default: gemma3:4b │
└──────────────────────────┘
| Layer | Where it lives | Read more |
|---|---|---|
| Frontend (PWA) | frontend/ |
frontend/README.md |
| Backend (FastAPI) | backend/ |
backend/README.md |
| Curriculum & exercises | curriculum/ |
curriculum/exercises/README.md |
| Sandbox & safety | backend/app/safety.py |
docs/safety-and-sandboxing.md |
| Architecture | — | docs/architecture.md |
| UX workflow | — | docs/ux-workflow.md |
The sandbox is prototype safety, not production isolation. It is stronger
than a bare subprocess.run, but a local single-user tutor is its design
target — not a multi-tenant code execution service.
| In force | Not in force |
|---|---|
Static AST scan (rejects subprocess, socket, ctypes, pickle, os.system, exec, eval, __import__, …) |
Kernel-level isolation |
Isolated python -I -B subprocess, scrubbed env |
Defense against side-channel attacks |
Per-call tempdir at 0o700, removed after run |
macOS RLIMIT_AS (Python ignores it) |
| Wall-clock timeout + process-group kill | Windows POSIX rlimits |
| POSIX rlimits: CPU, memory, file size, nproc | |
| Output truncation + code-size cap |
For multi-tenant or hostile workloads, wrap the runner in a container, a
microVM, or a restricted user. Details and threat model:
docs/safety-and-sandboxing.md.
The tutor only cites official Python docs from a curated allowlist —
docs.python.org, peps.python.org, packaging.python.org, plus the official
sites for NumPy, pandas, Matplotlib, SciPy, Flask, FastAPI, Django, Requests,
HTTPX, SQLAlchemy, pytest, and mypy. URLs are never generated by the LLM:
they come from an in-repo map (backend/app/docs_refs.py)
or exercise-supplied references. When online, each link is HEAD-checked before
display; unreachable links are dropped or flagged "unverified".
GitHub Actions runs on every push and pull request: backend tests, a static
safety scan over the curriculum, and a Markdown link sanity check. See
.github/workflows/ci.yml.
- Architecture
- Workflow
- UX workflow
- Safety & sandboxing
- Evaluation
- Roadmap
- Install & runtime workflow
- Install reliability audit
- Python foundations curriculum
- Tutor system prompt
- ADR 0001 — offline-first local LLM
The static PWA frontend was adapted from Python Power User (MIT).