Architecture
This page describes Forginate's architecture as it exists today: the services that make up the platform, the data stores behind them, the tenant model, how factory runs are scheduled, and the security/isolation model.
Forginate is a multi-tenant "AI software factory": each tenant runs a project through an LLM-driven pipeline — intake → discovery → planning (TOC + iterative section docs) → architect review → build (per-task agent runs) → review → QA → deploy — on Kubernetes, with model inference delegated to whichever provider is configured per tier.
The public product is served at dev.forginate.com. The SPA talks to the REST API under /api/* on the same host.
Service topology
The platform is a TypeScript monorepo (saas/) deployed as a set of Kubernetes services from a single Helm chart:
| Service | Role |
|---|---|
| api-gateway | Public HTTP + WebSocket termination. Authentication (session cookies, personal access tokens), all /api/orgs/:orgSlug/... REST routes, SSE event streams, the deploy-agent WebSocket (/agent/ws), webhooks (GitHub, Stripe), and serving of the built SPA bundle. |
| orchestrator | Background control loops. Schedules factory and chat-editor runs as Kubernetes Jobs, watches them, and runs the deployment sweeps (planner, dispatcher, monitor, AWS account vendor, cost pull). |
| factory-runner | The containerised factory pipeline. One ephemeral pod per run; executes the actual LLM work (planning docs, build agents, audits) and reports usage and events back to the gateway's internal API. |
| chat-editor | The inline document-editor agent service ("vibe editor"). Streams LLM turns over SSE and proposes document patches via a propose_edit tool loop. |
| qa-runner | Sandboxed QA harness for exercising built applications. |
| shared/db | Not a service — the shared Drizzle schema, migrations, and DB client library used by every service. |
| web | The React SPA (Vite, TanStack Query, Tailwind, Radix). Bundled into the api-gateway's static assets and served by the gateway; there is no separate web server in the deployed shape. |
There is also a separate public, unauthenticated share portal surface (token-scoped document review for outside customers), served by the same gateway under /api/share/* and gated so the public share host can reach only those routes.
┌────────────────────────────┐
Browser (SPA) ────▶ │ api-gateway │ ◀──── deploy agent
/api/* + SSE/WS │ auth · REST · SSE · /agent│ (outbound WebSocket
│ /ws · webhooks · SPA │ from customer cluster)
└──────┬──────────┬──────────┘
│ │ internal API (cluster-only)
Redis pub/sub + │ │
job queues │ │
┌──────▼───┐ ┌───▼──────────────┐
│ orchestr.│ │ factory-runner │ one pod per run
│ sweeps │──│ chat-editor │ (k8s Jobs)
│ + k8s │ │ qa-runner │
│ Jobs │ └──────────────────┘
└──────┬───┘
│
┌──────────────────┼───────────────────┐
▼ ▼ ▼
CNPG Postgres Redis MinIO
(all tenant state) (queues, pub/sub, (doc bodies, artifacts,
sessions, rate attachments, logs)
limits)Data stores
- Postgres (CloudNativePG). The single source of truth for all tenant state: organizations, users, memberships, projects, products, requests, documents and versions, runs, usage events, deployments, deploy targets, vended AWS accounts, tokens, audit events, billing state. Every tenant-owned row carries an
org_id. - Redis. Job/dispatch queues, pub/sub channels (e.g. the
deploy:cmdchannel the orchestrator uses to hand deployment commands to whichever gateway instance holds the agent's socket), rate-limit counters, and live UI fan-out. - MinIO (S3-compatible object storage). Document bodies, uploaded attachments, run artifacts and transcripts, and post-mortem logs. Postgres rows hold pointers into MinIO, not the payloads themselves.
Tenant model
Organization (the billable entity; slug appears in every API path)
├── Memberships (user ↔ org, role: owner | admin | developer | viewer)
├── Projects (a software product being built; may be linked to a
│ │ GitHub repo via the Forginate GitHub App)
│ ├── Products / Requests (improvement & build requests moving
│ │ through the pipeline)
│ ├── Documents (planning docs: TOC + section docs, with
│ │ versions, approvals, feedback, shares)
│ ├── Runs (each factory pipeline execution — one
│ │ k8s Job / pod, with live cost + events)
│ └── Deployments (running instances of the built app on a
│ deploy target)
├── Deploy targets (customer k8s cluster via deploy agent, or a
│ Forginate-vended AWS account)
├── Providers (BYO LLM provider configs)
└── Billing (Stripe customer, plan, usage, invoices)Isolation is enforced logically at the database layer — every query in the gateway is scoped by the authenticated org — and physically at the run layer (see below).
How factory runs are scheduled
- A user action (advancing a request, running build tasks, requesting a doc regeneration, etc.) creates a run row in Postgres.
- The orchestrator claims pending runs and creates one Kubernetes Job per run with a deliberately locked-down pod spec:
restartPolicy: Never,backoffLimit: 0— a failed run fails; nothing silently retries and re-spends money.activeDeadlineSecondswall-time cap (raised high enough that a run patiently waiting out a provider outage isn't SIGKILLed).automountServiceAccountToken: false— the runner has zero Kubernetes API access.ttlSecondsAfterFinishedso logs remain fetchable post-mortem before the cluster reaps the Job.- A configurable
runtimeClassName(NetworkPolicy is the primary sandbox boundary today).
- The factory-runner pod boots, reads a short-lived run-scoped agent token from its environment, and fetches its manifest (what to do, which model tiers, cost limits) from the gateway's cluster-internal API.
- During execution the runner emits durable events (progress, per-call usage, questions for the user) to the gateway; the SPA follows along over SSE.
- On completion the runner posts exact token usage per model; the gateway reprices server-side from the admin-managed tier price table (the runner carries no price table — its own cost figure is advisory and used only for the in-pod spend cap).
- If an LLM provider hits a hard wall (credit exhaustion, overload, rate limit), the runner does not die: it keeps its full in-memory conversation and retries the same call every 15 minutes for up to 24 hours, in place, so nothing completed is ever re-spent. The UI shows a "delayed but still running" badge.
Security / isolation model
- Authentication. Web login ("Sign in with GitHub") produces a JWT session cookie (
forginate_session) signed by the api-gateway. Programmatic access uses personal access tokens (fgn_pat_..., stored hashed, scoped read/write/admin). See API. - Per-org authorization. Every tenant route is nested under
/api/orgs/:orgSlug/and re-checks the caller's membership and role on that org. Admin-only routes require elevated roles. - Run isolation. Each run is its own pod with an ephemeral filesystem, no service-account token, a wall-time deadline, and a per-run dollar cost cap.
- Deploy-agent trust model. Customer-cluster deploy agents dial out to the gateway (
GET /agent/wswith an enrollment key); Forginate never needs inbound access to a customer cluster, and every frame in both directions is schema-validated before it is acted on. A malformed frame is answered with an error and dropped, never crashed on. - Vended AWS accounts. Each
aws_accountdeploy target is a dedicated AWS account created under Forginate's AWS Organization for that customer, so cloud-hosted workloads are account-isolated from each other. - Share portal. Public document shares are capability-token URLs scoped to a single product; revoked/expired tokens are rejected, and reads are limited to approved document versions.
- Audit log. Security- and billing-relevant actions (token creation, member changes, approvals, plan changes) are written to an append-only audit table, queryable by org admins.
What this page does not cover
Internal deployment details (Helm values, cluster operations, backup runbooks) live in the repository's docs/ and deploy/ directories and are aimed at operators, not end users.