Architecture
StudioBrain is a schema-driven digital asset management and narrative intelligence platform for game development. This page covers the system architecture from service topology through the entity system to the frontend layout engine.
Core Principle: Markdown is Source of Truth
Every entity in StudioBrain lives in a markdown file with YAML frontmatter. The database is a cache and index layer that is fully rebuildable from files. All writes flow through the markdown sync layer, which updates both the file on disk and the database record atomically.
User's Markdown Files (YAML frontmatter + content)
|
v
Backend (parse, validate, cache in DB)
|
+---> Database (SQLite local / PostgreSQL cloud)
|
+---> AI Service (RAG indexing, context building, generation)This means:
- Users can edit files directly in any text editor and changes sync into the app.
- The database can be deleted and rebuilt from files at any time.
- Git version control works naturally on the markdown files.
- No vendor lock-in: your content is always plain text.
Three-Service Stack
StudioBrain runs as three independent services that communicate over HTTP/WebSocket.
| Service | Technology | Default Port | Purpose |
|---|---|---|---|
| Frontend | Next.js 15, React 19, TypeScript, Tailwind 4 | 3100 | Web UI, client-side rendering, API orchestration |
| Backend | FastAPI, SQLAlchemy 2.0, Python 3.12 | 8201 | Entity CRUD, auth, file sync, billing, plugins |
| AI Service | FastAPI, PyTorch, multi-provider orchestration | 8202 | RAG, content generation, image generation, embeddings |
Request Flow
Entity operations (CRUD, sync, search) route through the backend:
Frontend --> Backend API (port 8201) --> Database + FilesystemAI operations (chat, image gen, analysis) route directly to the AI service:
Frontend --> AI Service (port 8202) --> External APIs (OpenAI, Anthropic, etc.)Complex AI generation loads context from the backend first:
Frontend --> Backend (load entity context, rules, templates)
--> AI Service (generate with context) --> External APIsAuthentication
- JWT with httpOnly cookies: The backend issues JWTs containing
tenant_id,user_id, androle. No PII is stored in the token. - API key auth: The AI service accepts API keys passed per-request. Users can bring their own keys (BYOK) or use admin fallback keys.
- Web auth routes:
/api/auth/web/login,/api/auth/web/session,/api/auth/web/select-tenanthandle cookie-based browser sessions.
Deployment Topology
Production Layout
StudioBrain runs across two compute hosts and four dedicated database LXCs:
Proxmox LXC 138 (10.15.0.137) -- App Services
+-- Docker: studiobrain-caddy (HTTPS termination, port 443)
+-- Docker: studiobrain-frontend (internal port 3100)
+-- Docker: studiobrain-backend (port 8201)
+-- NFS mount: /data/content --> NAS
BRAINZ (10.15.0.20) -- AI Service + GPU
+-- Docker: studiobrain-ai (port 8202, GPU access)
+-- NFS mount: /data/content --> NAS (same content)Database Services
Four dedicated LXCs enforce security zone separation. PII never co-locates with content.
| Service | LXC IP | Engine | Purpose |
|---|---|---|---|
| Auth DB | 10.15.0.138 | PostgreSQL 17.8 | Users, tenants, billing, OAuth tokens |
| Content DB | 10.15.0.139 | PostgreSQL 17.8 + RLS | Entities, assets, templates, plugins |
| Vector Store | 10.15.0.140 | Qdrant v1.13.6 | Embeddings, collection-per-tenant |
| Cache/PubSub | 10.15.0.141 | Redis 8.0.2 | Sessions, rate limits, presence, real-time events |
Security zones:
- Zone 1 (Restricted): Auth DB accepts connections only from the app backend. The AI service is blocked.
- Zone 2 (Standard): Content DB, Qdrant, and Redis accept connections from both the backend and the AI service (read-only for AI).
Desktop Mode
For local/offline use, StudioBrain runs as a Tauri 2.0 desktop app. The backend runs as a Python sidecar with SQLite for storage and ChromaDB for local vector search. No cloud services are required.
Multi-Tenancy
Every database table includes a tenant_id column. When running on PostgreSQL, Row-Level Security (RLS) policies enforce tenant isolation at the database level. On SQLite (desktop mode), tenant filtering happens in the application layer.
JWT tokens carry tenant_id, which the TenantIsolationMiddleware extracts and passes to every database query via scope_query().
Entity System
Unified Entity Model
All entity types share a single database model (EntityBase) with a flexible fields JSON column. The entity type determines which fields are present, as defined by the corresponding template.
class EntityBase(Base):
__tablename__ = "entities"
id = Column(Integer, primary_key=True)
entity_type = Column(String) # "character", "location", "brand", etc.
entity_id = Column(String) # snake_case identifier
name = Column(String)
status = Column(String)
fields = Column(JSON) # Template-specific fields
markdown_body = Column(Text) # Markdown content below frontmatter
tenant_id = Column(String)
created_at = Column(DateTime)
updated_at = Column(DateTime)Template-Driven Fields
Templates define the schema for each entity type. They live at _Templates/Standard/{TYPE}_TEMPLATE.md and contain YAML frontmatter declaring all fields with their types and defaults.
The backend’s TemplateSchemaService parses these templates to:
- Validate entity data on create/update
- Generate TypeScript interfaces and Zod schemas for the frontend
- Provide schema information to the AI service for structured outputs
Unified API Pattern
All entity types use the same API pattern:
GET /api/entity/{type} -- List entities of this type
GET /api/entity/{type}/{id} -- Get single entity
POST /api/entity/{type} -- Create entity
PUT /api/entity/{type}/{id} -- Update entity
DELETE /api/entity/{type}/{id} -- Delete entity (soft, to recycle bin)
GET /api/entity/{type}/schema -- Get template schema
POST /api/entity/{type}/smart-merge -- AI-assisted merge
POST /api/entity/{type}/import -- Import from markdown
POST /api/entity/{type}/compare -- Compare two versionsThe response shape is consistent across all entity types:
{
"total": 42,
"offset": 0,
"limit": 50,
"entities": [
{
"entity_type": "character",
"entity_id": "rex_marshall",
"name": "Rex Marshall",
"status": "active",
"fields": {
"age": "34",
"gender": "male",
"faction": "independent",
"personality_traits": ["resourceful", "paranoid", "loyal"]
},
"markdown_body": "# Rex Marshall\n\n## Background\n...",
"primary_asset": "/Characters/rex_marshall/images/portrait.png"
}
]
}Entity Types
StudioBrain ships with 16 entity types. New types can be added by creating a template file — no code changes required.
| Type | Template | File Prefix | Description |
|---|---|---|---|
| character | CHARACTER_TEMPLATE.md | CH_ | People, NPCs, players |
| location | LOCATION_TEMPLATE.md | LOC_ | Places, points of interest |
| district | DISTRICT_TEMPLATE.md | DST_ | City areas, zones |
| brand | BRAND_TEMPLATE.md | BR_ | In-world companies, products |
| faction | FACTION_TEMPLATE.md | FAC_ | Organizations, groups |
| item | ITEM_TEMPLATE.md | ITM_ | Game items, collectibles |
| job | JOB_TEMPLATE.md | JOB_ | Occupations, roles |
| quest | QUEST_TEMPLATE.md | QST_ | Missions, objectives |
| campaign | CAMPAIGN_TEMPLATE.md | CMP_ | Story arcs, campaigns |
| event | EVENT_TEMPLATE.md | EVT_ | World events, incidents |
| dialogue | DIALOGUE_TEMPLATE.md | DLG_ | Conversation trees |
| timeline | TIMELINE_TEMPLATE.md | TL_ | Timeline entries |
| assembly | ASSEMBLY_TEMPLATE.md | ASM_ | Modular entity assemblies |
| style_bible | STYLE_BIBLE_TEMPLATE.md | STY_ | Visual style guides |
| universe | UNIVERSE_TEMPLATE.md | UNI_ | World-building lore |
Automatic Type Generation
Running npm run generate:types (or starting npm run dev) reads all templates and generates:
src/types/generated/entities.ts— TypeScript interfaces for each entity typesrc/types/generated/schemas.ts— Zod validation schemassrc/types/generated/mappings.ts— Entity type constants and utilities
These are re-exported from src/types/index.ts for barrel-import usage:
import { Character, Location, District } from '@/types';
import { CharacterSchema } from '@/types';Plugin System
StudioBrain supports plugins that extend the UI with custom panels rendered as sandboxed iframes. Plugins communicate with the host via a typed postMessage protocol.
Key plugin capabilities:
- UI panels in entity sidebars, tabs, or footers
- Component blocks placeable in the Layout Designer (
plugin:{pluginId}:{panelId}) - Backend routes registered via the plugin loader at startup
- Event handlers subscribing to the backend event bus
- Per-plugin data storage via
/api/plugins/{plugin_id}/data/{record_type} - Settings (global and per-user) via
/api/plugins/{plugin_id}/settings
See Plugin Development and Plugin Iframe Protocol for full details.
Layout System
The Layout Designer is a page composer that drives entity edit and view pages from JSON layout definitions stored at _Templates/Layouts/{entity_type}.json.
Layout Structure
interface EntityLayout {
entity_type: string;
sections: LayoutSection[]; // Field groups and component blocks
tabs?: LayoutTab[]; // Configurable edit page tabs
view_config?: LayoutViewConfig; // View page settings
}Each section contains either form fields or a component block (mutually exclusive). Sections support column counts, collapse defaults, and custom titles.
Component Blocks
Nine built-in blocks are available in the Layout Designer palette:
| Block ID | Category | Description |
|---|---|---|
entity-assets | media | Asset browser with upload and primary image selection |
entity-timeline | display | Interactive timeline event panel |
production-status | workflow | Production pipeline status editor |
markdown-content | display | Rendered markdown body content |
primary-image | media | Hero image display with fallback |
entity-chat | interaction | AI chat panel for conversational editing |
entity-relationships | display | Auto-discovered relationship links |
entity-workflow-trigger | workflow | ComfyUI workflow trigger buttons |
static-content | display | Static text, headers, separators, info/warning boxes |
Plugin blocks use the ID format plugin:{pluginId}:{panelId} and render inside sandboxed iframes.
Layout-Driven Views
Entity view pages can opt into layout-driven rendering by setting view_config.layout_driven: true. The LayoutDrivenEntityView component reads the layout, renders sections in read-only mode, and falls back to the original hand-coded view page if no layout is configured.
Configurable Tabs
Edit page tabs are resolved from the layout JSON via the useLayoutTabs hook:
- If the layout defines
tabs, use those. - Otherwise, fall back to 6 defaults:
visual,yaml,markdown,preview,assets,generation. - Plugin-registered tabs are merged in.
Key Files
| Area | Path |
|---|---|
| Layout types | src/types/layout.ts |
| Block registry | src/lib/component-block-registry.ts |
| Plugin protocol | src/lib/plugin-message-protocol.ts |
| Tab resolution | src/hooks/useLayoutTabs.ts |
| Layout loading | src/hooks/useEntityLayout.ts |
| Block renderer | src/components/ComponentBlockRenderer.tsx |
| Dynamic editor | src/components/DynamicEntityEditor.tsx |
| View shell | src/components/ViewPageShell.tsx |
| Layout-driven view | src/components/LayoutDrivenEntityView.tsx |
| Designer UI | src/components/LayoutDesigner/ |
| Backend validation | backend/services/layout_service.py |
Theme System
StudioBrain uses a semantic surface system with 12 surfaces, each containing 7 coordinated CSS properties. Users can customize every value through the Settings UI, and all components — including plugins — must use semantic surface classes.
See Theme Customization for the full reference.
Product Tiers
| Tier | Storage | AI Credits | Access |
|---|---|---|---|
| Free | Local only | BYO API keys + 100 BrainBits/mo | Desktop |
| Indie ($29/mo) | Google Drive, 25GB cloud | 500 BrainBits/mo | Desktop + Web |
| Team ($45/user/mo) | + S3/Azure, 100GB cloud | 750/user pooled | Desktop + Web + Mobile, SSO |
| Enterprise | Unlimited | Unlimited | All + Dedicated instance |
Feature gating is enforced by the FeatureGateMiddleware on the backend and useFeatureGate hooks on the frontend. The billing system uses Stripe with webhook-driven subscription lifecycle management.