Skip to main content
BlogAI

Kubernetes Agent Sandbox CRD: Isolated Stateful AI Agents

Kubernetes Agent Sandbox CRD explained: Sandbox, SandboxTemplate, SandboxClaim and WarmPool for isolated stateful AI agents, with gVisor and tuning traps.

Matthews Wong

Software Engineer

October 202612 min read
Kubernetes Agent Sandbox CRD: Isolated Stateful AI Agents

Key Takeaway

Kubernetes Agent Sandbox is a SIG Apps project that adds a Sandbox custom resource for isolated, stateful, singleton workloads such as AI agent runtimes. It delegates isolation to gVisor or Kata Containers through RuntimeClass, and uses SandboxWarmPool and SandboxClaim to hand out pre-warmed pods instead of paying about one second of pod startup.

The first thing I did with this project was stop reading the announcement and open the manifests, because the Kubernetes blog post and the repository README disagree on a detail that decides whether your install command works. The blog says to apply manifest.yaml from a release; the README today says sandbox-with-extensions.yaml, and the v1.0.7 release page lists exactly three assets: extensions.yaml, sandbox-with-extensions.yaml and sandbox.yaml.

This post is a close reading, not a benchmark I ran. I did not stand up a cluster for it. Everything below comes from the Kubernetes blog announcement of 20 March 2026, the kubernetes-sigs/agent-sandbox repository (README, threat model, performance tuning guide, configuration guide, roadmap and example manifests) and the PyPI page, each fetched on 17 October 2026. Where a number appears, it is the project's own, with the cluster it was measured on.

Why a StatefulSet of one does not cut it

The announcement describes an AI agent as an isolated, stateful, singleton workload: a digital workspace for an LLM that needs a persistent identity and a secure scratchpad for writing and executing, often untrusted, code. Because agents are expected to be mostly idle except for brief bursts, they also need suspension and rapid resumption. The authors, Janet Kuo and Justin Santa Barbara, concede that you could approximate this with a StatefulSet of size 1, a headless Service and a PersistentVolumeClaim per agent, and then call managing that at scale an operational nightmare.

That framing is the reason the project exists, and it is also the test I apply to it. If you run five agents, the StatefulSet recipe is fine and a new CRD is overhead. The CRD earns its place when you are creating and deleting thousands of these per day, need one place to enforce a RuntimeClass, and want a pool of ready ones. The announcement itself says the project is currently in development under SIG Apps.

The four resources and the one install command that matters

The README defines one core resource and three opt-in extensions. The Sandbox kind lives in agents.x-k8s.io/v1beta1 and manages a single stateful pod with a stable hostname, network identity and persistent storage. The extensions live under extensions.agents.x-k8s.io, and the example manifests in the repository use v1beta1 for them as well. The roadmap marks the alpha-to-beta move as done: v1beta1 is the only served version and the v1alpha1 types and conversion webhooks are removed.

ResourceAPI groupRole in the design
Sandboxagents.x-k8s.io/v1beta1One stateful pod with stable identity, persistent storage and lifecycle control (create, scheduled delete, pause, resume)
SandboxTemplateextensions.agents.x-k8s.io/v1beta1Reusable recipe: pod spec, runtimeClassName, network policy and volume claim templates, owned by the platform team
SandboxWarmPoolextensions.agents.x-k8s.io/v1beta1Keeps replicas pre-provisioned sandboxes ready from one SandboxTemplate, so nobody waits for a pod to start
SandboxClaimextensions.agents.x-k8s.io/v1beta1The request an agent platform makes: hand me a sandbox from this pool (the example claim sets only warmPoolRef)

Install is two kubectl apply calls, or one if you take the combined manifest. The block below is the README's own sequence, followed by the verification it prescribes. The README recommends pinning a version for production and GitOps; v1.0.7, published on 15 October 2026, was the latest release when I looked.

# From the README. Pin a version for production and GitOps.
export VERSION="v1.0.7"

