Architecture diagrams

after phases 1–4

A visual reading of the approved decisions. Solid shapes are in scope for the MVP. Teal marks every place an LLM, embedding or speech model is called. Dashed shapes are agreed but deferred.

Decisions this picture is built on

Phase 1 · LMS core
  • ·Organization-first tenancy; a solo teacher is an org of one
  • ·Global student identity, org membership by invite, CSV, later SSO
  • ·Per-course progression rules, certificates with optional expiry
  • ·Roles: super-admin, org admin, educator, student
Phase 2 · Builder & testing
  • ·Block editor with AI co-pilot panel; AI output is always editable blocks
  • ·Auto-graded core set, LLM-graded open answers, 3D practical scoring
  • ·Grounded in uploads by default, per-request toggle
  • ·Widget and Scene DSL first-class; sandboxed iframe embeds second-class
Phase 3 · 3D / VR
  • ·Three.js via React Three Fiber, JSON Scene DSL, no generated code
  • ·2D screens first; WebXR on Quest enabled and smoke-tested
  • ·Guided interaction, light physics
  • ·Subject-agnostic asset search behind an automated quality gate
  • ·QA: play-test, inspector, chat edits, versions, pilot cohort
Phase 4 · RAG & tutor
  • ·Text chat with citations at MVP; voice and 3D avatar later
  • ·Direct or Socratic per course; exam lockdown
  • ·Unknowns: say so, labeled general answer, escalate, pin to FAQ
  • ·Multilingual from day one
  • ·Cloud APIs by default, self-hosted models for some tenants

1. System layers

Every client talks to one edge that resolves the tenant and sets the database session. Services below it never see a request without a tenant. AI calls from any service go through one router, which is what makes per-tenant self-hosting possible later without touching the services.

Clients Edge Services Data AI providers Web app (Next.js / React) Educator Studio · Student Player · Org Admin 3D / XR runtime React Three Fiber + WebXR · Scene DSL interpreter Sandboxed widgets iframe + postMessage scoring (PhET, H5P…) HTTPS · SSE streams API / BFF global identity auth · tenant resolution · Postgres RLS session · rate limits · token metering Courses &blocks Assessment &gradebook Ingestionworkers Tutor(RAG) Generation(vibe-coding) Assetsearch LLM · embed · STT Postgres + pgvector RLS per tenant · FTS · HNSW Object storage + CDN uploads · normalized GLB · thumbnails Provider router (per-tenant policy) default VPC / residency tenants Queue + cache (Redis) ingestion jobs · FAQ semantic cache Video streaming Bunny / Cloudflare Stream Cloud adapters Anthropic Opus · Sonnet · Haiku Self-hosted adapters vLLM open-weight · BGE-M3 · WhisperX
All six services read and write Postgres under row-level security; only the arrows that carry the argument are drawn. Ingestion, Tutor, Generation and Asset Search are the four services that spend tokens, and all four reach models only through the router.
MVP componentcalls an AI modelagreed, deferred

2. Material ingestion and the tutor loop

Index time runs once per upload and is cheap. Query time runs per student question and is where the recurring cost lives, so the FAQ cache sits in front of retrieval and the course digest stays in the cached prompt prefix.

Index time · once per upload Query time · per student question UploadPDF · PPTX · DOCX · video ConvertDocling · WhisperX ContentBlockspage · slide · timestamp Chunkheading-aware · 400–800 tok Context lineHaiku · Batch API −50% EmbedBGE-M3 (multilingual) pgvector + FTSparent/child · course_id Course digest5–10k tok · prompt-cached always in system prompt Questionany language Mode gateexam lockdown · Direct/Socratic FAQ cachesemantic · pinned answers Hybrid searchvector + BM25 → RRF Rerankbge-reranker · top-k Tutor LLMSonnet · search tool ≤N Cited answerdeep-link page / ts miss hit → answer directly not in materials Say so · labeled general answer if enabled student may escalate to educator inbox Educator answers once, pins visible to the whole cohort
Hybrid search reads the pgvector and full-text indexes built above. A cache hit skips retrieval and the model entirely. Every pinned educator answer becomes a future hit, so the course gets cheaper and more accurate as it is used.

