github kubernetes-sigs/agent-sandbox v1.0.10

one hour ago

🚀 Announcing Agent Sandbox v1.0.10!

We're excited to announce the release of Agent Sandbox v1.0.10! Highlights: gRPC-Web transcoding in sandbox-router for browser clients, sandboxd unary execution limits and expanded file operations (mkdir, recursive list, create-only writes, move, copy, and tar extraction), registry.k8s.io image promotion for sandboxd, ConnectivityRouterPortForward for Kata and gVisor in the Go SDK, controller fixes for SandboxClaim mirroring and SandboxWarmPool replica accounting, and new examples for Hyperlight micro-VMs, GKE L4 GPU snapshots, OpenCode, and multi-tenant RL.

⚠️ Action Required

  • Router NetworkPolicy changes (#1550, #2095, #2144):
    • sandbox-router/deploy/networkpolicy.yaml (applied by kubectl apply -k sandbox-router/deploy/) now restricts sandbox egress to Pods carrying agents.x-k8s.io/sandbox-name-hash and allows all TCP ports to those Pods instead of only 8888 and 9090 (#1550). The router Helm chart (sandbox-router/helm/, version 0.1.2) already required that label and now restricts that rule to TCP (#2095).
    • Both sandbox-router/deploy/networkpolicy.yaml and sandbox-router/helm/ now allow egress on TCP 443 and 6443 (API server) and UDP/TCP 53 (DNS) to any destination by default (#1550, #2144), removing the default and kube-system namespace selectors that broke Pod-cache sync and TokenReview when the API server runs on host-network nodes (for example under Cilium). With the default --authz-mode=allow-all, callers can also target ports 443 and 6443 via X-Sandbox-Pod-IP. In the Helm chart, setting networkPolicy.apiServerCIDRs now replaces the port-only rule with only those CIDRs instead of adding them alongside the default namespace.
    • Action: If you route through sandbox-router to Pods without agents.x-k8s.io/sandbox-name-hash, deploy an additional NetworkPolicy permitting those targets. To restrict API server egress from 0.0.0.0/0 on 443 and 6443, set networkPolicy.apiServerCIDRs in Helm or add an ipBlock targeting your API server endpoint addresses (and ClusterIP on CNIs that evaluate policy before DNAT).
  • OLM bundle includes the router NetworkPolicy (#1550, #2141): The OLM bundle (olm/bundle/, updated to operator version 1.0.9 in #2141) now packages the sandbox-router NetworkPolicy. Upstream OLM added bundle support for NetworkPolicy in v0.32.0; earlier upstream OLM versions reject the InstallPlan (OpenShift OLM 4.12+ supports it).
    • Action: Upgrade upstream OLM to v0.32.0 or later before installing or upgrading from an OLM bundle at or after 1.0.9. Because OLM owns bundled manifests, deploy a second NetworkPolicy rather than editing the bundled one if you need to allow extra ingress or egress peers.
  • sandbox-router/server.Options.ProbeAddr removed (#2076): Options.ProbeAddr was removed from sigs.k8s.io/agent-sandbox/sandbox-router/server when probe serving moved to server.StartProbes.
    • Action: Go code that imports sigs.k8s.io/agent-sandbox/sandbox-router/server must call server.StartProbes instead of setting Options.ProbeAddr.

Highlights

Core & Extension Controllers

  • Bound SandboxClaims keep mirroring when their template is unavailable (#2168): Deleting or losing access to a referenced SandboxWarmPool or SandboxTemplate previously caused bound claims with additionalPodMetadata to clear their mirrored Finished condition (preventing ttlSecondsAfterFinished expiry) and stop updating podIPs and serviceFQDN. When the bound Sandbox already carries the claim's additionalPodMetadata, reconciliation now succeeds; when unapplied metadata or a template key conflict remains, Ready reports False while Finished, Name, podIPs, and serviceFQDN continue to mirror the Sandbox. Routine reconcile logs in SandboxClaimReconciler also moved from V(1) to V(4).
  • Warm-pool replica accounting on failed updates, deletes, and adoptions (#2158): SandboxWarmPool previously dropped sandboxes from capacity accounting when a launch-type label update, stale-member deletion, or orphan adoption failed (and ignored terminating orphans), causing reconcilePool to create extra sandboxes past spec.replicas. Pool-owned sandboxes with failed metadata updates or failed stale deletes now stay in active accounting, and failed adoptions and terminating or failed-delete orphans count toward inactive capacity so no replacements are created until they are gone.
  • PodTerminating reason on PodScheduled for terminating unscheduled Pods (#1761): When an unscheduled backing Pod (such as Unschedulable or SchedulingGated) has deletionTimestamp set, Sandbox now sets PodScheduled=False with reason PodTerminating instead of copying the scheduler's last reason. SandboxWarmPool exempts Unschedulable and SchedulingGated members from stuck-sandbox cleanup, so a terminating Pod wedged on a finalizer no longer holds a pool slot indefinitely past the readiness grace period.

Sandbox Router

  • gRPC-Web transcoding for browser clients (#2143): The Go sandbox-router now transcodes binary application/grpc-web and application/grpc-web+proto POST requests (HTTP/1.1 or HTTP/2) to native gRPC (h2c) upstream for unary and server-streaming RPCs, including sandboxd's ProcessService and FileWatcherService. It's always on:
    • Text-mode application/grpc-web-text and non-proto codecs are rejected with gRPC InvalidArgument.
    • Set --grpc-web-allowed-origins to a comma-separated list of explicit scheme://host[:port] origins (* is rejected; default allows same-origin only) to enable CORS headers and OPTIONS preflight handling.
    • --path-routing-prefix doesn't apply to gRPC-Web; callers must send X-Sandbox-ID and an explicit X-Sandbox-Port header. --grpc-proxy-timeout and Grpc-Timeout apply.
    • As with native gRPC, the router strips Authorization, so a token-protected sandboxd (--auth-token-*) can't be reached through it.
  • /healthz served before initial Pod cache sync (#2076, #2144): sandbox-router now starts its probe listener (:8081) before OTel initialization and the initial Pod cache sync (whose timeout increased from 60s to 90s). /healthz returns 200 for up to 120s during startup (then 503 if still not ready), while /readyz stays 503 until the proxy listeners are bound. This prevents kubelet liveness restarts on large clusters, and #2144 removes the redundant startupProbe from sandbox-router/helm/ (chart 0.1.2).
  • Optional controller NetworkPolicy (#1550): New k8s/hardening/networkpolicy.yaml restricts agent-sandbox-controller ingress to metrics (8080) and health probes (8081), and egress to DNS (53) and the API server (443, 6443). It isn't included in sandbox.yaml or sandbox-with-extensions.yaml.

sandboxd

  • Unary execution limits and bounded output (#2117): New gRPC RPC process.v1.ProcessService/ExecuteWithLimits runs a unary command with an optional time_limit (independent of the RPC deadline, killing the process tree on expiry and reporting EXECUTE_TERMINATION_REASON_TIME_LIMIT_EXCEEDED and elapsed_duration) and independent stdout_limit_bytes and stderr_limit_bytes prefix caps (stdout_truncated, stderr_truncated).
    • Six daemon flags (--execute-default-time-limit, --execute-max-time-limit, --execute-default-stdout-limit-bytes, --execute-max-stdout-limit-bytes, --execute-default-stderr-limit-bytes, --execute-max-stderr-limit-bytes; all 0 / unlimited by default) set deployment defaults and maximums.
    • When those daemon flags are set, existing ProcessService/Execute also enforces them and returns RESOURCE_EXHAUSTED if the time limit is exceeded or output is truncated.
    • Older sandboxd versions return UNIMPLEMENTED for ExecuteWithLimits. The Go, Python, and TypeScript SDKs don't call ExecuteWithLimits yet.
  • Filesystem REST operations (#2097, #2101, #2112, #2118, #2130):
    • Directory creation (#2097): PUT /v1/files/{path}?type=directory creates a directory (default mode=0755, idempotent 204 if it already exists, 409 if a file exists). Missing parent directories return 404 unless parents=true is set. PUT ?type=file on an existing directory also now returns 409.
    • Recursive directory listing (#2101): GET /v1/files/{path}?recursive=true with optional max_depth=N (>= 1). Entry names are /-separated paths relative to {path}. Symlinked directories are listed without descending into them, and multi-level listings cap at 10,000 entries (400).
    • Create-only file writes (#2112): PUT /v1/files/{path} with If-None-Match: * atomically creates the file via link(2) and returns 412 (FAILED_PRECONDITION) if {path} already exists. Requires hard-link support on the backing filesystem (not supported on GCS FUSE).
    • Move and copy (#2118): POST /v1/files/{path}:move?destination={dest} and :copy?destination={dest} move or copy files, symlinks, and directory trees, creating destination parent directories automatically and staging cross-filesystem moves. Existing destinations return 409 unless overwrite=true replaces a non-directory with a non-directory.
    • Tar extraction on upload (#2130): PUT /v1/files/{path}?extract=tar unpacks a plain .tar or gzip-compressed .tar.gz stream into {path} (created if missing), confining entry paths to {path} (403 on escape) and capping archives at 100,000 entries or 1 GiB of file data (413). Extraction isn't atomic across the whole archive.
    • Version skew: sandboxd v1.0.9 and earlier ignore unknown query parameters and If-None-Match on PUT and GET: against <= v1.0.9, ?type=directory and ?extract=tar write a regular file with 204, If-None-Match: * overwrites an existing file, and ?recursive=true returns a flat listing (POST :move and :copy return 405). Upgrade sandboxd to v1.0.10 before calling these endpoints.
  • sandboxd image promoted to registry.k8s.io (#2139): Releases now promote registry.k8s.io/agent-sandbox/sandboxd:v1.0.10 (linux/amd64 and linux/arm64).

SDKs

  • Go SDK:
    • New ConnectivityRouterPortForward ("router-port-forward") and Options.RouterNamespace reach RuntimeSandboxd sandboxes through a port-forward to sandbox-router-svc, supporting both REST and gRPC (Run, Commands().Start, WatchDir) (#2003, #2145). Use this from a laptop or CI runner for Kata and gVisor sandboxes, where Pod port-forward can't reach the guest network stack. gRPC calls require sandbox-router v1.0.9 or later, and sandboxd bearer-token auth (--auth-token-*) isn't supported because the router strips Authorization.
    • New Mkdir(ctx, path, parents, opts...), Move(ctx, src, dst, overwrite, opts...), and Copy(ctx, src, dst, overwrite, opts...) on *Sandbox and *Files (#2156). Move and Copy default to 1 attempt (WithMaxAttempts opts into retries). Requires sandboxd v1.0.10+.
  • TypeScript SDK:
    • New sandbox.files.mkdir(path, { parents, mode }), move(src, dst, { overwrite }), and copy(src, dst, { overwrite }) (#2140).
    • New append and createOnly options on sandbox.files.write() and sandbox.files.writeStream() (#2151); createOnly throws SandboxdApiError (status: 412, code: "FAILED_PRECONDITION") if the target exists.
    • New recursive and maxDepth options on sandbox.files.list(path, { recursive: true, maxDepth }) (#2167).
    • Requires sandboxd v1.0.10+ (append requires v1.0.9+).
  • Python SDK:
    • Broke the Sandbox and AsyncSandbox handle-to-connector reference cycle on close_connection(), freeing closed handles without waiting for cyclic GC (#2086).
    • Stopped atexit cleanup registration from retaining closed SandboxClient and AsyncSandboxClient instances until interpreter exit (#2087).
    • AsyncSandboxConnector now initializes httpx.AsyncClient lazily on first HTTP use, and AsyncSandboxClient shares one cached default ssl.SSLContext across handles instead of reloading the CA bundle per handle (#2132).
    • Fixed sync kubectl port-forward tunnels (LocalTunnelConnectionStrategy and SandboxdPodTunnelStrategy) stalling after ~250 reset connections filled kubectl's unread stderr pipe (#2106).
    • Widened kubernetes_asyncio support from <34.0.0 to <37.0.0 (#2071), and raised the tracing extra's OpenTelemetry upper bound to <1.46 so pip install "k8s-agent-sandbox[tracing,grpc]" resolves cleanly with protobuf>=7 (#2099).

Reinforcement Learning & Examples

  • Multi-tenant RL over shared node image caches (#2165): New examples/agent-sandbox-rl/examples/multi_tenant/ adds per-user rl-<user> namespace RBAC, ResourceQuota, LimitRange, and NetworkPolicy provisioning, shared node-cache pre-pull (admin/prepull_shared.py), quota-fitting runner CLIs (run_experiment.py, run_many.py), and isolation verification (verify.sh). Also updated examples/agent-sandbox-rl/CHANGELOG.md (#2038, #2114).
  • Hyperlight micro-VM sandboxes (#2119): New examples/hyperlight-sandbox runs a stdlib Python runtime speaking the python-runtime-sandbox HTTP API inside a Hyperlight micro-VM (hyperlight-unikraft with a Unikraft unikernel guest) in an unprivileged FROM scratch container via the hyperlight.dev/hypervisor CDI device plugin, supporting both initrd cold boot and sub-200ms VM snapshot restore (Containerfile.snapshot).
  • L4 GPU Pod Snapshots on GKE (#2147): New examples/gpu-l4-snapshot-sandbox checkpoints and restores an NVIDIA L4 GPU gVisor Sandbox on GKE using GKE Pod Snapshots (podsnapshot.gke.io, which uses cuda-checkpoint under the hood), releasing the GPU while Suspended and restoring host and GPU memory on resume.
  • OpenCode sandbox (#2100): New examples/opencode-sandbox runs the OpenCode coding agent inside a core Sandbox (via kubectl exec) or a SandboxWarmPool driven by the Python SDK.
  • Terminal-Bench 2.0 with Harbor (#2126): New examples/harbor-terminal-bench adds Harbor configs, RBAC, image mirroring, and result reporting for running Terminal-Bench 2.0 on SandboxClaims (requires the clients/integrations/harbor environment adapter in #2125).
  • Example updates: Made the jupyter-hf-token Secret optional and init-container-only in examples/jupyterlab so public models need no Secret and notebook kernels don't inherit HF_TOKEN (#2160); added Cloud Hypervisor (kata-clh) setup instructions to examples/kata-gke-sandbox (#2134); updated examples/recall-memory to recall 0.24.0 and v1.0.9 (#2148).

Documentation, CI & Tooling

  • Kubernetes version requirements (#1892): Documented minimum (1.25+, with CustomResourceValidationExpressions enabled on 1.25–1.28), recommended (1.29+, where CRD CEL validation is GA), and Gateway API v1.5.0 bundle (1.31+) Kubernetes versions.
  • sandboxd routing docs (#2113): Updated packages/sandboxd/USER_GUIDE.md and SDK comments to reflect native gRPC proxying through sandbox-router.
  • CI and release tooling: Fixed a duplicate Dockerfile directory name in push-images and added a presubmit uniqueness test (#2161), added go install retries in kOps CI lanes (#2121), and excluded bot accounts from generated contributor lists (#2115).

Installation

Standard Install (Core + Extensions)

Recommended for most users and GitOps engines (Argo CD, Config Sync, kustomize):

kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/download/v1.0.10/sandbox-with-extensions.yaml

Selective Install

Install components separately:

# Core only:
kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/download/v1.0.10/sandbox.yaml

# Extensions (opt-in):
kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/download/v1.0.10/extensions.yaml

Python SDK

pip install k8s-agent-sandbox==1.0.10

Contributors

We extend our sincere thanks to all contributors to this release:
@D-Joey-G, @HasonoCell, @LucasGois1, @RyoKusnadi, @Sean-790761, @abhayjoshi201, @aditya-shantanu, @alanhuangch, @anuptalwalkar, @briankhoi, @cloudonly, @danbugs, @dongjiang1989, @ericcurtin, @esposem, @fponsse, @janetkuo, @lilong555, @lunarwhite, @nsknojj, @pbxqdown, @rkb32, @tomergee

New Contributors

Full Changelog: v1.0.9...v1.0.10

Don't miss a new agent-sandbox release

NewReleases is sending notifications on new releases.