Architecture Overview¶
Volundr is a self-hosted remote development platform that provisions ephemeral coding sessions on Kubernetes. Each session runs AI-assisted development tools (Claude Code, Codex) inside isolated pods with their own workspace, terminal, and IDE.
System Diagram¶
Users
+-----------+
| Web UI | React/Vite, OIDC auth
| (browser) |
+-----+-----+
|
+-----------+-----------+
| |
REST/SSE WebSocket
(sessions, (chat)
chronicles, |
git, etc.) |
| |
+---------v----------+ |
| Volundr API | |
| (FastAPI) | |
| | |
| +-- Ports ---+ | |
| | Session | | |
| | Chronicle | | |
| | PodManager | | |
| | Storage | | |
| | Gateway | | |
| | Identity | | |
| | AuthZ | | |
| | Git | | |
| | Events | | |
| | Secrets | | |
| | Resources | | |
| +------------+ | |
+---+---+---+---+----+ |
| | | | |
| | | +--- Event Pipeline ---> PostgreSQL (always)
| | | | RabbitMQ (optional)
| | | | OTel (optional)
| | | |
| | +--- Git APIs -----> GitHub / GitLab
| | |
| +--- IDP --------------> Keycloak / Entra ID / Okta
| |
+--- Kubernetes API |
| |
+--------v-----------------------v--------+
| Session Pod |
| +-------------+ +----------+ +-------+ |
| | Skuld broker | | code- | | ttyd | |
| | (WebSocket | | server | | term | |
| | <-> CLI) | | (VS Code)| | | |
| +------+-------+ +----------+ +-------+ |
| | |
| Claude Code CLI / Codex CLI |
| | |
| +------v------+ +-----------+ |
| | Workspace | | Home PVC | |
| | PVC | | (per-user)| |
| | (per-session)| +-----------+ |
+-----------+-------------------------------+
|
+--------v--------+
| Vault / Infisical| (CSI secret injection)
+------------------+
+-------------+
| CLI (Go) | Local mode: embedded stack
| local/remote| Remote mode: REST + WebSocket to API
+-------------+
Key data path: Chat bypasses the API¶
Chat messages flow directly from the browser to the Skuld broker inside the session pod via WebSocket. The Volundr API is not in this path. This keeps latency low and avoids the API becoming a bottleneck during interactive coding.
Browser --WebSocket--> Skuld broker --SDK WS--> Claude Code CLI
|
(reports usage/chronicle
back to API on shutdown)
Data Flows¶
1. Session Creation (Contributor Pipeline)¶
Session creation is a two-phase process: record creation followed by pod provisioning.
- API receives
POST /sessionswith name, model, source config, optional template/preset. - Template and profile defaults are resolved (if specified).
- Repository is validated against the git registry (if git source with validation enabled).
- Session record is persisted with status
CREATED. start_session()transitions toSTARTINGand runs the contributor pipeline:- Build a
SessionContextfrom the request metadata (principal, template name, credentials, integrations). - Run 10+ contributors sequentially. Each returns a
SessionContribution(Helm values + pod spec additions). - Deep-merge all contributions into a single
SessionSpec. PodManager.start()submits the merged spec to the backend (Flux or direct K8s).- Session transitions to
PROVISIONING. A background task polls readiness. - When all containers report ready, session transitions to
RUNNING.
The contributors, in order:
| # | Contributor | Responsibility |
|---|---|---|
| 1 | Core | Session identity, ingress host, terminal restriction |
| 2 | Template | Workspace template defaults (repos, setup scripts) |
| 3 | Git | Clone URL, branch, git credentials |
| 4 | Integration | MCP servers, env vars from enabled integrations |
| 5 | Storage | Workspace PVC + home PVC provisioning |
| 6 | Gateway | Gateway API HTTPRoute config for session routing |
| 7 | Resource | CPU/memory/GPU requests, node selectors, tolerations |
| 8 | Isolation | Namespace, security context, network policy |
| 9 | SecretInjection | CSI driver volumes/mounts for secret injection |
| 10 | Secrets | K8s secret env refs |
2. Chat (WebSocket through Skuld)¶
- Browser opens WebSocket to Skuld broker (
wss://<session>.volundr.example.com/ws/chat). - User message arrives as JSON over WebSocket.
- Skuld forwards the message to the CLI transport:
- SDK transport (Claude Code): sends NDJSON message over a second WebSocket to the CLI process, which connected back via
--sdk-url. - Subprocess transport (legacy): spawns
claude -p <message>as a subprocess. - Codex transport: spawns
codex --full-auto <message>, normalizes output to the common event format. - CLI streams response events (content deltas, tool use, result) back through the transport.
- Skuld forwards events to the browser over the original WebSocket.
- On
resultevent, Skuld extracts token usage and reports it back to the Volundr API via HTTP.
The API is never in the chat hot path.
3. Chronicle Creation¶
Chronicles capture what happened during a session for continuity across reforges.
- Session stops (user-initiated or timeout).
- Skuld broker sends a final report to the Volundr API: summary, key changes, unfinished work, duration.
- Volundr creates (or enriches an existing draft) chronicle with session metadata plus broker data.
- Timeline events collected throughout the session lifetime are aggregated into file summaries, commit summaries, and token burn charts.
- Chronicle status moves from
DRAFTtoCOMPLETE.
4. Authentication Flow¶
Volundr is IDP-agnostic. Authentication is delegated to a standard OIDC provider.
- User authenticates with the IDP (Keycloak, Entra ID, Okta, etc.) via the web UI's OIDC flow or the CLI's device/authorization code flow.
- JWT access token is sent with every API request in the
Authorizationheader. - The
IdentityPortadapter validates the JWT (signature, expiry, issuer, audience). - On first login, JIT provisioning creates the user record, home PVC, and secret backend resources.
- A
Principal(user_id, email, tenant_id, roles) is extracted from the validated token. - The
AuthorizationPortadapter (Cerbos, OPA, or allow-all for dev) checks whether the principal can perform the requested action on the target resource.
In production, Envoy sits in front of session pods and validates JWTs before traffic reaches Skuld.
Key Design Decisions¶
Raw SQL via asyncpg. No ORM. Queries are parameterized, migrations are idempotent SQL files run by migrate (Kubernetes-native). This keeps the data layer explicit and avoids the abstraction overhead of an ORM.
Dynamic adapter loading. Infrastructure adapters are specified as fully-qualified Python class paths in YAML config. The composition root (main.py) imports each class dynamically and passes remaining config keys as kwargs. Adding a new adapter means writing the class and updating YAML -- zero code changes elsewhere.
Ports over mocks. Business logic depends only on abstract port interfaces (ABCs). Tests can use stub implementations without mocking framework magic. The domain layer has zero imports from the adapters package.
CSI-based secrets. Volundr never reads or stores secret values. It generates pod spec additions (volumes, mounts, annotations) that tell the CSI driver (Vault Agent, Infisical) how to inject secrets at pod startup. The SecretInjectionPort returns PodSpecAdditions, not secret data.
Config-driven profiles/templates vs. user-owned presets. Profiles and workspace templates are read-only, loaded from YAML config or Kubernetes CRDs -- managed by platform operators. Presets are DB-stored, user-created runtime configurations (model, MCP servers, resources). This separation keeps operator guardrails distinct from user preferences.
Direct WebSocket for chat. Chat traffic goes straight from the browser to the Skuld broker inside the session pod. The Volundr API is not in this path. This eliminates a proxy hop, reduces latency, and means the API can restart without interrupting active chat sessions.
Session contributor pipeline. Pod spec assembly is decomposed into independent contributors that run sequentially. Each contributor wraps a single port and produces values and pod spec additions. The contributions are deep-merged into a single SessionSpec. This makes pod composition extensible without touching the session service.