System Architecture · Dev reference

How Nova AI is actually built

A thin AI layer over the tools we already use, structured around three abstractions — model router, Git provider, and plugin registry — so the platform is portable, swappable, and extensible from day one.

v2 · April 2026
For Tuesday dev session
Source: nova-ai-architecture.md
01

Runtime topology

All on GCP · region europe-west1
Inbound · clients & events
HTTPS · WSS · webhooks
Browser
Next.js app, real-time inbox via Firestore listener
PWA · responsive
Slack
Daily digest, release alerts, MR reminders
webhook out
Email (releases@)
Release notes, partner platform notifications
SMTP via Workspace
GitLab webhooks
Push, MR, pipeline, comment events
9 event types
Apps Script
Spark / GLI cert email ingest into compliance@
5-min trigger
Cloud Load Balancer · Cloud CDN · Cloud Armor
Application services · Cloud Run (scale to zero)
europe-west1
web
Next.js + TS, Tailwind, shadcn/ui, Tiptap (free core)
SSR · React Server Components
api
Fastify REST + GraphQL, auth, plugin routes mounted here
Node 20 · OpenAPI
webhook-listener
Validates GitLab/Drive signatures, classifies, enqueues
public ingress · Fastify
worker
Pulls jobs from Cloud Tasks · runs AI exec · updates Firestore
2-tier sandbox in P5
All AI / Git / plugin calls flow through abstractions below
★ Architectural innovation
Abstraction layer · the three indirections
LLM Router · llm.ts
Roles → providers. Feature code never imports a provider SDK. Swap models by config.
complete() · classify() · embed()
GitProvider · git.ts
Single interface — branches, MRs, comments, webhooks. GitLab today; GitHub/Bitbucket later.
openMR() · commitFiles() · …
Plugin Registry · plugins.ts
Loads enabled plugins, mounts API routes, registers UI slots, hooks lifecycle events.
register() · slots[] · hooks[]
Modules — split between core platform and pluggable extensions
Core platform modules · ship to anyone
Issues & cycles
Issue CRUD, cycles, planning threads
AI execution engine
Plan → exec → MR → revision loop
Notifications
Inbox, daily digest, MR reminders
Docs hub + Q&A
Drive sync, embeddings, Gemini Q&A
To-dos & snippets
Personal task mgmt, code snippets
Auth & admin
Google OAuth, workspaces, RBAC
Audit log
Append-only AI & user action trail
Reviewer pool
Workspace-level MR reviewer assignment
Critical Path
Cross-project end-game board · plugins contribute lanes
iGaming plugins · BBG-specific bundle
Certification
Spark/GLI/US-state pipeline + email ingest
Cheats DB
Per-game cheats, vendor exports
Translations
Localazy replacement, vendor exports
Game Data Hub
Unified game-data UI shell
Engine releases
Framework version broadcast
Cert lanes
Contributes Spark/GLI stages to Critical Path
Async & data — managed services only
Async
at-least-once · retry-on-fail
Cloud Tasks
AI exec jobs, webhook fanout, notification dispatch
3 queues
Cloud Scheduler
Daily digest cron, stale doc sweep, MR reminder ticks
cron
Data — metadata only, no content
multi-region
Firestore
jobs · notifications · audit · prefs · plugin_*
native mode
Memorystore
Sessions, repo cache, rate limits
Redis 7
Vector Search
Doc embeddings, semantic search
text-embedding-005
External services — sources of truth, never duplicated
External services · sources of truth
via abstractions where applicable
GitLab
Code, MRs, issues — via GitProvider
REST + GraphQL
Google Drive
Specs, briefs — read via service account
v3 API
Miro
Design boards — read-only as exec context
REST · OAuth
Anthropic
Claude Sonnet 4.6 + Haiku — via LLM Router
Agent SDK
Vertex AI
Gemini 2.5 Flash + embeddings — via LLM Router
GCP-native
Workspace SMTP
releases@bangbang.games — Nodemailer relay
no SendGrid
Cross-cutting
🔐
Secret Manager · all tokens, never in code
🛠
Cloud Build · CI/CD on MR-merge webhook
📋
Cloud Logging + Audit · structured logs
👤
IAM · per-service service accounts, least privilege
📦
Artifact Registry · container images
02

