> ## Documentation index
> Fetch the complete documentation index at: https://docs.katakate.org/llms.txt
> Ask it with GET https://docs.katakate.org/ask?q=<keywords or question>. Short keywords rank best.
> Use these to discover all available pages before exploring further.

# agent-sandbox on k7d

> Run kubernetes-sigs/agent-sandbox Sandbox objects, warm pools and claims as k7d microVMs on runtimeClassName k7 or k7-fc

[kubernetes-sigs/agent-sandbox](https://github.com/kubernetes-sigs/agent-sandbox) runs isolated, stateful agent workloads as `Sandbox` objects, with warm pools (`SandboxWarmPool`) and claims (`SandboxClaim`) on top. It picks its isolation through the pod's `runtimeClassName`, and k7d plugs in there **with no upstream change**: every Sandbox pod on `k7` or `k7-fc` is its own k7d microVM — the native in-process VMM, or stock Firecracker under the jailer ([Backends](/k7d/concepts/backends)).

Tested with agent-sandbox **v1.0.5** (every image and manifest digest-pinned in [`utils/agent-sandbox/pins.env`](https://github.com/katakate/k7d/blob/main/utils/agent-sandbox/pins.env)) on k7d `main` after 0.9.0 — the integration, and the `kubectl port-forward` support it needs, landed after the 0.9.0 tarball.

## Node prerequisites

1. **k3s, pinned**: `sudo utils/install-k3s.sh` (k3s `v1.36.5+k3s1`), or your own cluster.
2. **k7d, the shim and RuntimeClasses `k7` and `k7-fc`**: `sudo ./packaging/install.sh --with-k3s` from the release tarball ([Installation](/k7d/getting-started/installation)). On a [K7](/k7/index) cluster, `k7 install --backend k7d,k7d-fc` does the same.
3. **agent-sandbox, pinned and sha256-checked**: `utils/agent-sandbox/install.sh` installs the controller, CRDs, extensions and the Go `sandbox-router`; `utils/agent-sandbox/uninstall.sh` removes all of it.

Then run the demo:

```bash
KUBECONFIG=/etc/rancher/k3s/k3s.yaml examples/agent-sandbox/demo.sh
```

It creates a `k7-fc` warm pool and a claim, drives the upstream Python SDK through the router, and runs `docker run hello-world` inside a `k7` sandbox.

## Examples

All in [`examples/agent-sandbox/`](https://github.com/katakate/k7d/tree/main/examples/agent-sandbox). Every image is digest-pinned, and **set CPU and memory limits** — k7d sizes the VM from the container limits.

| File | What |
|---|---|
| `sandbox-k7.yaml` | Bare `Sandbox` on `k7`: python-runtime, `service: true`, httpGet readiness probe |
| `template-warmpool-claim-k7-fc.yaml` | `SandboxTemplate` (managed NetworkPolicy) + `SandboxWarmPool` (3) + `SandboxClaim` on `k7-fc` |
| `template-docker-k7.yaml` | Docker-in-VM: in-guest `dockerd` via annotations, no privileged container |
| `sdk_smoke.py` | Upstream SDK (`k8s-agent-sandbox==1.0.5.post1`, hash-pinned): claim, `run`, file write / read |

A bare Sandbox is an ordinary pod template with the RuntimeClass set:

```yaml
apiVersion: agents.x-k8s.io/v1beta1
kind: Sandbox
metadata:
  name: python-k7
spec:
  service: true
  podTemplate:
    spec:
      runtimeClassName: k7            # or k7-fc
      automountServiceAccountToken: false
      containers:
      - name: python-runtime
        image: registry.k8s.io/agent-sandbox/python-runtime-sandbox:v0.1.0@sha256:…
        ports: [{containerPort: 8888, name: http}]
        readinessProbe: {httpGet: {path: /, port: 8888}, periodSeconds: 5, timeoutSeconds: 5}
        resources:
          limits: {cpu: "1", memory: 1Gi}
          requests: {cpu: 250m, memory: 1Gi}
```

```bash
kubectl apply -f examples/agent-sandbox/sandbox-k7.yaml
kubectl wait sandbox/python-k7 --for=condition=Ready --timeout=180s
```

A warm pool is a `SandboxTemplate` plus a `SandboxWarmPool` with `replicas`; a `SandboxClaim` adopts a booted VM from the pool without a restart (`restartCount 0`). `networkPolicyManagement: Managed` on the template gives the upstream default policy — ingress only from the `sandbox-router`, egress to public IPs only — and it is enforced on the pod IP of a k7d VM exactly as it would be for runc.

**Docker-in-VM** is two pod annotations on the template's `podTemplate`; the workload keeps `capabilities: {drop: [ALL]}` and `dockerd` runs in the guest outside the container ([Security — Docker as a guest service](/k7d/concepts/security)):

```yaml
metadata:
  annotations:
    k7d.katakate.org/docker: "true"        # dockerd in the guest, socket at /var/run/docker.sock
    k7d.katakate.org/docker-disk: "20Gi"   # ext4 graph disk, so overlay2 is not on tmpfs
```

## Support matrix

From `crates/k7d/tests/test_agent_sandbox.rs`, single node, k3s `v1.36.5+k3s1` (flannel, kube-router NetworkPolicy), 2026-10-07.

| # | agent-sandbox feature | `k7` | `k7-fc` |
|---|---|---|---|
| V1 | Cold `Sandbox` → Ready, `podIPs`, `serviceFQDN` | ✅ | ✅ |
| V2 | Python SDK through `sandbox-router` (`run`, files) | ✅ | ✅ |
| V3 | `kubectl port-forward` (SDK tunnel mode) | ✅ | ✅ |
| V5 | Template + WarmPool(3) + Claim; adoption keeps the VM, `restartCount 0` | ✅ | ✅ |
| V6 | Managed NetworkPolicy: public egress, private / link-local blocked, ingress only from the router | ✅ same as runc | ✅ same as runc |
| V7 | `operatingMode: Suspended` → `Running`: pod and VM gone, cold boot on resume | ✅ | ✅ |
| V7 | `volumeClaimTemplates` PVC data kept across suspend | ✅ (local-path via virtiofs) | ❌ refused loudly: "backend firecracker has no virtiofs" |
| V8 | `shutdownTime` + `shutdownPolicy: Delete` | ✅ | ✅ |
| V9 | httpGet readiness, exec liveness, startup probe under a burst of 4 | ✅ | ✅ |
| V10 | Projected SA token rotation reaches the guest | ❌ gap (below) | ❌ gap |
| V11 | Docker-in-VM: overlay2 on ext4, `hello-world`, not privileged, `CapEff` 0 | ✅ | ✅ |
| V13 | Out-of-band `pause_vm`: Ready flips False via the probe, back after resume | ✅ | ✅ |
| V14 | Upstream e2e `TestRuntimeClassStartupComparison`, `TestRuntimeClassBurstRecovery` | ✅ | ✅ |
| V14 | Upstream e2e `TestRuntimeClassLifecycle` | ⚠️ 1 of 3 runs: upstream test race | ✅ |

V4 (`sandboxd` + Go SDK) is not run: v1.0.5 publishes no `sandboxd` image. Warm-forking a Sandbox (V12) is the next increment; today a fork of a pod VM goes through the [daemon API](/k7d/api/protocol) (`fork_vm` with `pod_netns`) or `k7 fork` on a K7 cluster.

### Timings

Intel Core Ultra 7 265 (20 threads), 62 GB, NVMe, Hetzner, single node, 2026-10-07, images already pulled:

| Path | `k7` | `k7-fc` | runc |
|---|---|---|---|
| Cold `Sandbox` → Ready, upstream `TestRuntimeClassStartupComparison` (no probe) | 0.84 s | 0.93 s | — |
| Cold `Sandbox` → Ready, `sandbox-k7.yaml` (httpGet readiness, 5 s period) | 0.96–1.90 s | 5.91–7.17 s | — |
| Warm `SandboxClaim` → Ready (pool of 3) | 0.05 s | 0.05 s | 0.05–0.06 s |
| Warm claim, upstream StartupComparison | 0.011 s | 0.011 s | — |

A warm claim adopts a running pool VM, so it costs the same as runc. With a readiness probe, cold start moves in steps of the probe period: `k7-fc` boots in about 1 s but misses the first probe.

Upstream `TestRuntimeClassBurstRecovery` with `SANDBOX_POOL_SIZES=10,20,40` (two claims per pool slot, in batches):

| Pool | Backend | Pool fill | Warm / grey / cold claims | Worst start | All ready | Claims/s |
|---|---|---|---|---|---|---|
| 10 | `k7` | 1.52 s | 19 / 1 / 0 of 20 | 1.08 s | 1.67 s | 12.0 |
| 20 | `k7` | 2.97 s | 30 / 1 / 9 of 40 | 3.54 s | 4.07 s | 9.8 |
| 40 | `k7` | 5.02 s | 70 / 1 / 9 of 80 | 6.21 s | 7.33 s | 10.9 |
| 10 | `k7-fc` | 2.37 s | 20 / 0 / 0 of 20 | 0.94 s | 1.53 s | 13.0 |
| 20 | `k7-fc` | 2.62 s | 30 / 9 / 1 of 40 | 2.85 s | 3.36 s | 11.9 |
| 40 | `k7-fc` | 5.12 s | 70 / 2 / 8 of 80 | 5.75 s | 6.89 s | 11.6 |

## Known gaps

- **Kubelet volume updates do not reach the guest.** ConfigMap, Secret, downwardAPI and projected volumes are packed into one read-only erofs image when the pod is created. kubelet rotates projected tokens and updates ConfigMaps on the host; the guest keeps the first copy. Use `automountServiceAccountToken: false` (the examples do) or tokens whose lifetime covers the sandbox. The default `kube-api-access` token stays valid for a year on clusters with `--service-account-extend-token-expiration` (the default), but it is never refreshed.
- **`k7-fc` has no virtiofs.** `hostPath` and local-path PVCs fail Create naming the backend. Use `k7` for `volumeClaimTemplates` on local-path storage.

## Related pages

- [RuntimeClass k7](/k7d/guides/runtime-class) — how the runtime is registered, annotation reference
- [Feature matrix](/k7d/guides/feature-matrix) — everything else the host RuntimeClass supports
- [Backends](/k7d/concepts/backends) — choosing `k7` vs `k7-fc`
