Architecture overview
Read this first. Every other doc assumes it.
The one-sentence version
The Media Graph is the memory; vibe directing is the interface; the Model Router is the swappable engine underneath. Everything else is a surface over those three.
Layering
┌──────────────────────────────────────────┐
SURFACES │ Creator Studio · Film Studio · Theater · │
│ Publishing Hub · Admin console │
└───────────────────┬──────────────────────┘
│ server actions + API routes
┌───────────────────▼──────────────────────┐
SERVICES │ auth · credits · graph · character · │
(modular │ script · shots · generation · continuity ·│
monolith) │ director · timeline · moderation · │
│ publishing · theater │
└───────────────────┬──────────────────────┘
┌───────────────────▼──────────────────────┐
ORCHESTRATION │ Model Router — capability matching, │
│ failover, scoring, benchmarking │
└───────────────────┬──────────────────────┘
┌───────────────────▼──────────────────────┐
ADAPTERS │ mock (always live) · fal · seedance · │
│ kling · runway · veo · gemini · audio │
└───────────────────┬──────────────────────┘
┌───────────────────▼──────────────────────┐
PERSISTENCE │ Prisma · SQLite (dev) / Postgres (target) │
│ + object storage + the GraphEdge table │
└──────────────────────────────────────────┘
Dependencies point downward only. A service may call the Router; the Router must never import a service (it would create a cycle through generation). Adapters know nothing about projects, shots or credits — they receive a GenerationJob and return an AdapterResult.
The rules that keep this coherent
- No call site names a provider. Anything above the Router speaks in capabilities. The one deliberate exception is arena mode, which is explicitly a model comparison.
- Regeneration reads dependencies from the graph, never from the caller.
composeShotPrompt(shotId)takes an id and nothing else; it resolves characters, state, wardrobe, props, location, style and camera itself. - Credits are spent in exactly one place —
services/generation.ts. Reserve before, settle after, refund on failure. - Assets are born in exactly one place — the same file. That is what makes provenance airtight.
- Anything the system cannot verify, it reports as unverified. Never "passed" for a check that did not run. This is a correctness rule, not a style preference; see
docs/moderation.
Where to change things
| I want to… | Touch |
|---|---|
| Add a model | server/models/registry.ts + a config in server/router/adapters/providers.ts |
| Add a style | lib/styles/catalog.ts, then reseed. Styles are data. |
| Change how a shot's prompt is built | server/services/shots.ts → composeShotPrompt |
| Change what a cascade touches | server/services/graph.ts → resolveImpact / reindexShotEdges |
| Add a Director capability | DIRECTOR_TOOLS + a planOperation case in server/services/director.ts |
| Change moderation behaviour | server/services/moderation.ts |
| Move to Postgres | prisma/schema.prisma datasource, server/db.ts adapter, server/lib/json.ts, server/lib/vector.ts |
Deliberate deviations from the spec
Both are flagged in the README and are reversible.
- SQLite, not Postgres+pgvector. Zero-infrastructure startup. Schema is Postgres-compatible; three files isolate the difference.
- One Next.js app, not Next.js + FastAPI. Part 8.1 prescribes "modular monolith first". Service boundaries are real, so extraction is mechanical.
A third, forced by the environment: this machine is Windows on ARM64 and Prisma ships no native engine for it, so the client uses the WASM query compiler over a better-sqlite3 driver adapter.
Request lifecycle: rendering one shot
UI (ShotBoard)
→ server action generateShotAction
→ services/generation.generateShotVideo
→ services/shots.composeShotPrompt reads the graph
→ services/generation.enqueue dedupe, estimate, RESERVE credits
→ router.route capability match → decision + fallbacks
→ generation.processGeneration
→ router.executeWithFailover adapter chain, health marking
→ router.scoreGeneration six-dimension score
→ asset write + provenance + C2PA
→ shot.renderedAssetId updated
→ continuity.lintShot amber/red flags
→ credits.settle refund the unused reservation
→ recordBenchmark Production Intelligence
Every arrow is a function boundary you can test in isolation. scripts/verify-pipeline.ts asserts across all of them.