Architecture
Relay is a .NET 9 solution made of focused projects. The public API and the control panel are separate ASP.NET Core apps that share the same domain, data and provider libraries.
#Projects
| Project | Kind | Responsibility |
|---|---|---|
| Application | Blazor Web App (host) | The control panel host + api/* control-plane controllers + cookie auth via the company identity app. Serves the WebAssembly panel. |
| Application.Client | Blazor WASM | The interactive admin panel (Dashboard, Playground, Prompts, Agents, Workflows, Observability, …). |
| Application.Api | ASP.NET Core | The public gateway API (/v1/*), API-key auth, the routing pipeline, the workflow engine, the realtime relay and all background services. |
| Application.Shared | Class library | Domain models, both DbContexts, EF configuration, and shared services (prompts, agents, skills, workspaces, connectors, …). |
| Application.Providers | Class library | LLM provider adapters (OpenAI-compatible, Anthropic, Gemini). Raw HttpClient, no vendor SDKs; it carries its own request/response contracts and does not reference Application.Shared. |
| Application.Telemetry | Class library | Writes the observability trace tree (relay_observation, relay_score) to ClickHouse and serves the dashboard and explorer queries. |
| Application.Database | SQL project | The SQL Server schema as a DACPAC (tables authored as .sql). The sole schema authority. |
| Application.Doc | ASP.NET Core | This documentation site. No package or project references. |
| Application.AppHost | .NET Aspire AppHost | Local development only: starts panel, gateway and docs together and shows their logs, traces and metrics in the Aspire dashboard. |
| Application.ServiceDefaults | Class library | OpenTelemetry SDK wiring plus Development-only /healthz and /alive endpoints; referenced by Application and Application.Api. |
Alongside the solution, Application.Database.Log is a plain folder of ClickHouse scripts (schema and dashboard queries — ClickHouse has no DACPAC tooling), sdks/ holds the thin client SDKs, and deploy/clickstack/ a ClickStack compose file.
#Data stores
- SQL Server — the relational store, split across two
DbContexts:GatewayDbContext(databaserelay_gateway) — workspaces and members, providers, models, API keys, budgets, prompts, agents and versions, skills, tools, MCP servers, connectors, workflows and runs, knowledge-base metadata, batches, evals, conversations, observability configuration (score configs, queues, evaluators, datasets, alert rules), audit.UserManagementDbContext(databaseidentity) — ASP.NET Identity users and roles, per-workspace SSO setup, and read-only application-access tables. Tenancy itself (workspaces) lives inrelay_gateway.- Naming is snake_case; rows carry soft-delete + audit columns via a shared base model.
- ClickHouse — the observability store: one row per observation in
relay_observationand scores inrelay_score, always written when a connection string is configured. The usage dashboard can optionally read a ClickStack collector'sotel_tracesinstead (Telemetry:DashboardSource=otel). - Qdrant — the vector store for knowledge bases (RAG).
#Request lifecycle (chat)
A POST /v1/chat/completions flows through the gateway pipeline:
- Authenticate the API key (
ApiKeyscheme) and load the key + its workspace. The panel's Playground arrives here too, with a short-lived delegation token that names one of your keys. - Apply key policy — scopes, the per-key rate limit and the model allow-list — then open a trace from the attribution headers.
- Route the requested model through
ModelRouter→ a concrete provider adapter + invocation context, resolving the explicit model →Gateway:DefaultModel→ alias remap →auto/tierstrategies, decrypting the provider's key, building the fallback chain, and applying the workspace budget hard-stop. - Execute via
GatewayChatService: image validation, moderation, the response cache and idempotency store, transient retry, provider-health circuit breaking, fallback to the next model in the chain, and SSE streaming when requested. - Record the generation as an observation (tokens, cost, latency, status, routed model, fallback used, captured bodies per policy) — written to ClickHouse and, optionally, exported over OTLP.
Agents, workflows, batches, evals, realtime sessions and knowledge search run through the same router and tracer, so their model calls appear in the same trace tree.
#Deliberate constraints
Two constraints shape how Relay is built and extended:
- No new NuGet packages. The build environment cannot restore packages beyond what is already cached. Every new capability is written with the BCL only — raw
HttpClient,System.Text.Json, hand-written parsers (the OTLP exporter, the RAG reranker, the PDF/DOCX/XLSX/PPTX/HTML extractors, the workflow engine, even this docs site's Markdown renderer are all BCL-only). - DACPAC-only schema. New tables/columns are authored as
.sqlin Application.Database and added to theDbContextmodel — not via EF migrations. Deploy schema by publishing the DACPAC.
See Configuration & secrets and Deployment for how these play out in practice.