Gravity Labs Platform Architecture
Layer-by-layer reference for the entire platform — every technology, every decision, and exactly how it fits. Living document updated each sprint.
Users & Actors
Four distinct user types interact with the Gravity Labs platform. Each has its own access model, primary surfaces, and operational footprint — from learners in isolated lab VMs to partners co-selling sponsored cohorts.
Participants / Learners
Learners access labs through the browser with least-privilege credentials. Each session receives a uniquely isolated cloud environment. Interaction flows through Guacamole (RDP/SSH) or Code-Server; the AI scoring agent evaluates submissions continuously and surfaces results without blocking the lab experience.
Trainers / Evaluators
Trainers author and curate lab content through the catalog, monitor cohort health in real time via Proctor View, and reconcile AI judgments with human judgment when the domain demands it. Overrides are audited so scoring stays explainable across programs and events.
Platform Admins
Administrators operate the tenant boundary: approving HITL gates, managing subscriptions and quotas, monitoring infrastructure signals, and triggering high-stakes event launches. The Admin Portal is the authoritative surface for policy, procurement, and reliability workflows.
Sponsors / Partners
Partners sponsor cohorts, hackathons, and branded challenges with scoped visibility into utilization, seat consumption, and outcomes dashboards. Access is deliberately read-oriented — enough to prove ROI and co-market success without exposing participant-grade controls.
Access & Edge Layer
All external traffic enters through Nginx acting as the ingress controller inside AKS. Throttling and rate limiting are managed at the AKS/application layer — no external API gateway or WAF in the current architecture.
Nginx Ingress Controller deployed inside AKS. Single entry point for all frontend and API traffic. TLS termination, URL routing, upstream health checks.
Rate limiting handled at two layers:
- Nginx:
limit_req_zoneper IP at ingress - auth-service: Redis-backed per-IP counter with SETNX + EXPIRE pattern
- AKS Network Policy: pod-to-pod traffic rules enforced by Kubernetes NetworkPolicy resources
Azure Application Gateway + WAF v2 is an excellent managed service but is Azure-specific and adds cost. Nginx inside AKS provides the same routing and TLS termination capabilities, runs identically on AWS/GCP, and keeps the stack cloud-agnostic. WAF rules can be added via Nginx ModSecurity module if needed in a future phase.
Full Architecture Stack
Click any layer to expand. Each layer links to its detailed section below.
Single-page application served from AKS. Communicates exclusively through Nginx. Handles auth token storage, silent refresh, and role-based UI rendering. → see detail
Nginx ingress controller inside AKS. TLS termination, URL-based routing to microservices, per-IP rate limiting. No external WAF layer. → see detail
Each business domain is an independent FastAPI microservice in AKS. auth-service ✅ Phase 1. Many more added each sprint. → see detail
Each agent is its own container in AKS. KEDA scales each agent independently based on RabbitMQ queue depth. → see detail
Temporal orchestrates all long-running lab lifecycle workflows. Crash-safe, replay-on-failure, supports HITL wait signals. → see detail
RabbitMQ for async inter-service events. Redis for ephemeral stateful data — JWT tokens, rate limit counters, SSO state, session cache. → see detail
One PostgreSQL 16 instance in AKS. Each microservice owns its own database. Enforced at the Postgres user/permissions level — no cross-service queries possible. → see detail
Dev+Stage on shared AKS cluster (namespaced). Production on dedicated AKS cluster. GitHub Actions builds + pushes to ACR. Argo CD syncs cluster to Git. → see detail
Prometheus scrapes all pods. Grafana manages dashboards and alert routing (path-based). Loki aggregates logs. All inside AKS. → see detail
Client Layer
Single-page application served as static files from an Nginx pod inside AKS.
React 18 + TypeScript, Tailwind CSS, Zustand (state), Axios (HTTP), React Hook Form + Zod (validation), Framer Motion. Built with Vite.
- React 18 concurrent rendering handles real-time lab status without UI freezing
- Zustand — lightweight, no Redux boilerplate
- Tailwind — styles co-located with components
- Vite — sub-second hot reload
Participant clicks "Launch Lab." React polls lab-service every 3s via Axios. Zustand useLabStore updates the status chip — Analyzing → Designing → Provisioning → Ready — without re-rendering the whole page.
API Gateway — Nginx
Nginx runs as a Kubernetes deployment. Single entry point for all API traffic from the frontend. Internal service-to-service calls bypass it entirely on the cluster network.
Azure API Management / AWS API Gateway are cloud-specific and costly at scale. Nginx is free, runs in AKS, and the config is version-controlled. Migrating clouds means copying one nginx.conf — zero code changes.
Combined with Redis-backed per-service rate limiters in each FastAPI service for fine-grained control.
Application Services Layer
Every business domain is its own FastAPI microservice. The list below grows each sprint. The pattern is consistent — one service, one database, one Helm chart, one pipeline.
- FastAPI — async, OpenAPI docs, Pydantic validation
- SQLAlchemy 2.x async + asyncpg
- Alembic — migrations per service
- structlog — structured JSON logs (Loki-ready)
- Each service calls
/internal/verifyon auth-service to validate JWTs
| Service | Domain | Phase |
|---|---|---|
| auth-service | Identity, JWT, SSO | Phase 1 ✅ |
| tenant-service | Orgs, seats, SAML | Phase 2 |
| event-service | Hackathons, cohorts | Phase 2 |
| lab-service | Lab lifecycle + Temporal | Phase 3 |
| catalog-service | Lab templates | Phase 3 |
| scoring-service | 5-dim grading | Phase 3 |
| billing-service | Tokens, subscriptions | Phase 4 |
| notification-service | Email, in-app alerts | Phase 4 |
| analytics-service | Reports, dashboards | Phase 4 |
At 25,000 concurrent users, the scoring engine is under maximum load while billing is idle. A monolith scales everything together and wastes compute. Microservices let us scale scoring-service to 200 pods while billing stays at 2.
AI Agents Layer
Each of the 8 AI agents is its own independently deployed microservice in AKS. Agents are orchestrated by Temporal workflows and communicate via RabbitMQ.
- LangGraph — agent state machine & tool calls
- Claude API — primary LLM for lab design & analysis
- Azure OpenAI GPT-4o — fallback for high-volume scoring
- FastAPI — same framework as app services
- Temporal Activity Workers — each agent registers as a Temporal worker
- During a 25K event, scoring-agent needs 300+ replicas. Lab-architect needs 3. Bundled = you scale both to 300.
- LLM timeout in hint-dispenser doesn't crash provisioning-agent
- Independent versioning — hotfix scoring without redeploying the fleet
- KEDA autoscaling per agent based on RabbitMQ queue depth
| Agent | Max Pods |
|---|---|
| lab-architect-agent | 10 |
| dataset-forge-agent | 50 |
| provisioning-agent | 100 |
| qa-validator-agent | 50 |
| live-observer-agent | 200 |
| hint-dispenser-agent | 100 |
| scoring-agent | 300 |
| cross-validator-agent | 100 |
At T-30min, 20,000 participants submit. The scoring queue fills with 20,000 messages. KEDA sees the queue depth and scales scoring-agent from 5 → 280 pods in under 90 seconds. Each pod calls Claude API for commentary and writes results to the scoring DB. Queue drains in ~8 minutes. KEDA scales back down automatically.
Workflow Orchestration — Temporal
Temporal provides durable execution for all long-running, multi-step lab lifecycle workflows. Write the workflow as normal Python code — Temporal guarantees it completes correctly even if servers crash mid-execution.
Without: Background job calls Terraform → Graph API → DB update. Server crashes after Terraform. You have a live VM, no license, leaked cost, broken state. Manual recovery required.
With Temporal: Workflow replays from the last checkpoint. Terraform step completed? Skip it. Resume at Graph API. Zero data loss, zero manual recovery.
- ProvisionLabWorkflow — AI design → dataset → VM/container → license assign → dataset inject → URL return. 2–8 min.
- GradeLabWorkflow — scoring scripts → AI commentary → 5-dimension score → store results.
- DestroyLabWorkflow — revoke licenses → delete Entra users → Terraform destroy → log cost. Atomic teardown.
Messaging & Cache
RabbitMQ carries async events between services. Redis stores all ephemeral stateful data. Both are self-managed inside AKS for cloud portability.
| Queue | Publisher → Consumer |
|---|---|
| lab.provision.requested | lab-service → provisioning-agent |
| event.started | event-service → lab-service |
| lab.ready | lab-service → notification-service |
| submission.received | lab-service → scoring-agent |
| score.ready | scoring-agent → analytics-service |
Redis is the industry standard — broad team familiarity, strong client ecosystem, and Azure Cache for Redis provides a fully managed HA option when moving to production. The same redis.asyncio client works against both self-managed Redis in AKS and managed Azure Cache for Redis — zero code changes when switching.
Self-managed Redis in AKS for Dev/Stage. Azure Cache for Redis for Production (managed HA, geo-replication). AWS ElastiCache for Redis if moving to AWS. Config change only — no code changes.
Data Layer — PostgreSQL
One PostgreSQL 16 instance running in AKS. Each microservice owns its own logical database inside that instance. Self-managed for full cloud portability.
- Simpler ops — one backup, one upgrade, one monitoring target
- Cheaper — one pod vs 8 separate pods
- Logical isolation enforced at Postgres user level — wrong credentials = no access
- Easy to split individual DBs to dedicated instances later if one service needs to scale
Azure Database for PostgreSQL Flexible Server and AWS Aurora are excellent but cloud-specific. Running Postgres in AKS means identical setup on Azure, AWS, GCP, or on-prem. Backup runs as a Kubernetes CronJob executing pg_dump → cloud object storage (Azure Blob / S3).
PgBouncer is a lightweight proxy that sits between all application pods and PostgreSQL. It multiplexes thousands of application connections into a small fixed pool of real Postgres connections — preventing connection exhaustion at event scale.
| Scenario | Without PgBouncer | With PgBouncer |
|---|---|---|
| 25K event peak | ~2,000+ direct connections → Postgres OOM crash | 2,000 app connections → 20 real Postgres connections |
| KEDA scale-out | 300 pods × 2 = 600 new connections in 90s → storm | Pool absorbs burst transparently |
| Postgres default | max_connections = 100 (hard limit) | max_client_conn = 5000 visible to apps |
Transaction pooling mode breaks prepared statements. Set statement_cache_size=0 in SQLAlchemy connect_args across all services. One-line change — nothing else required.
Services connect to pgbouncer:5432 — never directly to Postgres. Connection string host change only. All query code unchanged.
At 25,000 users the platform has 2,000+ simultaneous DB-connected pods. PostgreSQL cannot handle this. PgBouncer is the difference between the event running and not running.
Infrastructure & CI/CD
Terraform provisions infrastructure. Helm packages services. GitHub Actions builds. Argo CD deploys. Dev and Stage share one AKS cluster; Production gets its own dedicated cluster.
| Environment | Cluster | Namespaces |
|---|---|---|
| Dev | Shared AKS cluster | gl-dev |
| Stage | Shared AKS cluster | gl-staging |
| Production | Dedicated AKS cluster | gl-prod |
| World Record | 2nd prod cluster (multi-region) | gl-prod-2 |
Dev and Stage don't need SLA guarantees — sharing a cluster cuts infra cost significantly. Production gets a dedicated cluster for full isolation, independent scaling, and zero blast radius from staging activity.
charts/*/values.yamlMonitoring & Alerting
Prometheus scrapes metrics from every pod. Grafana manages all dashboards and alert routing. Loki aggregates logs. All deployed inside AKS alongside the application.
Deployed in AKS. Scrapes all pods via ServiceMonitor CRDs. Key metrics:
- Request rate, latency p50/p95/p99, error rate per service
- Active lab count, provisioning queue depth
- RabbitMQ queue depth per queue
- Redis hit/miss rate, memory usage
- Agent LLM token usage + latency
- PostgreSQL connections, query time
Grafana is the single pane of glass — dashboards and alert management. Alert sources: Prometheus (primary) and CloudWatch (AWS metrics for multi-cloud deployments).
Alerts are routed by severity and domain using Grafana's built-in Alerting with path-based routing rules:
| Path Match | Destination |
|---|---|
| severity=critical | PagerDuty (immediate page) |
| domain=lab-provisioning | Ops Slack channel |
| domain=billing | Finance Slack channel |
| severity=warning | Email digest |
| severity=info | Log only |
Ops team watches the Event Operations dashboard on a large screen during a live event. Provisioning queue depth rises → KEDA auto-scales → queue drains. If the queue doesn't drain within 5 minutes, Grafana fires a critical alert → PagerDuty pages the on-call engineer directly.
Peripheral Services
Services outside the core application layer. Object storage uses cloud-managed services per deployment target. Email and CDN are provider-agnostic OSS/SaaS choices.
| Concern | Choice | Azure | AWS | Rationale |
|---|---|---|---|---|
| Object Storage | Cloud-managed | Azure Blob Storage | Amazon S3 | S3-compatible API across all providers. Config-only swap between clouds. |
| Sendgrid | Azure Communication Services | Amazon SES | SMTP-compatible. Swap by changing SMTP host config only. | |
| CDN / DDoS | Cloudflare | Azure Front Door | CloudFront | Cloudflare is provider-agnostic — sits in front of any cloud origin. |
| Secret Management | Azure Key Vault (cloud) / HashiCorp Vault (on-prem) | Azure Key Vault | AWS Secrets Manager | AKS has native Key Vault integration via CSI driver. |
| Lab Streaming | Apache Guacamole (AKS) | Azure Virtual Desktop | Amazon AppStream | Open-source, browser-based, runs anywhere. No client install. |
| VM Labs Compute | Azure VMSS | Azure VMSS | AWS Auto Scaling Groups | Warm pool enabled. Pre-built images. Auto-scaling. Isolated networks. |
Scale Strategy
From 500 to 25,000 participants — the platform scales horizontally at every layer.
Single region
Default node pool
Node autoscale
Quota pre-request
East US 2 + West US 2
Traffic Manager
4 regions
Sponsored licenses
| Mechanism | What It Does |
|---|---|
| HPA | Scales pods based on CPU / memory |
| KEDA | Scales agent pods based on RabbitMQ queue depth |
| Cluster Autoscaler | Adds / removes VM nodes in AKS node pool |
| Temporal worker pools | Provisioning workers scale with workflow load |
| PgBouncer | Connection pooling in front of Postgres for 10K+ connections |
| Azure quota tickets | Submitted 6–8 weeks before large events for vCPU headroom |
Architecture Decision Records
Decisions made and pending. Updated each sprint.
Agent deployment: each agent = own container
Independent scaling. KEDA scales scoring-agent independently during events.
Single PostgreSQL, multiple databases
Self-managed in AKS. Cloud-agnostic. Logical isolation at DB user level.
CI/CD: GitHub Actions + Argo CD
Actions builds + pushes to ACR. Argo CD GitOps-syncs AKS. Drift detection included.
Cluster topology: Dev+Stage shared, Prod dedicated
Shared cluster for non-prod cuts cost. Prod cluster is fully isolated with independent scaling.
Cache: Redis (replacing Valkey)
Same redis.asyncio client. Azure Cache for Redis for managed production HA. Code unchanged.
Object storage: cloud-managed per provider
Azure Blob on Azure, S3 on AWS, GCS on GCP. S3-compatible API across all three — config-only swap.
Alerting: Grafana path-based routing
Grafana manages all alert rules and routing. Prometheus + CloudWatch as metric sources. PagerDuty for critical path.
JWT algorithm: HS256 → RS256 upgrade
Phase 1 uses HS256 (symmetric). Phase 2 should migrate to RS256 — services hold only public keys.
Microsoft Partner enrollment
CSP procurement blocked until enrollment completes. 2–4 week timeline. Blocks Phase 3 lab provisioning.
SMS / Push notifications
Twilio vs self-hosted vs Azure Communication Services. Evaluate in Phase 4.