# Core + extensions in one manifest (SandboxTemplate, SandboxClaim, SandboxWarmPool):
kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/download/$VERSION/sandbox-with-extensions.yaml

# Or split it. Core only, then the opt-in extensions:
kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/download/$VERSION/sandbox.yaml
kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/download/$VERSION/extensions.yaml

# Verify, exactly as the README prescribes:
kubectl get crd sandboxes.agents.x-k8s.io
kubectl get deploy agent-sandbox-controller -n agent-sandbox-system

# The announcement blog (March 2026) still shows the older shape:
#   .../releases/download/vX.Y.Z/manifest.yaml
# v1.0.7 lists sandbox.yaml, extensions.yaml and sandbox-with-extensions.yaml.

# Client SDK (optional):
pip install k8s-agent-sandbox

Do not copy the install lines from the announcement blindly. It uses manifest.yaml and the example tag v0.1.0, while current releases ship sandbox.yaml, extensions.yaml and sandbox-with-extensions.yaml. The README is the document that tracks the release assets, so read the assets list on the release page before you script an install.

Isolation is delegated, and the template is where you enforce it

The most important sentence in the threat model is also the easiest to skim past: Agent Sandbox itself does not implement isolation but supports configuring secure runtimes. The announcement says the Sandbox resource natively supports runtimes like gVisor or Kata Containers for kernel and network isolation. In practice that is the ordinary Kubernetes mechanism, a RuntimeClass named in the pod template. The project's gVisor quickstart creates one with handler runsc and tells you to uncomment runtimeClassName: gvisor in the template.

The threat model then says where to enforce it. Platform administrators can define the secure runtimeClassName in a SandboxTemplate used by a SandboxWarmPool, so the tenant who claims a sandbox never chooses the runtime. That is the design decision worth copying: put the isolation choice in the object the platform team owns, not in the object the agent platform creates per session.

Below is a template assembled from two files in the repository: the Python client's template for the runtimeClassName line and resource requests, and the aider example for the network policy and workspace volume. It is my composition of their fields, not a file from the repo, so treat it as a starting point and apply it to a test cluster first.

# 1. RuntimeClass, as in the project's gVisor quickstart (handler = runsc)
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: gvisor
handler: runsc
---
# 2. Template: the platform team owns this, the claiming tenant never sees it.
#    Fields composed from the Python client template and the aider example.
apiVersion: extensions.agents.x-k8s.io/v1beta1
kind: SandboxTemplate
metadata:
  name: agent-gvisor
  namespace: default
spec:
  networkPolicyManagement: Managed      # the documented default
  networkPolicy:
    ingress:
    - ports: [{protocol: TCP, port: 8888}]
    egress:
    - ports: [{protocol: UDP, port: 53}, {protocol: TCP, port: 53}]
    - ports: [{protocol: TCP, port: 443}]
  volumeClaimTemplates:
  - metadata:
      name: workspace
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 1Gi
  podTemplate:
    spec:
      runtimeClassName: gvisor          # isolation chosen here, not by the claim
      automountServiceAccountToken: false   # templates default to this when omitted
      restartPolicy: OnFailure
      containers:
      - name: runtime
        image: registry.example.com/agent-runtime:1.0   # replace with your image
        ports:
        - containerPort: 8888
        resources:
          requests: {cpu: 250m, memory: 512Mi, ephemeral-storage: 512Mi}
          limits:   {cpu: "1",  memory: 1Gi,  ephemeral-storage: 1Gi}

Warm pools: what the one-second claim really buys

The announcement's argument is short. Starting a pod adds about a second of overhead, fine for deploying a microservice and not fine when an idle agent is invoked and the model must wait before it can think or act. SandboxWarmPool maintains pre-provisioned sandbox pods, and a SandboxClaim hands one over. The example claim in the repository sets a single spec field: a warmPoolRef with a name.

# Warm pool: keeps 3 sandboxes ready from the template above.
apiVersion: extensions.agents.x-k8s.io/v1beta1
kind: SandboxWarmPool
metadata:
  name: agent-gvisor-pool
  namespace: default
