# 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](https://gvisor.dev/) or [Kata Containers](https://katacontainers.io/). 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](https://kubernetes.io/docs/concepts/containers/runtime-class/). It does not install gVisor or Kata.

## Prerequisites

- 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](https://cloud.google.com/kubernetes-engine/docs/concepts/sandbox-pods) provides gVisor as the RuntimeClass `gvisor`; elsewhere use gVisor's [containerd quick start](https://gvisor.dev/docs/user_guide/containerd/quick_start/), or [kata-deploy](https://github.com/kata-containers/kata-containers/tree/main/tools/packaging/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](#trade-offs).

## Create the RuntimeClass

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.

```yaml
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`.

## Choose where to set it

| Pods | Field |
|---|---|
| One agent (facade, runtime and the injected `policy-broker`) | `AgentRuntime` `spec.podOverrides.runtimeClassName` |
| Every agent in a workspace | `Workspace` `spec.runtime.runtimeClassName` |
| The tool sandboxes of one class | `SandboxClass` `spec.runtimeClassName` |
| session-api, memory-api, eval-workers, Arena workers | That 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.

## Run one agent under gVisor

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

   ```yaml
    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.

   ```bash
    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

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`.

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

## Run the tool sandboxes under gVisor

Set `spec.runtimeClassName` on the SandboxClass the ToolRegistry names. Sandboxes claimed after the change run under it. See [Sandbox security](/explanation/security/sandboxes/#stronger-isolation-gvisor-and-kata).

```yaml
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`:

```yaml
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:

    ```bash
    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.

```bash
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.

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

## Trade-offs

### 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](/how-to/security/authoring-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 Containers

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.

### Both

- 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.