Skip to content

Run agents under gVisor or Kata

Run an agent’s pod, and the tool sandboxes it claims, under a sandboxed container runtime such as gVisor or Kata Containers. Both put a boundary between the pod and the host kernel, so a kernel vulnerability reachable from inside the pod does not reach the node.

Omnia selects the runtime through a Kubernetes RuntimeClass. It does not install gVisor or Kata.

  • Nodes with the runtime installed and registered with containerd: the runsc shim for gVisor, or a Kata shim such as kata-qemu-runtime-rs. GKE Sandbox provides gVisor as the RuntimeClass gvisor; elsewhere use gVisor’s containerd quick start, or kata-deploy, which also creates Kata’s RuntimeClasses.
  • A RuntimeClass whose handler names that shim.
  • For Kata: nodes that expose hardware virtualisation (/dev/kvm). See Trade-offs.

Name the RuntimeClass anything; the handler must match the containerd runtime name. Set scheduling.nodeSelector when only some nodes have the runtime, so pods that select it land only on those nodes.

apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc
scheduling:
nodeSelector:
example.com/gvisor: "true"

A pod scheduled onto a node whose containerd has no such handler fails to start with a FailedCreatePodSandBox event. A pod that names a RuntimeClass the cluster does not define is rejected at admission, and the agent’s ReplicaSet reports FailedCreate.

PodsField
One agent (facade, runtime and the injected policy-broker)AgentRuntime spec.podOverrides.runtimeClassName
Every agent in a workspaceWorkspace spec.runtime.runtimeClassName
The tool sandboxes of one classSandboxClass spec.runtimeClassName
session-api, memory-api, eval-workers, Arena workersThat workload’s own podOverrides.runtimeClassName
A chart-owned Deployment (operator, dashboard, arena-controller, PromptKit LSP, doctor, management API)<component>.podOverrides.runtimeClassName in Helm values

An agent’s own spec.podOverrides.runtimeClassName replaces the Workspace default. An agent that sets its own serviceAccountName opts out of the Workspace identity defaults, but still inherits the Workspace runtimeClassName.

The facade and runtime share one pod, so they always run under the same runtime. A RuntimeClass on the agent does not apply to its sandboxes, and a RuntimeClass on a SandboxClass does not apply to the agent.

  1. Set spec.podOverrides.runtimeClassName on the AgentRuntime.

    apiVersion: omnia.altairalabs.ai/v1alpha1
    kind: AgentRuntime
    metadata:
    name: support-agent
    namespace: support
    spec:
    podOverrides:
    runtimeClassName: gvisor
    # ...the rest of the spec
  2. Confirm the new pod carries the class.

    Terminal window
    kubectl get pods -n support -l app.kubernetes.io/instance=support-agent \
    -o jsonpath='{.items[*].spec.runtimeClassName}'

The change alters the pod template, so the Deployment rolls the agent’s pods.

Run every agent in a workspace under gVisor

Section titled “Run every agent in a workspace under gVisor”

Set spec.runtime.runtimeClassName on the Workspace. The operator re-reconciles every AgentRuntime in the workspace namespace and rolls each agent that does not set its own runtimeClassName.

apiVersion: omnia.altairalabs.ai/v1alpha1
kind: Workspace
metadata:
name: support
spec:
displayName: Support
namespace:
name: support
runtime:
runtimeClassName: gvisor

Set spec.runtimeClassName on the SandboxClass the ToolRegistry names. Sandboxes claimed after the change run under it. See Sandbox security.

apiVersion: omnia.altairalabs.ai/v1alpha1
kind: SandboxClass
metadata:
name: codegen
namespace: support
spec:
image: ghcr.io/altairalabs/codegen-sandbox:latest
runtimeClassName: gvisor
# ...ports, filesystem, resources

For the chart’s codegen and browser classes, enable a preset: a copy of the class, beside it, on the RuntimeClass you name. A ToolRegistry handler opts in through sandbox.class:

