Platform at a Glance
One page for the shape of the whole system: who talks to it, where the requests land, what runs where when you self-host it, and the two design facts that explain most of the surprises engineers hit on day one.
System context
flowchart TB
subgraph users["Users & channels"]
TU["Tenant users (web console)"]
PA["Platform operators (web-admin)"]
EU["Channel end-users (WhatsApp, Telegram, email, voice)"]
EX["External agents & API clients (REST, MCP, A2A)"]
end
EDGE["Caddy (Compose domain overlay) or Kubernetes Ingress"]
subgraph app["Application services"]
WEB["web :3000 (tenant console)"]
WADM["web-admin :3001"]
KC["keycloak :8080 (realm agentic-ai)"]
CORE["core :8080 (modular monolith)"]
CI["code-intel :8090 (optional)"]
AB["app-builder :8091 (optional)"]
SC["sidecar :8001 (internal)"]
end
subgraph opt["Opt-in services (Compose profiles / Helm values)"]
RP["redpanda :9092"]
TMP["temporal :7233 + temporal-ui"]
LK["livekit + voice-agent"]
TS["tool-sandbox :8095"]
MCPR["mcp-stdio-runner :8097"]
LF["langfuse (tracing)"]
end
subgraph data["Data"]
PG[("PostgreSQL 16 + pgvector + AGE (schema-per-tenant)")]
RD[("Valkey (Redis API)")]
S3["SeaweedFS or any S3 API"]
end
LLM["LLM providers (tenant BYOK or operator-configured pool)"]
TU --> EDGE
PA --> EDGE
EU --> EDGE
EX --> EDGE
EDGE --> WEB
EDGE --> WADM
EDGE --> KC
EDGE --> CORE
EDGE --> CI
EDGE --> AB
WEB --> CORE
CORE --> PG
CORE --> RD
CORE --> S3
CORE --> SC
CORE --> RP
CORE --> TMP
CORE --> MCPR
CORE --> TS
CORE --> LK
CORE --> LLM
CORE --> LF
Figure 1 — system context for a self-hosted install. Locally every service is
reached on a localhost port; with a domain, Caddy (Compose) or your
Ingress (Helm) terminates TLS for app., admin.,
api. and auth. on <your-domain>.
Layers
| Layer | Components | Stack |
|---|---|---|
| Edge | None locally; Caddy with automatic Let's Encrypt in the Compose domain overlay; Ingress or Gateway API with Helm | Caddy 2.8, your ingress controller |
| Frontends | web tenant console + public site, web-admin platform-operator console | Next.js (SSR) |
| API / core | core/app — the modular monolith; ~193 REST controllers, SSE/AG-UI streaming, webhooks, inbound MCP server, A2A agent card | Spring Boot 3.4, Java 21, 42 Maven modules |
| Satellite services | sidecar (knowledge/ML), code-intel (codebase RAG), app-builder, mcp-stdio-runner, voice-agent, tool-sandbox, rl-trainer — all under services/ | Python FastAPI, Node, LiveKit |
| Data & infrastructure | PostgreSQL 16 + pgvector + Apache AGE (schema-per-tenant), PgBouncer (session mode), Valkey, Redpanda, Temporal, Keycloak 26, SeaweedFS, Langfuse | Bundled containers or your managed equivalents |
| External providers | ~14 LLM providers via tenant BYOK or an operator-configured platform pool; Sarvam/ElevenLabs for Indic voice; optional payment gateways (off by default) | HTTP APIs |
| Observability | Prometheus, Grafana, Loki, Promtail, Alertmanager (profile observability); Langfuse LLM tracing (profile tracing) | Grafana stack, Langfuse |
The two architectural facts that explain most surprises
Every tenant is a Postgres schema, resolved from the request's
tenant context via SET search_path. Shared platform state lives
in the control-plane schema. The hard consequence:
PgBouncer must run in session pooling mode — transaction
pooling hands a connection mid-session to another tenant's search_path and
reads the wrong tenant's data. The Full Compose tier runs PgBouncer as the
pgbouncer service on port 6432, configured for session pooling.
Migrations live in two directories —
core/app/src/main/resources/db/migration/controlplane/
(shared, once) and …/tenant/ (per schema) — but they are
validated as a single version space. A
controlplane/V227 and a tenant/V227 collide even
though the folders differ. Before picking a number, check the maximum in
both folders; scripts/ci/flyway-lint.sh is the CI gate that
catches collisions.
Technology stack
| Concern | Choice | Where |
|---|---|---|
| Backend runtime | Java 21, Spring Boot 3.4, Maven multi-module (42 modules, one JAR) | core/ |
| Async/runtime split | Same modules, three assemblies: app (API), app-worker (Temporal), app-jobs (scheduled sweeps); shape-api/shape-jobs Spring profiles | core/app* |
| Frontends | Next.js (SSR); public settings from the runtime environment or optional build args | web/, web-admin/ |
| Python services | FastAPI on Python 3.12 — embeddings/OCR/rerank/transcription, codebase RAG, app generation | services/sidecar/, services/code-intel/, services/app-builder/ |
| Primary datastore | PostgreSQL 16 + pgvector + Apache AGE; schema-per-tenant | deploy/postgres/ image, CNPG in Helm |
| Connection pooling | PgBouncer, session mode | Compose pgbouncer:6432 |
| Cache / quotas | Valkey (Redis API) | Compose valkey:6379 |
| Eventing | Redpanda (Kafka API), opt-in; DB outbox relays in core | profile events |
| Durable workflows | Temporal server + UI; app-worker assembly | profile temporal |
| Identity | Keycloak 26, realm agentic-ai, dual-issuer JWT validation | deploy/keycloak/ |
| Object storage | SeaweedFS S3 gateway (Full), local disk (Lite), or any S3 API | STORAGE_* |
| LLM access | Spring AI dynamic ChatClients; ~14 providers BYOK + optional operator pool; decision model for cost routing | core/providers, core/chat |
| LLM observability | Langfuse (profile tracing); every call also writes llm_call_log in the tenant schema | internal |
| Untrusted code | MCP STDIO packages spawn on mcp-stdio-runner; tenant code executes in tool-sandbox — never inside core | :8097, :8095 |
| Deploys | Signed multi-arch images and OCI Helm charts on GHCR from release.yml; scripts/vegaduta for Compose | deploy/, .github/workflows/ |
Request path, in two sentences
A browser hits Caddy or your Ingress → web (server-rendered);
the page's client JS then calls api.<your-domain> (or
localhost:8080) → core:8080 → PgBouncer → Postgres.
An agent turn inside core runs guardrails → tool resolution (MCP / custom code /
built-ins) → the tenant's BYOK provider or the platform pool →
LlmMetricsAdvisor, which writes an llm_call_log
row and emits an OTLP span to Langfuse when tracing is on.
System Architecture — the layered internals of the monolith and its satellites · Service Topology — the runtime map, service by service · Glossary — every term used above.