🚀 Announcing Agent Sandbox v1.0.9!
We're excited to announce the release of Agent Sandbox v1.0.9! Highlights: native gRPC proxying in sandbox-router (including sandboxd's ProcessService), a Helm chart for the router, working WebSockets through the router again (broken since v1.0.7), directory watching in sandboxd, fixes for warm-pool adoption and suspend in the controllers, streaming command execution in the Python SDK, and fail-fast, liveness, and pre-pull improvements in examples/agent-sandbox-rl.
⚠️ Action Required
- Router NetworkPolicy now allows egress to TCP 9090 (#1880): The NetworkPolicy in
sandbox-router/deploy/(applied bykubectl apply -k sandbox-router/deploy/) now allows egress on TCP 9090 as well as 8888, so the router can reachsandboxd's gRPC port. The rule usesnamespaceSelector: {}, so it covers every Pod in every namespace. With the default--authz-mode=allow-all, any client that can reach the router can target port 9090 on any Pod IP throughX-Sandbox-Pod-IP.- Action: If you don't proxy gRPC through the router, remove the 9090 entry before applying. Otherwise, limit the rule to sandbox Pods with a
podSelectoronagents.x-k8s.io/sandbox-name-hash(Exists), as the new router Helm chart does. OLM installs don't include this NetworkPolicy.
- Action: If you don't proxy gRPC through the router, remove the 9090 entry before applying. Otherwise, limit the rule to sandbox Pods with a
- Go label constants moved (#1878):
SandboxNameHashLabelmoved fromsigs.k8s.io/agent-sandbox/controllerstosigs.k8s.io/agent-sandbox/api/v1beta1.PodSandboxNameHashLabelandPodWarmPoolLabelwere removed fromsigs.k8s.io/agent-sandbox/sandbox-router/cache. Label values are unchanged.- Action: Go code that imports these constants must switch to
v1beta1.SandboxNameHashLabelandv1beta1.SandboxWarmPoolLabel.
- Action: Go code that imports these constants must switch to
Highlights
Core Controller
volumeClaimTemplatesvalidated at admission (#1886):Sandbox,SandboxTemplate, andSandboxClaimnow rejectvolumeClaimTemplatesentries with a missing or emptymetadata.name, duplicate names, or names longer than 63 characters, as well as lists of more than 128 entries. Apart from the 128-entry cap, these manifests were accepted before but never produced a running Pod: the claim reportedReady=False(VolumeClaimTemplatesError), or PVC or Pod creation failed. Existing objects aren't re-validated. Helm doesn't upgrade CRDs, so Helm users must runkubectl apply -f helm/crds/to get the new rules.- Claims no longer stuck at
Ready=Falseafter warm adoption (#1944): During bursts of concurrent claims, aSandboxClaimcould stayReady=Falseafter adopting a Ready warm Sandbox. Its status write hit a 409 conflict and was dropped, and nothing triggered another reconcile, so SDK readiness waits timed out. On a conflict, the controller now re-reads the claim from the API server and retries the status write once. It also reconciles a claim when the owner references of a Sandbox it owns change, such as the handoff from theSandboxWarmPoolto the claim. - No second Sandbox after warm adoption (#2010): A
SandboxClaimreconciled from a stale cache could cold-create a second Sandbox after it had already adopted a warm one, and it kept both until the claim was deleted. Before a cold create, the controller now re-reads the claim from the API server and skips the create if a Sandbox is already assigned. - Suspend no longer reports
Finished=True(#2079): Suspend deletes the Pod, and since Kubernetes 1.27 the kubelet marks a deleted PodFailedorSucceededbefore removing it. The controller reported that asFinished=Trueuntil the Pod was gone.SandboxClaimmirrorsFinishedto startttlSecondsAfterFinished, so suspending a claimed Sandbox with a very small TTL (such as0) could expire the claim and delete the Sandbox and its PVCs. Whilespec.operatingModeisSuspended,Finishedis no longer derived from the Pod phase. - Claim name on Pods (#1974, #1997): Pods of Sandboxes owned by a
SandboxClaimnow carry theagents.x-k8s.io/claim-nameannotation with the claim's full name, next to the existingagents.x-k8s.io/claim-uidlabel. A service that holds a Pod can find its claim without reading the Sandbox's owner references. Warm-pool Pods get the annotation shortly after adoption. It's informational, not proof of identity. See SandboxClaim identity on Pods. - Controller flags from a ConfigMap (#1301): The controller now reads 19 allowlisted tuning flags (API client, worker concurrency, warm-pool, event, and write-behind settings) from the optional, user-created
agent-sandbox-configConfigMap in its namespace. This is for installs such as OLM, where you can't edit the Deployment's args. Command-line flags take precedence, unknown keys are logged and ignored, and an invalid value stops the controller from starting. The ConfigMap is read once at startup, so restart the controller after changing it. The core-onlysandbox.yamldoesn't mount it, so it has no effect there. See Controller flag overrides. - Sandbox lifecycle metrics (#1775): New metrics, all labeled by
namespace,sandbox_template, andowned_by:- the
agent_sandbox_suspend_totalandagent_sandbox_resume_totalcounters (byresult) - the
agent_sandbox_suspend_latency_msandagent_sandbox_resume_latency_mshistograms agent_sandbox_finished_total(byreason)agent_sandbox_expired_total(byshutdown_policy), which counts Sandboxes that reachspec.shutdownTime.SandboxClaimlifecycle expiry isn't counted.
- the
Sandbox Router
-
Native gRPC proxying (#1880): The Go
sandbox-routernow proxies gRPC to sandbox services, unary and streaming, includingsandboxd'sProcessService. It's always on:- Port 8080 now also accepts cleartext HTTP/2 (h2c), and gRPC also works over the optional TLS listener. The router dials sandboxes over h2c.
- Clients send the usual routing headers as gRPC metadata, with an explicit
x-sandbox-port. There's no 8888 default for gRPC;sandboxduses 9090. Each RPC is authorized separately. - The new
--grpc-proxy-timeout(default0s, no limit) caps RPC duration.--proxy-timeoutdoesn't apply to gRPC. --path-routing-prefixand gRPC-Web aren't supported.- The router strips
authorization, so requests through it can't authenticate to asandboxdstarted with--auth-token-fileor--auth-token-sha256. - The SDKs still connect to
sandboxdby port-forward or Pod IP.
See
examples/sandbox-router-grpc/, and the NetworkPolicy change under Action Required. -
WebSockets through the router work again (#2065): Since v1.0.7, every WebSocket or other
Upgraderequest through the Gosandbox-router(code-server, for example) got a 502 instead of a 101. The byte counters added in v1.0.7 (#1502) wrapped the upgraded connection in a read-only body, so the proxy rejected it. The router always enables metrics, so there was no workaround. Upgraded connections now also count towardsandbox_router_upstream_rx_bytes_totalandsandbox_router_upstream_tx_bytes_total. -
Helm chart for the router (#2044): New chart in
sandbox-router/helm/(chart version 0.1.0). It renders the same resources, with the same names, assandbox-router/deploy/.- It isn't published to a chart repository. Install it from a checkout of the release tag with
helm install sandbox-router ./sandbox-router/helm/ --namespace agent-sandbox-system --create-namespace --set image.tag=v1.0.9(image.tagis required). - Values cover TLS (
tls.enabled),authz.mode(tokenreviewbindssystem:auth-delegator), a namespace-scoped Pod cache (router.cacheNamespace), and NetworkPolicy peers. - Its NetworkPolicy allows egress only to sandbox Pods, on any port.
- Helm won't adopt resources created by
kubectl apply -k sandbox-router/deploy/, so delete those first.
- It isn't published to a chart repository. Install it from a checkout of the release tag with
sandboxd
- Directory watching (#1767): New server-streaming RPC
watcher.v1.FileWatcherService/WatchDiron the gRPC port (9090). It streamsCREATE,WRITE,REMOVE,RENAME,CHMOD, andERRORevents for a directory, optionally recursively, in batches of about 250 ms.- A
RENAMEevent carries the old path. The new path arrives as aCREATEif it lands inside the watched tree. - If the watched directory is removed or renamed, the stream sends a final
ERRORand closes. - Each
sandboxdallows 64 concurrent watches. - Go SDK:
Sandbox.WatchDir()andWatchers().WatchDir(). Python SDK:sandbox.watcher.watch(), sync and async (requires thegrpcextra). - Older
sandboxdversions returnUNIMPLEMENTED.
- A
- Append mode for
PUT /v1/files/{path}(#2077): With?append=true, the request body is appended to the file instead of replacing it. The file and its parent directories are created if missing, andmodeapplies only when the append creates the file. Appends aren't atomic, and a retried append adds the body again. Oldersandboxdversions ignoreappendand overwrite the file. The SDKs don't expose it yet. - Expected-kind check for
GETandHEAD /v1/files/{path}(#2088): Withtype=fileortype=directory, a path that exists but is the other kind returns409(CONFLICT) instead of the file bytes or directory listing. Any othertypevalue returns400. Oldersandboxdversions ignoretype. The SDKs don't send it yet.
SDKs
- Python SDK:
- New
sandbox.commands.start()streams a command's output (#2054). It returns aProcessHandle(AsyncProcessHandleon the async client). The handle'sevents()yieldsProcessOutputEventchunks and then aProcessExitEvent, andsignal()sendsSIGINT,SIGTERM, orSIGKILL. Requires thegrpcextra andSandboxdPodTunnelConnectionConfigorSandboxdInClusterConnectionConfig. - New
sandbox.health()andsandbox.metadata()returnSandboxHealthandSandboxMetadatafromsandboxd(#1722). - New optional
auth_tokenonSandboxdPodTunnelConnectionConfigandSandboxdInClusterConnectionConfig, for asandboxdstarted with--auth-token-sha256or--auth-token-file(#2024). The sync and async clients send it on REST requests and gRPC calls. - New
SandboxFileConfig(max_download_size=...), passed asfile_config=toSandboxClientorAsyncSandboxClient, capsfiles.read()andfiles.read_to()(#2041). The default,None, keeps downloads unlimited. Oversized downloads raise the newSandboxResponseTooLargeError. - Fixed a memory leak: long-lived
SandboxClientandAsyncSandboxClientinstances kept a reference to every Sandbox closed withsandbox.terminate()(#2085). client.k8s_helper.wait_for_claim_ready()accepts opt-inrequire_current_generation=True, which ignores aReadycondition from before a claim spec update (#1872).
- New
- Go SDK (#2007): With
RuntimeSandboxdover the default pod port-forward,RunandCommands().Startnow returnErrNotReady(wrappingErrPortForwardDied) after the tunnel dies, instead of a gRPCUnavailableerror. The README's port-forward recovery recipe can then reconnect withOpen(). - TypeScript SDK (#2062): New
SandboxClient.getSandboxByName()attaches to a coreSandboxwithout aSandboxClaim. It waits for Ready unless you passwaitForReady: false. The client doesn't track or delete these Sandboxes. - MCP server (#2040): New connection type
sandboxd-in-cluster(K8S_SANDBOX_CONNECTION__TYPE) lets an in-cluster MCP server usesandboxd-based Sandboxes without the router or port-forwarding. SetK8S_SANDBOX_CONNECTION__MODEtoin-cluster-serviceorin-cluster-pod-ip. The template's NetworkPolicy must allow the MCP server on ports 8080 and 9090.
Reinforcement Learning (examples/agent-sandbox-rl)
- Fail fast on claims that can't start (#1791):
SandboxFleet.acquire()now watches the claimed Pod and is on by default.- On
InvalidImageName,ErrImageNeverPull, an OOM-killed container, or a Pod in phaseFailedorSucceeded, it deletes the claim and raisesSandboxStartErrorinstead of waiting outready_timeout(900s). - Retryable reasons such as
ImagePullBackOff,CreateContainerConfigError, andCrashLoopBackOfffail only afterFailFastPolicy.grace_s, andUnschedulableonly afterunschedulable_grace_s. Both are unset by default. - To turn it off, pass
FleetConfig(fail_fast=FailFastPolicy(enabled=False)).
- On
- Liveness and cleanup (#1791, #1966):
SandboxHandle.is_alive()detects a deleted, finished, or replaced Pod, andexec()raisesSandboxLostErrorwhen the Pod is gone.reap_orphans(alive_run_ids)(CLI:--orphans --alive-run-ids) cleans up runs that aren't listed. It supports--min-age-s(default 300) and--dry-run.teardown(verify=True)andverify_teardown()run the run-scoped reaper afterwards and report what's left (#1966).
- External training loops (#1963):
LookaheadWarmerkeeps warm pools sized for the batches a trainer has in flight.add_tasks()extends a fleet's task plan in place, andwait_pools_ready()waits up totimeoutwithout raising. All three work withAsyncSandboxFleet. - Pod templates (#1961):
- New
ResourceSpec.cpu_limitandmemory_limit. - An
extra_pod_speccontainer with image{task_image}runs the task image, for example as a grading sidecar. TemplateSpec.extra_template_specaddsSandboxTemplatespec fields.- Rendered Pods now set
automountServiceAccountToken: falseandshareProcessNamespace: falseexplicitly. These were already the effective defaults.
- New
- Multi-container Pods (#1960):
exec(container=),open_session(container=), andSandboxHandle.target_containerchoose the container. The default is the runtime container. - OpenHands (#1962):
adapters.openhands.agent_server_template()returns aTemplateSpecfor OpenHands agent-server pools. The server comes from the task image or is copied in fromserver_image=. Pods get/healthstartup and readiness probes, and a session key is required unlessallow_unauthenticated=True. - Task sources (#1964):
load_tasks,JsonlSource, andto_tasksread R2E-Gym and SWE-bench rows; override the field names withimage_field=andid_field=.load_image_listandImageListSourceread image manifests from local paths orgs://. - Pre-pull (#1965): Images are pre-pulled by DaemonSets of
prepull_images_per_podimages each (default 25), at mostprepull_max_parallelat a time (default 4), using the template's tolerations and runtime class.- New optional
prepull_delete_when_doneandprepull_drop_caches. - Fixed a second run's pre-pull failing on the DaemonSet selector.
- New optional
- Testing (#1967):
agent_sandbox_rl.testing.FakeFleetandFakeHandleprovide an in-memory fleet for unit-testing training loops without a cluster. - Client setup (#2035): Fixed concurrent first use building more than one
SandboxClient. Each cluster now builds one client from its ownApiClient. The package now requiresk8s-agent-sandbox>=1.0.6.
Examples
- DeerFlow (#2039): New
examples/deer-flow-sandboximplements DeerFlow's sandbox provider on the Python SDK andsandboxd, so DeerFlow tool calls run in Sandbox Pods. - Deep Agents (#2064): New
examples/deepagentsruns a LangChain Deep Agent whose file and shell tools run in a Sandbox, using theclients/integrations/deepagentsbackend. - Agent Client Protocol (#2017):
examples/agentclientprotocolaccepts-addr kubeservice://<context>/<namespace>/<service>[:<port>]. It port-forwards to the Service through the API server in-process, with nokubectlor local port. sandboxed-tools(#2019): Sessions now record their toolset, parent session, and creation time.- VS Code (#2069):
examples/vscode-sandboxdocuments attaching desktop VS Code to the sandbox Pod.
Testing, CI & Tooling
- Kata Containers (#2023, #2031): New
test-e2e-kata-gcpperiodic script runs theTestRuntimeClass*E2E tests withkata-qemuon a kOps/GCE cluster with nested virtualization. The shared kOps/GCE setup moved intodev/ci/shared/kops-gcp-lifecycle.sh. The Prow job isn't registered yet (kubernetes/test-infra#38129). - Peer-pods in E2E (#2029): The RuntimeClass E2E tests now support
kata-remote(peer-pods). NewSANDBOX_CONTROLLER_WORKERSsets the test controller's worker counts and is derived automatically forkata*runtime classes.SANDBOX_DEFAULT_TIMEOUTmakes the 60s default timeout configurable. - Fewer slow E2Es (#1953, #2048): Replaced four shutdown-policy E2Es with unit tests, and trimmed the warm-pool rollout E2E to its
Recreatecase. - Tooling:
- Dependencies: Built with Go 1.27.3 (#2037).
controller-runtimev0.25.2 (#1859). - Docs and contributor guidance: Clearer docs-site sidebar hierarchy (#2022).
AGENTS.mdadditions on writing style, the condition lifecycle, and test suites (#2036, #2081, #1969).
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.9/sandbox-with-extensions.yamlSelective Install
Install components separately:
# Core only:
kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/download/v1.0.9/sandbox.yaml
# Extensions (opt-in):
kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/download/v1.0.9/extensions.yamlPython SDK
pip install k8s-agent-sandbox==1.0.9Contributors
We extend our sincere thanks to all contributors to this release:
@BootstrapperSBL, @HasonoCell, @LucasGois1, @Quiyyy, @RachelWanggg, @YoungJinJung, @abhayjoshi201, @aditya-shantanu, @briankhoi, @dongjiang1989, @ericcurtin, @esposem, @fponsse, @hakman, @janetkuo, @justinsb, @leomcl, @niting, @stream7, @tomergee, @vvoronko, @wjun29
New Contributors
Full Changelog: v1.0.8...v1.0.9