spec:
  replicas: 3                  # replicas: 0 means every claim cold-starts
  sandboxTemplateRef:
    name: agent-gvisor         # must match the SandboxTemplate metadata.name
---
# Claim: what your agent platform creates per session.
apiVersion: extensions.agents.x-k8s.io/v1beta1
kind: SandboxClaim
metadata:
  name: session-4821
  namespace: default
spec:
  warmPoolRef:
    name: agent-gvisor-pool
  # Wrong: setting spec.env or spec.volumeClaimTemplates here disables warm
  # adoption and forces a cold start from the template.

The performance tuning guide is where the project puts its measurements, and it is more candid than the announcement. It separates two regimes and says so explicitly. Draining an already-ready pool is cheap; keeping a pool full while claims keep arriving is not. The table collects the figures I read, with the environment each came from.

ScenarioWhat the guide reportsWhere it was measured
Burst warm adoptionAbout 300 claims per second per cluster at p90 of 200 ms or less (3,600 claims over a 12 s burst into a pre-warmed 3,700-replica pool)PR-level benchmark: kops, Kubernetes 1.35.6, e2-standard-16 control plane
Sustained, tuned (replenish delay 0, max refill rate 100)p50 92 ms, p90 182 ms, p99 376 ms at 45 claims per second; per-pool refill ceiling about 70 to 85 sandboxes per secondPoisson arrivals across 4 pools, 113 replicas per namespace, kops
Sustained, burst-tuned (replenish delay 20 s, no rate cap)About 57 ms for the first 10 s, then about 1.4 s cold; steady-state p90 of 2.28 sSame 45 per second test as the row above
Real pod cold startAbout 42 to 50 s p50 on that cluster, which is why the guide treats warm adoption as the point of the designGKE live test, Kubernetes 1.36.2, 20 e2-standard-16 workers, 75-claim burst from a 150-sandbox pool

Two caveats from the same guide change how you use it. First, a claim that sets spec.env or spec.volumeClaimTemplates disables warm adoption entirely and always cold-starts a fresh sandbox from the template, because those customisations cannot be applied to a running warm pod. Second, a pool whose spec.replicas is 0 simply cold-starts from its template. A warm pool you size well but claim with a custom env is a pool you pay for and never use.

Stateful lifecycle: what is shipped and what is only promised

The announcement says Agent Sandbox supports scaling idle environments to zero while ensuring they resume exactly where they left off. The roadmap is more precise, and the difference matters if you are planning around it. PVC-based suspend and resume is listed as completed: when scaled to 0, the PVC is persisted and attached again on resume. Automatic suspension of inactive sandboxes, and a separate Scale to Zero item, are listed as planned.

CapabilityRoadmap statusWhat it means for you
Suspend and resume (PVC-based)CompletedYou can pause a sandbox and keep its workspace volume
Auto suspend and resume on trafficPlannedSomething of yours must decide when a session is idle and call suspend
Auto-deletion of bursty sandboxes (TTL)PlannedGarbage collection of abandoned sandboxes is your job for now, besides the scheduled deletion the README lists as a lifecycle feature
Network policy attached at claim timePlannedEgress rules live in the template today, so one template per egress posture

The stable identity part is shipped and simple: every Sandbox gets a stable hostname and network identity so coordinated agents can find each other. The controller backs each Sandbox with a headless Service whose selector is a name-hash label, which is a detail that matters in the next section.

Where it bites: security defaults and tuning traps

The threat model is unusually frank about what is and is not covered, and the performance guide records a failed configuration rather than hiding it. I read both for the places a platform team would get hurt. The table lists five, each with the evidence in the project's own words.