The three abstractions, in detail

Where portability and extensibility live
◆ Abstraction · LLM Router

Models are config, not architecture

Every AI call routes through llm.ts. Feature code asks for a role; the router picks the provider and model. Admins configure mappings in the admin UI — no redeploy needed.

// llm.ts — single entry point type ModelRole = | 'code_execution' | 'classification' | 'doc_qa' | 'embedding'; interface LLMConfig { provider: 'anthropic' | 'vertex' | 'openai' | 'bedrock'; model: string; supportsPromptCaching?: boolean; supportsBatch?: boolean; } // Loaded from Secret Manager const MODEL_MAP: Record<ModelRole, LLMConfig> = loadModelConfig(); export async function complete( role: ModelRole, messages: Message[] ): Promise<LLMResponse>;
  • Provider SDKs imported only inside adapters
  • Token budget guard sits in router — applies to every provider
  • Cost tracking normalised across providers in audit log
  • Today: Sonnet 4.6 (exec) · Haiku (classify) · Gemini Flash (Q&A)
◆ Abstraction · GitProvider

Git platform is a strategy, not a hard dependency

The execution engine, webhook listener, and reviewer pool talk to a GitProvider interface. Today there's a GitLab adapter; GitHub or Bitbucket adapters can be slotted in without touching feature code.

interface GitProvider { // Repository ops listFiles(repo: RepoRef): Promise<FileList>; readFile(ref: FileRef): Promise<string>; createBranch(repo, name, from): Promise<Branch>; commitFiles(branch, files, msg): Promise<Commit>; // Merge requests / pull requests openMR(branch, title, body): Promise<MR>; postMRComment(mr, body): Promise<void>; readMRDiff(mr): Promise<Diff>; // Webhook validation verifyWebhook(req): WebhookEvent | null; // Identity resolveUser(handle): User; } // Adapter today class GitLabProvider implements GitProvider
  • Default branch never written directly — feature branches only
  • MR approval semantics abstracted (GitLab/GitHub differ)
  • Webhook normalisation: 9 GitLab types → unified WebhookEvent
  • Lives in packages/git-provider
⊕ Abstraction · Plugin Registry

iGaming features are plugins, not first-class citizens

Certification, cheats, translations, game data hub — all valuable to BBG, all noise to anyone else. They live in a separate package and are loaded by the registry at boot. Each plugin declares routes, UI slots, hooks, and a nova.yml schema extension.

interface NovaPlugin { id: string; version: string; // Backend extension points apiRoutes?: FastifyPluginCallback; hooks?: { onIssueCreated?: Hook; onMRMerged?: Hook; onWebhook?: Hook; onConfigLoad?: Hook; }; // UI extension points uiSlots?: { projectTab?: SlotComponent; criticalPathLane?: SlotComponent; novaYmlSection?: SlotComponent; dashboardWidget?: SlotComponent; }; firestoreNs: string; configSchema?: JSONSchema; }
core: issues core: cycles core: planning core: notifications core: ai-exec core: critical-path plugin: certification plugin: cheats plugin: translations plugin: game-data-hub plugin: engine-releases
03

The non-negotiables

Read these before starting any work
Approval is structural
AI never has a merge_mr tool. Safety is the absence of a permission, not a prompt rule.
No content in Firestore
Code → GitLab. Docs → Drive. Designs → Miro. We store metadata only — jobs, notifications, audit, prefs.
Pay-per-use, not per-seat
Cost lives in API calls and Cloud Run vCPU-seconds. ~£26–40/mo infra at current scale + usage-based AI.
Autopoietic from P2 onward
From the moment AI execution lands, new Nova features ship the same way game tickets do — through Nova itself.