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.