TrapEvidenceWhat to do
Bare Sandbox keeps the service account tokenautomountServiceAccountToken defaults to false only when provisioned via a SandboxTemplate; for bare Sandbox CRDs the threat model says to use admission control such as ValidatingAdmissionPolicyCreate sandboxes only from templates, and add a policy that rejects the rest
Default-deny network blocks your sidecarManaged NetworkPolicy mode restricts ingress to the Sandbox Router and egress to the public Internet, blocking RFC1918 ranges and cloud metadata endpoints; it blocks sidecar ports by defaultAllow sidecar ports explicitly in the template or health checks will fail
The router's only SSRF guard is an IP-class checkThe default authorizer is AllowAll, so validating X-Sandbox-Pod-IP as a non-local IP literal is currently the only SSRF defence; the Go router has no connection limits or timeouts for upgraded connections such as WebSocketsConfigure a custom authorizer and rate-limit at the Gateway or Envoy tier
One big warm pool stalls refillReconciles are serialised per pool; the expectations gate waits for watch events, and if watch delivery lags 10 to 30 s a single pool freezes all replenishmentShard across pools above about 30 claims per second or 500 to 1,000 replicas
A replenish delay turned a blip into a drainIn the GKE test, one transient API timeout on claim creation (74 of 75 succeeded) let the pool drain because the delay blocked immediate refill, giving p90 of 204.7 s against 74.2 s for the sustained profileLeave the replenish delay off unless you can guarantee no claim-creation failures

The label spoofing issue deserves its own paragraph because it is the sort of bug a CRD like this invites. A tenant controls spec.podTemplate metadata, and the Service for Sandbox A selects pods by the agents.x-k8s.io/sandbox-name-hash label. If a malicious tenant copied that label value onto its own pod, traffic meant for Sandbox A could reach the attacker. The threat model documents the fix: the controller filters keys under agents.x-k8s.io and extensions.agents.x-k8s.io out of user templates and assigns the name-hash after merging user labels. It also lists strengthening the FNV-1a hash as tracked separately.

The threat model calls NetworkPolicy the primary defence-in-depth control for tenant isolation. A cluster whose CNI does not enforce NetworkPolicy gives you no cross-tenant network isolation from the managed policy, however correct the YAML looks. I could not find a statement in the sources about CNI requirements, so check yours before relying on it.

One more drift to keep in mind: the roadmap lists API-versus-runtime decoupling, rolling updates for templates and warm pools, and smarter warm pool selection as in progress. A KEP in the repository proposes reducing SandboxClaim to a single warmPoolRef field. I treat that as a proposal, not a shipped API, but it tells you to keep claim manifests small and generated from one place.

Tuning the controller before the pool becomes the bottleneck

The configuration guide has a high-throughput Deployment example, and the performance guide explains each flag. The summary I took away: defaults are safe for small deployments, and most flags only matter once you hit the API server concurrency ceiling. At 75 concurrent claims several of them were neutral. The one with a consistent effect was max refill rate, which cut p50 and p90 by about 15 percent versus the baseline in the GKE test, and rate 80 and rate 100 performed identically.

The snippet shows only flags I read in the two guides, with their documented reasons. Do not copy the numbers as a recommendation for your cluster: the guide ties them to its own profiles, and one of its worker sweeps showed that too few workers regress (50, 50, 1, 1 workers cost about 11 percent on p50 against 200, 150, 2, 1).

# Fragment of the agent-sandbox-controller container args.
# Every flag below is named in docs/configuration.md or docs/performance-tuning.md.
# The values are the guides' examples for their own profiles: measure before copying.
args:
- --leader-elect=true
- --extensions=true
# Keep write traffic from starving the watch events that clear a pool's expectations gate.
- --separate-watch-connection=true
- --api-connections=4
# Pace refill as a steady stream instead of 300-sandbox bursts (default rate is 0 = unpaced).
- --sandbox-warm-pool-max-refill-rate=100
- --sandbox-warm-pool-max-batch-size=200
# Parallelism is across distinct SandboxWarmPool objects, not within one pool.
- --sandbox-warm-pool-concurrent-workers=4
# Fewer redundant writes per lifecycle transition.
- --disable-sandbox-events=true
- --disable-claim-events=true
- --disable-claim-observability-annotations=true

# Wrong: --sandbox-warm-pool-replenish-delay=20s. In the GKE test one failed
# claim creation drained the pool and p90 went from 74.2 s to 204.7 s.