sandbox:
runtimePresets:
gvisor: {enabled: true, runtimeClassName: gvisor} # class codegen-gvisor
kata: {enabled: true, runtimeClassName: kata-qemu-runtime-rs} # class codegen-kata
browserClass:
runtimePresets:
gvisor: {enabled: true, runtimeClassName: gvisor} # class browser-gvisor

Check the class before you rely on it. kubectl get sandboxclass codegen -n support shows the runtime family the operator resolved (FAMILY) and whether its claims can run (READY).

  • READY is False when the RuntimeClass does not exist, or when the class uses a workspace or scratch volume on Kata with Firecracker, which cannot serve either. Its Ready condition gives the reason, and its claims fail ClassNotReady.

  • FAMILY is unknown when Omnia does not recognise the RuntimeClass’s handler. The class still runs, but none of the protections is verified on it. Label the RuntimeClass with its family to vouch for it:

    Terminal window
    kubectl label runtimeclass my-vm sandbox.omnia.altairalabs.ai/runtime-family=kata

To confirm a sandbox runs on gVisor’s kernel, read its kernel log. gVisor’s log names gVisor; a container on the host kernel reads the host’s log or is denied it.

Terminal window
kubectl exec -n support deployment/<claim>-sandbox -- sh -c 'dmesg | head -3'

The facade and runtime images ship without a shell, so check an agent pod by its spec.runtimeClassName instead.

To measure what your runtime keeps on your own nodes, run hack/sandbox-runtime-spike/run.sh from the Omnia repository. It runs a sandbox-shaped pod under each RuntimeClass you name and writes summary.md to the output directory. Delete the spike-<runtimeclass> namespaces it leaves behind.

Terminal window
STORAGE_CLASS=<your RWX class> hack/sandbox-runtime-spike/run.sh ~/.kube/config <context> ./out runc gvisor

gVisor intercepts the pod’s system calls in a user-space kernel and implements most of the Linux system call surface, not all of it. A binary that needs an unimplemented call fails at that call.

  • The built-in facade and runtime are Go binaries that use TCP, gRPC, HTTPS and the filesystem, which gVisor implements.
  • A custom runtime (see Author a custom runtime) or a framework runtime that starts subprocesses brings its own system call profile. Test it under gVisor before you rely on it.
  • gVisor runs networking in its own user-space stack. Every model call, session write and tool call pays that cost, so expect higher per-request latency and lower throughput than on the host kernel. Measure with your own traffic.
  • File-heavy work, such as builds in a codegen sandbox, is slower than on the host kernel.
  • gVisor needs no hardware virtualisation, so it runs on ordinary cloud VM nodes and on kind.

Kata runs each pod in a lightweight virtual machine with its own guest kernel, so the system call surface is a full Linux kernel.

  • Kata applies no seccomp filter inside its VM. The guest kernel is the boundary.
  • Kata on Firecracker has no shared filesystem: a sandbox’s writes to /workspace never reach the volume, and scratch sizeLimit is not enforced. Use it only for a class with neither; the class reports Ready=False otherwise.
  • Kata needs hardware virtualisation on the node. On cloud VM nodes that means nested virtualisation, which not every instance type or provider offers. Bare-metal nodes avoid the question.
  • Each pod carries the guest VM’s memory and CPU. Declare it in the RuntimeClass’s overhead.podFixed so the scheduler and resource quotas count it.
  • Pods start more slowly than under runc or gVisor, which lengthens agent rollouts and the first tool call of a conversation that claims a sandbox.
  • Agent pods are long-lived, so the start-up cost applies at rollout and scale-out. Sandboxes start per conversation, so their start-up cost lands on the first tool call that claims one.
  • The pod security context, the ServiceAccount and, on a sandbox, the NetworkPolicy apply unchanged. A RuntimeClass adds a boundary and removes none of them.
  • A RuntimeClass applies to a whole pod. Every conversation an agent pod serves shares that pod’s boundary.