The Learning Tree is a local-first educational application built around a full-screen 3D tree. Learners progress by selecting leaves that represent topics, receiving AI-generated lessons, and growing mastery over time. Everything runs locally: profiles are stored in SQLite, lessons are generated through a local Ollama model, and speech playback uses the browser Web Speech API.
This repository currently delivers a working MVP for profile-based learning, streamed lesson generation, local persistence, and a child-first immersive interface. It is not yet a finished infinite-canopy curriculum platform.
- Full-screen React Three Fiber tree interface with touch-friendly leaf targets
- Local learner profiles with selectable learner levels before entering the tree
- Profile-specific tree mastery colors and recent lesson history
- Streaming lesson generation over Server-Sent Events from a single local Ollama model
- Immediate lesson rendering as text arrives in the frontend
- Local lesson persistence and challenge-based mastery progression per leaf
- Warmup mini-games while waiting for the first lesson token
- Web Speech API support for leaf titles, lesson reading, and game prompts
- Dynamic branch growth with locally generated new leaves
- Local AI health check against the configured Ollama server
Implemented:
- Local profiles
- Seeded tree data in SQLite
- 3D tree renderer
- Streamed lesson generation
- Saved lesson history
- Warmup mini-games
- AI health checks
- Dynamic branch growth
- Lesson modal and read-aloud support
Still open:
- True infinite canopy with streamed world chunks
- Full Alembic-style migration tooling
- Broader frontend and backend test coverage
- End-user packaging beyond the local setup/run scripts
- Advanced citation-grounded academic workflows
- Curriculum modes beyond the current child-first MVP
- React 18
- TypeScript
- Vite
- React Three Fiber
- Drei
- React Spring
- Three.js
Responsibilities:
- Render the immersive 3D tree
- Manage profile selection, learner level selection, and local lesson UI state
- Fetch tree and lesson history data
- Start lesson streams and render tokens in real time
- Run warmup games while waiting for the first token
- Speak leaf titles and lesson text with the browser speech engine
- FastAPI
- SQLAlchemy
- SQLite
- Local Ollama HTTP API
Responsibilities:
- Bootstrap the local database
- Serve profile, tree, lesson-history, and health endpoints
- Resolve learner context for lesson generation
- Stream lesson text from a single Ollama model over SSE
- Persist generated lessons and update mastery progress after lesson challenges
- Generate additional leaves for branches
The MVP no longer uses a multi-agent orchestration layer.
Lesson generation now works as a single-model streaming pipeline:
- The frontend sends
profile_idandleaf_id. - The backend resolves the learner profile, grade, subject, topic, and recent related progress from SQLite.
- The backend builds one tutoring prompt for Ollama.
- Ollama streams lesson markdown back through
text/event-stream. - The frontend appends lesson text live as it arrives.
- When the stream completes, the backend saves the lesson.
- When the learner passes the lesson challenge, the backend marks completion and increments mastery for that leaf.
This architecture is designed to reduce latency and remove the timeout problems caused by the old multi-agent path.
learning_tree/
|-- apps/
| |-- api/
| | |-- app/
| | | |-- core/
| | | | `-- config.py
| | | |-- db/
| | | | |-- base.py
| | | | |-- bootstrap.py
| | | | |-- models.py
| | | | `-- session.py
| | | |-- routes/
| | | | `-- tree.py
| | | |-- schemas/
| | | | `-- tree.py
| | | |-- services/
| | | | |-- ai_health.py
| | | | |-- lesson_engine.py
| | | | `-- tree_layout.py
| | | `-- main.py
| | |-- requirements.txt
| | `-- sql/
| | `-- schema.sql
| `-- web/
| |-- public/
| |-- src/
| | |-- features/
| | | |-- layout/
| | | |-- lessons/
| | | |-- profiles/
| | | |-- rewards/
| | | |-- tree/
| | | `-- warmup/
| | |-- lib/
| | |-- styles/
| | |-- App.tsx
| | `-- main.tsx
| |-- package.json
| |-- tsconfig.json
| `-- vite.config.ts
|-- data/
| `-- learning_tree.db
|-- PHASE3_CHECKLIST.md
`-- README.md
The app persists everything locally in data/learning_tree.db.
Primary tables:
profiles: learner profiles stored on the local machinegrade_levels: trunk segments and grade orderingsubject_branches: branches for each grade/subject combinationleaves: persisted topic nodes, coordinates, and lesson seed promptslessons: saved generated lessons per profile and leafprofile_leaf_progress: mastery state, completed count, and last-open metadata per profile and leaf
Key relationships:
- One
GradeLevelhas manySubjectBranchrows - One
SubjectBranchhas manyLeafrows - One
Profilehas manyLessonrows - One
Profilehas manyProfileLeafProgressrows
On first backend startup, the database is created and seeded automatically.
Docker path:
- Docker Desktop or Docker Engine with Docker Compose v2
- Enough disk space for the Ollama model volume
Local path:
- Python 3.11+
- Node.js 20.19+ or 22.12+
- npm 10+
- Ollama installed locally
- At least one Ollama model pulled locally
Default model settings:
- Fast model:
llama3.2:3b - Advanced model:
qwen3:30b
The current lesson path prefers fast first-token response for the MVP.
From the repository root:
docker compose up --buildThe Docker stack starts:
- FastAPI backend at
http://127.0.0.1:8000 - Vite frontend at
http://127.0.0.1:5173 - Ollama at
http://127.0.0.1:11434
The first startup downloads the default llama3.2:3b model into a persistent Docker volume named learning_tree_ollama-data. Later startups reuse that model volume.
Docker keeps SQLite data on the host in data/learning_tree.db and mounts the repository into the API and web containers for local development.
Stop the stack with:
docker compose downRemove the downloaded Ollama model volume only when you want a full Docker reset:
docker compose down -vFrom the repository root:
.\scripts\setup.ps1Start Ollama separately, then run both local app servers:
.\scripts\run-dev.ps1The script opens separate PowerShell windows for the API and web app.
Pull the default fast model once:
ollama pull llama3.2:3bOptional larger model for advanced content:
ollama pull qwen3:30bStart Ollama:
ollama serveFrom the repository root:
py -3 -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r apps/api/requirements.txt
uvicorn app.main:app --reload --app-dir apps/apiThe API runs at http://127.0.0.1:8000.
In a separate terminal:
cd apps/web
npm install
npm run devThe frontend runs at http://127.0.0.1:5173.
Typical learner flow:
- Choose or create a child profile.
- Enter the 3D tree.
- Select a leaf.
- Start the lesson.
- Wait briefly for the first streamed token.
- Read or listen to the lesson as it appears live.
- Complete the lesson challenge to build mastery.
- Revisit the leaf later for more practice.
The backend reads .env values with the LEARNING_TREE_ prefix.
Example:
LEARNING_TREE_APP_NAME=The Learning Tree API
LEARNING_TREE_DATABASE_URL=sqlite:///data/learning_tree.db
LEARNING_TREE_OLLAMA_BASE_URL=http://127.0.0.1:11434
LEARNING_TREE_OLLAMA_FAST_MODEL=llama3.2:3b
LEARNING_TREE_OLLAMA_ADVANCED_MODEL=qwen3:30b
LEARNING_TREE_LESSON_TEMPERATURE=0.2
LEARNING_TREE_OLLAMA_TIMEOUT_SECONDS=180Supported settings:
| Variable | Default | Purpose |
|---|---|---|
LEARNING_TREE_APP_NAME |
The Learning Tree API |
FastAPI application title |
LEARNING_TREE_DATABASE_URL |
sqlite:///data/learning_tree.db |
SQLite connection string |
LEARNING_TREE_OLLAMA_BASE_URL |
http://127.0.0.1:11434 |
Local Ollama server URL |
LEARNING_TREE_OLLAMA_FAST_MODEL |
llama3.2:3b |
Default low-latency lesson model |
LEARNING_TREE_OLLAMA_ADVANCED_MODEL |
qwen3:30b |
Optional larger model for advanced content |
LEARNING_TREE_LESSON_TEMPERATURE |
0.2 |
Lesson-generation temperature |
LEARNING_TREE_OLLAMA_TIMEOUT_SECONDS |
180 |
Ollama request timeout in seconds |
All API routes are mounted under /api.
GET /api/healthGET /api/health/ai
GET /api/profilesPOST /api/profiles
Example profile creation payload:
{
"display_name": "Explorer",
"avatar_seed": "fox",
"age_band": "early-reader"
}GET /api/tree?profile_id=1
Returns seeded grade, branch, and leaf data with profile-specific mastery merged into each leaf.
GET /api/profiles/{profile_id}/lessons
Returns recent saved lessons for that profile, including completion state and fallback-recovery metadata when a local fallback lesson was saved.
POST /api/generate-lesson/stream
Request payload:
{
"profile_id": 1,
"leaf_id": 42
}Response type:
text/event-stream
Current SSE events:
starttokenreplacecompleteerror
If the live Ollama stream fails, the backend emits a replace event with a local fallback lesson and stores recovered, recovery_detail, and stream_model metadata with the saved lesson.
The backend streams lesson text as it is generated, then sends a final completion payload after the lesson is saved locally.
POST /api/branches/generate-leaves
Example payload:
{
"grade": "Grade 3",
"subject": "Science",
"count": 3
}The tree is rendered in React Three Fiber using placeholder geometry:
- Cylinders for the trunk and branches
- Planes for leaves
- Large invisible hit targets for child-friendly tapping
- Overlay UI rendered above the canvas
When a lesson starts:
- the lesson modal opens immediately
- a subtle waiting state shows until the first token arrives
- the warmup game appears only while the app is waiting for the first streamed token
- streamed lesson text is appended live into the reader
- the warmup game closes as soon as the first token is received
- recovered fallback lessons show a persistent notice in the lesson reader
The browser Web Speech API is used for:
- leaf title announcement
- read-aloud support for lesson text
- mini-game prompts
Speech support depends on the browser and may require a user interaction before playback is allowed.
- No cloud login is required
- Profiles and lessons stay local in SQLite
- The app is designed for a local Ollama server
- The current MVP does not rely on hosted AI APIs
Limits:
- AI-generated lessons still require adult judgment
- Advanced academic content is not yet citation-grounded
- The app is not yet appropriate for high-stakes or regulated learning environments
Recent verification completed in this repository:
- backend Python syntax compilation
- backend normalization, API, migration, and SQLite foreign-key regression tests
- frontend TypeScript checks
- frontend SSE, profile, warmup, and lesson reader component tests
- frontend production build with Vite
The frontend currently builds successfully. The 3D tree and reward games are code-split, but the Three/R3F chunks remain the largest frontend assets.
- The canopy is still finite rather than truly infinite
- Seeded curriculum is broad but still shallow at each grade/subject branch
- Migration tooling is a small in-app runner, not a full Alembic workflow
- Test coverage is improved but still not exhaustive
- Advanced higher-ed and research workflows are not implemented
- Lesson quality still depends heavily on the locally installed Ollama model and machine performance
Check:
- Ollama is running
- the configured model is installed locally
GET /api/health/aireports a healthy response- the machine has enough memory for the configured model
For better first-token latency, prefer the fast model in .env.
Check:
- the browser supports
speechSynthesis - the page has received a user interaction
- the device is not muted
Stop the API and delete:
data/learning_tree.db
Restart the API to recreate and reseed the local database.
Backend:
apps/api/app/main.pyapps/api/app/routes/tree.pyapps/api/app/services/lesson_engine.pyapps/api/app/services/ai_health.pyapps/api/app/db/models.pyapps/api/app/db/bootstrap.py
Frontend:
apps/web/src/App.tsxapps/web/src/lib/api.tsapps/web/src/features/tree/components/LearningTreeCanvas.tsxapps/web/src/features/tree/hooks/useTreeData.tsapps/web/src/features/tree/hooks/useLeafSpeech.tsapps/web/src/features/profiles/hooks/useProfiles.tsapps/web/src/features/lessons/hooks/useLessonHistory.tsapps/web/src/features/lessons/components/LessonReader.tsxapps/web/src/features/rewards/components/RewardGameModal.tsxapps/web/src/features/warmup/components/WarmupGameModal.tsx
The Learning Tree now uses a simpler and faster lesson architecture: one local model, one streaming request, one persisted lesson result. The remaining work is product hardening and curriculum expansion, not basic wiring.