What to do on Monday

I would not put this in front of production traffic yet, and the announcement says as much by calling the project in development. But it is good enough to evaluate on a scratch cluster this week. In order:

  1. Install on a disposable cluster with the pinned release, and verify the CRD and the controller deployment with the two commands from the README.
  2. Get isolation working first: create the gVisor or Kata RuntimeClass, put runtimeClassName in a SandboxTemplate, and confirm the pod actually runs under it before anything else.
  3. Measure your own claim latency with a SandboxWarmPool of a few replicas against a replicas 0 pool built on the same template, so the warm-versus-cold difference is a number from your nodes.
  4. Test the failure modes: a tenant pod that tries to reach 169.254.169.254, a pod that tries to read a service account token, and a claim that sets spec.env.
  5. Write down which lifecycle pieces you must own yourself (idle detection, cleanup of abandoned sandboxes) because the roadmap lists them as planned.
# Does the claimed pod really run under the sandbox runtime?
kubectl get pod -n default -o custom-columns=NAME:.metadata.name,RUNTIME:.spec.runtimeClassName

# Warm versus cold on your own nodes: time a claim against each pool.
time kubectl apply -f claim-warm.yaml
kubectl get sandboxclaim session-4821 -o yaml   # read status.conditions for when it was bound

# Failure probes from inside a sandbox: metadata endpoint and API token.
kubectl exec <sandbox-pod> -- curl -m 3 -s http://169.254.169.254/ ; echo "exit=$?"
kubectl exec <sandbox-pod> -- ls /var/run/secrets/kubernetes.io/serviceaccount

The Python SDK is on PyPI as k8s-agent-sandbox, and the Go SDK ships from the repository's root module. Both are the client side. The roadmap lists expanding the Python SDK to read and write files and run commands as in progress, so expect that surface to move.

The rule I take from this: an agent sandbox is only as isolated as the RuntimeClass in the template, and only as fast as the warm pool you size and the claims that are allowed to use it. Agent Sandbox gives you the objects to enforce both in one place, and its own documentation is honest that several pieces, from auto-suspend to claim-time network policy, are still ahead of it.

FAQ

Frequently Asked Questions

Short answers to what readers ask most about this topic.

What is Kubernetes Agent Sandbox?

It is a SIG Apps project that adds a Sandbox custom resource and controller for isolated, stateful, singleton workloads such as AI agent runtimes. Each Sandbox manages one pod with a stable hostname, network identity and persistent storage. Opt-in extensions add SandboxTemplate, SandboxClaim and SandboxWarmPool.

Does Agent Sandbox isolate untrusted code by itself?

No. The project's threat model says it does not implement isolation itself but supports secure runtimes such as gVisor or Kata Containers through a RuntimeClass in the pod template. Platform teams can enforce the runtime by setting it in a SandboxTemplate used by a SandboxWarmPool.

How does a SandboxWarmPool avoid pod cold starts?

A SandboxWarmPool keeps a set of pre-provisioned sandbox pods ready from one SandboxTemplate. A SandboxClaim then adopts one instead of waiting for a new pod, which the announcement says adds about a second of overhead. Claims that set spec.env or spec.volumeClaimTemplates skip the pool and cold-start.

Can idle agent sandboxes scale to zero automatically?

Not yet automatically. The roadmap lists PVC-based suspend and resume as completed, so a scaled-down sandbox keeps its volume and can be resumed. Automatic suspension of inactive sandboxes and a separate Scale to Zero item are marked as planned, so you currently decide when a session is idle.

How do I install Agent Sandbox on a cluster?

Pin a release and apply its manifests with kubectl, for example sandbox-with-extensions.yaml, or sandbox.yaml followed by extensions.yaml. Then verify with kubectl get crd sandboxes.agents.x-k8s.io and the agent-sandbox-controller deployment in agent-sandbox-system. Check the release page for current asset names, since the March 2026 blog shows an older manifest.yaml.