Skip to Content
Architecture

Architecture

Marshal gives every AI coding agent session its own on-demand, isolated Kubernetes environment. This page explains how that’s actually built: what runs all the time versus what exists only for the length of a session, how one session is kept apart from another and from the node it runs on, how credentials reach a tool without ever reaching the agent, who can see and control what, and where state lives. If you’re evaluating Marshal for security or architecture fit, this is the page to read closely.

Browser / CLICONTROL PLANE — always on, one per installwebdashboardgatewayterminal / WebSocket routingapiauth · sessions · RBAC · vaultmarketplacetool / agent / MCP catalogSESSION PLANE — one set per session, ephemeralsupervisorPID 1 — the only thing that touches real credentialsagentthe coding agent itselfmcps sidecaroptionalPostgreSQLdurable source of truthRedisephemeral cache onlyroutes I/Oprovisionscredentials injected at point of use

Solid arrows are always-live connections; the dashed arrow (supervisor → mcps) is optional, present only when a session’s manifest asks for MCP servers. Full detail on each component is below.

Control plane vs. session plane

Marshal is split into two very different lifecycles.

The control plane is a small set of always-on services, deployed once by the Helm chart and running for as long as the installation exists:

ComponentRole
apiAuth, session lifecycle, org/RBAC, credential vault, pod provisioning
gatewayTerminal/WebSocket routing — bridges a browser or CLI connection to the right session pod
webThe dashboard — manage orgs, credentials, sessions, settings
marketplaceIn-cluster catalog service — merges the upstream tool/agent/MCP catalog with any customer-added packages

These four are ordinary Kubernetes Deployments. They scale independently, have their own resource budgets, and are the only things a customer’s platform team needs to operate day to day.

The session plane is everything else, and it’s ephemeral by design. Each active coding session gets its own pod, created by the api at session-start and deleted (or stopped) when the session ends:

  • a supervisor container — the pod’s PID 1, and the only thing in the pod that ever touches real credentials or unfiltered network egress
  • an agent container — where the actual coding agent (Claude Code, or another supported agent) runs
  • an optional mcps sidecar, if the session’s manifest asks for MCP servers

A session pod exists only while that session is active. Stop the session and the pod is gone; there is no persistent per-session compute sitting idle between uses. This split — a handful of long-lived control-plane services versus a growing and shrinking population of session pods — is the shape of the whole system.

Isolation model

Every session pod is isolated from every other session pod, and from the control plane, on three layers:

Pod and namespace scoping. Session pods run in a dedicated namespace (<release>-spaces), separate from the namespace the control-plane Deployments run in. Each pod is labeled with its session, org, and user IDs, and the api’s Kubernetes access to that namespace is a namespace-scoped Role/RoleBinding — not a ClusterRole — so operating Marshal never requires cluster-admin.

Network policy. Each session pod gets its own NetworkPolicy scoped to that pod’s label selector. By default the agent container’s egress is locked down to DNS; all other outbound traffic — HTTP, HTTPS, tool connections — is forced through the supervisor, which enforces an allowlist and logs what it sees. One session’s agent cannot reach into another session’s pod, and cannot reach the internet except through its own supervisor’s mediated path.

Shared nodes, minimally-scoped node identity. For efficient bin-packing, session pods from different sessions (and different orgs, on a multi-tenant install) commonly land on the same Kubernetes node. That’s a deliberate scheduling choice, not an oversight — but it has a consequence Marshal’s design accounts for explicitly: any pod on a node can, in principle, reach that node’s own service-account identity (for example via the cloud metadata endpoint), so the node’s service account must never carry more privilege than the node itself strictly needs. Marshal keeps node-level service accounts minimal and puts anything more privileged — like the supervisor’s object-storage access for durable session volumes — behind a dedicated identity that’s bound only to the supervisor container, not ambient on the node. The practical implication for you: don’t grant the underlying node’s cloud identity broad roles “for convenience” in your own cluster config, since any session’s code effectively shares that node.

Credential flow

This is the architectural decision most evaluators care about most, so it’s worth stating plainly:

The agent process never holds a real credential. Not in its environment, not in a file it can read, not anywhere in its own container. Every credential a tool needs is injected at the moment of use, by the supervisor, into that tool’s own subprocess — and nowhere else.

Concretely: when the agent runs a tool that needs a credential — a git push, a cloud CLI call, an API request signed with a token — it isn’t invoking the real binary directly. It’s invoking a thin wrapper that talks to the supervisor over a Unix socket local to the pod. The supervisor looks up what that tool call needs, execs the real binary itself, and injects the credential into that subprocess’s environment — a process the supervisor owns, in a part of the pod the agent container cannot introspect. Output streams back to the agent normally; the credential itself never crosses into agent-owned memory, filesystem, or environment. The same pattern covers outbound HTTP: the agent’s traffic is transparently routed through a supervisor-side proxy, which is the only place in the pod where a credential-bearing header could be added.

The rationale is blast-radius containment. The agent container runs a general-purpose AI coding agent driven by an LLM interpreting instructions from a repository, a task, and whatever it reads along the way — it’s the most exposed, least predictable part of the pod. By construction, even a fully compromised or manipulated agent process has no credential to exfiltrate, because it was never handed one.

Underlying all of this, credentials at rest are envelope-encrypted in the control plane’s vault: each credential has its own per-credential encryption key, which is itself wrapped by a KMS (cloud KMS or, for self-hosted deployments without one, HashiCorp Vault’s Transit engine). The vault API never returns a stored credential’s value once it’s written — not to the dashboard, not to any API caller, regardless of role.

Access control

Marshal’s authorization model is deliberately narrower than “anyone in my org can see everything.” Org membership grants a role — owner, admin, or member — but a session (a “space”) belongs to the member who created it, not to the org at large. By default, a member only ever sees and controls their own sessions; owner and admin get org-wide read visibility (for accountability — knowing what’s running) but not the ability to act on someone else’s session or see the value of someone else’s stored credential. Org-wide visibility into other members’ resources is available to owner/admin roles, but it’s an explicit opt-in on specific endpoints, never the default view — opening the dashboard never surfaces another member’s sessions just because you happen to hold an admin role.

Datastores

Marshal uses two datastores, and they play very different roles:

  • PostgreSQL is the durable source of truth. Sessions, orgs and their membership, encrypted credentials, manifests, and the audit trail all live here. This is the data you cannot afford to lose.
  • Redis is a pure ephemeral cache. WebSocket session tokens, live SSE event streams, the registry mapping a session to its current pod address, and in-flight OAuth state all live here — and nothing stored in Redis is unique. If Redis were lost entirely, every one of those things could be reconstructed or would simply be re-issued on the next request.

The operational takeaway: back up Postgres. Redis needs no backup story at all — losing it costs you some in-flight session tokens and live connections that reconnect on their own, not data.

Deployment model

Marshal ships as a single Helm chart, and the chart’s pinned version is the product version — one number that pins an exact, tested set of component images (the control-plane workloads and the runtime images stamped into session pods alike). What you install is what was tested; upgrades and rollbacks are one-command operations against that same pinned set. The full mechanics — registry access, the Bill of Materials, secrets providers, and step-by-step installation on EKS, GKE, or a single node — are covered in Installing Marshal.