feat(workspace): add AgentSandboxWorkspace (Kubernetes, kubernetes-sigs/agent-sandbox) - #4516
Open
aleks-stefanovic wants to merge 8 commits into
Open
feat(workspace): add AgentSandboxWorkspace (Kubernetes, kubernetes-sigs/agent-sandbox)#4516aleks-stefanovic wants to merge 8 commits into
aleks-stefanovic wants to merge 8 commits into
Conversation
…agent-sandbox pod New RemoteWorkspace backend that claims a pod from a SandboxWarmPool, runs the OpenHands agent server in it, and connects via kubectl port-forward (local/kind) or a direct URL. Supports native pause/resume through the Sandbox operatingMode and teardown by deleting the SandboxClaim. Ships example manifests, a runnable example with a kind walkthrough, and unit tests. Depends on the optional k8s-agent-sandbox client (openhands-workspace[agent-sandbox]).
Live testing on kind surfaced two resume() failures: the local port was reused while still in TIME_WAIT, and kubectl port-forward could attach while the resumed pod's network namespace was still churning. Reconnect now retries with a fresh local port (_connect_port_forward_with_retry) and rebuilds the HTTP client, which is also used for the initial connect.
…helper scripts Documents three test tiers (unit / keyless workspace smoke / full agent e2e with a local Ollama model, no API key) with the setup that works: relax the sandbox network policy to reach an in-cluster LLM, use a model that emits structured tool calls (qwen2.5:7b / llama3.1:8b), reasoning_effort='none', and a minimal terminal-only agent for small models. Adds testing/smoke_test.py, testing/agent_ollama_example.py, and testing/ollama.yaml.
…cess - model_post_init now wraps everything after create_sandbox() in try/except so a failed connect/health phase terminates the claim instead of leaking it (the constructor raising means the caller never gets an object to close). - Track the caller's explicit host_port separately so resume() always reconnects on a freshly allocated port instead of retrying the previous (TIME_WAIT) one. - Resolve the pod name via the public client API (k8s_helper.get_sandbox + POD_NAME_ANNOTATION) instead of clearing the handle's private _pod_name. - Note the agent-sandbox OpenHands#1160/OpenHands#1296 follow-up on _patch_operating_mode. - Pin the agent-server image to 1.38.0-python and ship a commented scoped-egress networkPolicy example in the template; point TESTING.md at it. - Add tests for the claim-cleanup path and the fresh-port reconnect.
…python Rebased onto v1.42.1: bump the pinned agent-server tag to match the SDK version, and reformat the manifests with the repo's yamlfmt hook (re-aligning comments so the commented networkPolicy example still uncomments to valid YAML).
A dead kubectl port-forward exits immediately, so a flat 2s sleep spent the whole retry budget in ~10s -- shorter than a freshly resumed pod sometimes needs to become forwardable. Widen to 8 attempts with escalating backoff (~50s).
aleks-stefanovic
force-pushed
the
feat/agent-sandbox-workspace
branch
from
August 17, 2026 20:55
306142b to
02417ca
Compare
aleks-stefanovic
marked this pull request as ready for review
August 17, 2026 20:57
18 tasks
Collaborator
|
🚦 CI is currently failing on this PR's latest commit. Please fix the failing checks before OpenHands reviews it - this is re-checked automatically once you push a new commit. (A maintainer can also request This is an automated check - no AI was used to generate this comment. |
The PR-description check failed before the linked issue was added and passed on re-run, but the stale failing run is still attached to the previous commit.
This was referenced Aug 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
HUMAN:
Adds a Kubernetes-backed workspace so teams already running k8s can host agent sandboxes instead of relying on Docker or a hosted runtime. Validated end-to-end on kind and GKE. Reviewed with the agent-sandbox maintainers before opening.
AGENT:
Why
There is currently no way to run the agent server in a Kubernetes-managed sandbox.
The remote workspaces today cover Docker (single host), the hosted runtime API,
OpenHands Cloud, and Apptainer. Teams that already operate Kubernetes therefore
have to either run Docker inside something else or depend on a hosted runtime.
kubernetes-sigs/agent-sandbox
is a Kubernetes SIG-Apps project providing a
SandboxCRD for exactly this shape ofworkload: isolated, stateful, single-pod runtimes. Backing a workspace with it gives
capabilities the existing remote backends don't have:
SandboxWarmPool): a pre-warmed pod is claimed in about a second,instead of paying container cold start per conversation.
spec.operatingMode: Suspendedterminates the pod whilekeeping the PVC, so workspace state survives a suspend. This goes further than
docker pause.runtimeClassNameon the template.The agent server is already just an image on a port, so this needs no changes to the
agent server or to SDK core. It only adds a new workspace implementation.
Summary
AgentSandboxWorkspace(RemoteWorkspace)inopenhands-workspace: claims a podfrom a
SandboxWarmPool, connects overkubectl port-forward(local/kind) or acaller-supplied URL (router/gateway/in-cluster DNS), implements
pause()/resume()via the Sandbox
operatingMode, and deletes theSandboxClaimon cleanup.SandboxTemplate+SandboxWarmPoolmanifests that runghcr.io/openhands/agent-server, plusREADME.md,TESTING.md, a runnable example,and helper scripts (keyless smoke test, local-Ollama agent run).
openhands-workspace[agent-sandbox]) so nothingchanges for existing users; 9 unit tests cover the backend with the client mocked.
Issue Number
Fixes #4519
How to Test
Everything below runs on a local kind cluster and needs no API key.
1. Unit tests (no cluster):
2. Cluster + workspace smoke test (no LLM). This is the meaningful end-to-end
check: it claims a pod, runs a command in it, suspends and resumes it, and verifies
workspace state survived.
3. Full agent run with a local Ollama model (still no API key). See
TESTING.mdsection 4a; it deploys Ollama in-cluster and runs
testing/agent_ollama_example.py.Video/Screenshots
Workspace smoke test. It claims a pod, runs a command, pauses, resumes, checks
the state survived, and tears down:
Full agent run. The agent server runs in the sandbox pod, driven by a local
Ollama model, and creates a file on its own:
The same agent run was also executed on a GKE cluster (in-cluster Ollama) with the
same result, to confirm nothing is kind-specific:
Cleanup / leak check. An earlier revision leaked the claim when the connect phase
failed after
create_sandbox()had succeeded. Reproduced it by pointing the workspaceat a dead port so the health check fails, and confirmed the fix:
Checks:
uv run pre-commit run --files <changed files>passes all hooks (yamlfmt,ruff format/lint, pycodestyle, pyright, import rules, tool registration), and
uv run pytest tests/workspace/passes (193 tests).Type
Notes
implementation plus docs/manifests. The only shared-file edits are the
openhands.workspaceexport and a new optional extra inpyproject.toml.k8s-agent-sandboxis only imported insidemodel_post_init, and a missing install raises a message pointing at the extra, sousers who never touch this backend are unaffected.
mode, namespace, warm pool, and timeouts are explicit constructor params with
validation; infrastructure concerns (CPU/memory, image,
runtimeClassName, networkpolicy, volumes) live in the
SandboxTemplaterather than in Python.kubernetes-sigs/agent-sandboxmain and confirmed the client API usage and thepause/resume approach; their review comments are addressed in this branch.
#1160 (claim-level
idle lifecycle) and
#1296
(traffic-triggered resume) will make pause/resume a claim-level concern; there is a
TODO in the code to move to the claim API when those land. The pod-name lookup would
also be tidier once the client exposes a public refresh.