3. Vibe-coding pipeline and the asset quality gate

The model never writes engine code. It writes a validated JSON document, and every later edit is a patch to that document. The same pipeline produces quizzes, 2D widgets and 3D scenes, only the schema changes.

Educatorprompt or block edit Planner LLMobjectives · objects · criteria Asset resolvercatalog → primitives Compiler LLMOpus · strict JSON DSL Validatorschema · floor · overlaps PreviewR3F / widget renderer Published vNstudents pinned · rollback approve invalid → repair, max 2 passes Educator QA play-test · inspector · chat edits · pilot cohort link JSON Patch cached DSL docs ≈15–20k tok Asset quality gate · subject-agnostic, automated Open sourcesSketchfab CC · Poly Haven · Kenney · PhET · H5P License filterCC0 · BY · BY-SA, attribution Normalize GLBmeters · pivot · Draco · KTX2 · LOD Vision scorefit vs query · quality Catalogpgvector text + CLIP · approved cache Text-to-3D fallbackdeferred · slow and costly top-N candidates, educator picks in QA Educator GLBown upload
Every approved asset is cached in normalized form, so the catalog grows into a curated library without anyone curating it. The Compiler only ever sees asset IDs and a bounding box, never geometry.

4. Content and learning records

One shared primitive: a Block is a JSON document whatever its kind. Versions are immutable once published, attempts are append-only, and both decisions are what let audit-grade reporting be added later without a migration.

Organizationtenant · settings · seats later Courseprogression rule · tutor mode Modulegate: pass to unlock Lessonordered blocks Userglobal identity Enrollmentstatus · due date · progress Attemptappend-only · score · events Blocktext · video · quiz · widget · scene · embed member, role in on ordered Conversationper student · per course Messagetokens · model · latency Certificateissued · expires · recert BlockVersiondraft → published vN · immutable pass → issue publish Plan · Seat · OrderPhase 5 · schema now, gateway later Citationchunk · page / timestamp ContentBlockchunks · embeddings · digest seats · plans · orders bind to Organization and Course cites ingested from usage_events (tenant, user, task, model, tokens in/out, cost) written by every AI call from day one · quotas now, invoices later
Attempt records the pinned BlockVersion it was taken against, so a scene or quiz can be edited for the next cohort without rewriting anyone's history. The dashed billing entities are modeled in Phase 5 and ship without a payment gateway.

5. Which model does what

Routing by task is the main cost control. The router applies the same table per tenant, swapping the cloud column for the self-hosted one where a tenant's policy requires it.

TaskCloud defaultSelf-hosted equivalentPer-unit estimateLever
Chunk context lines, query rewrite, grading rubric checksclaude-haiku-5-5small open-weight via vLLMcents per courseBatch API, prompt caching
Tutor answersclaude-sonnet-5-5mid open-weight via vLLM≈ $0.01 / questioncached digest, FAQ cache, top-k ≤ 6
Scene, widget and quiz compilationclaude-opus-5-5large open-weight via vLLM≈ $0.10 full, $0.02 patchJSON DSL, cached schema docs, patches
EmbeddingsBGE-M3 (hosted or API)BGE-M3cents per coursesame model both sides, no re-index
TranscriptionWhisperX (hosted)WhisperX≈ $0 self-hosted, $0.006 / min APIrun once, store timestamps
Rerankbge-reranker-v2-m3bge-reranker-v2-m3negligibleCPU-capable
Asset fit scoringclaude-haiku-5-5 (vision)open VLMcents per searchcache scores per asset

Estimates use Anthropic list prices as of October 2026 and are order-of-magnitude. student questions dominate recurring spend