feat(opensandbox): keepalive-bounded aiohttp transport + create/command hardening - #2209
Closed
hemildesai wants to merge 6 commits into
Closed
feat(opensandbox): keepalive-bounded aiohttp transport + create/command hardening#2209hemildesai wants to merge 6 commits into
hemildesai wants to merge 6 commits into
Conversation
…nd hardening Sandboxed agent evals at high concurrency hit two infra failure classes that silently zero out rollout rewards: 1. "Server disconnected without sending a response": the SDK's default httpx pool keeps idle connections 30s, but the OpenSandbox server's uvicorn keep-alive reaper closes them after ~5s. Agent workloads idle between commands (model think time), so commands routinely reuse a socket the server already closed. The provider now injects a transport with keepalive_expiry below the server timeout (default 3s) — aiohttp-backed via httpx-aiohttp (connection.transport_backend: aiohttp, new sandbox extra dependency), falling back to httpx.AsyncHTTPTransport when the bridge is unavailable. 2. 502 "could not connect to the backend sandbox endpoint": with create.skip_health_check: true the first command races a pod whose exec daemon is not listening yet. The shipped config now health-checks on create (bounded by the spec's ready_timeout_s; create.timeout_s lowered to 900 to stay coherent) and allows 2 command retries — retries only fire for retryable-classified errors, never for command timeouts, so long-running commands are not double-executed. Validated on a 4-node SWE-bench Verified run (1500 rollouts, concurrency 500) against an EKS OpenSandbox deployment: disconnect and 502 rollout kills went from ~5 per 6 rollouts collected / ~7-12 per 64-rollout run to zero observed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tp opt-in httpx-aiohttp is pre-1.0 with limited adoption, so it should not be a hard dependency or the default path. The keepalive-expiry fix is transport- agnostic and fully delivered by the stock httpx transport; the aiohttp backend remains available behind connection.transport_backend: aiohttp for users who install httpx-aiohttp explicitly (graceful fallback + warning when missing). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tions The OpenSandbox SDK's create() accepts distinct `resource` (limits) and `resource_requests` maps; the server applies a lone `resource` map as requests=limits. Expose the requests side through sandbox_spec.provider_options.resource_requests (same keys as SandboxSpec.resources), so memory-spiky workloads (e.g. SWE-bench test suites) can run with high limits while keeping small scheduling requests for dense packing — without inflating cluster reservations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Retrying a command the server may have already started would execute it twice, and agent commands are frequently mutating (file writes, patch application, test-state changes). The keepalive bound added in this branch removes the stale-connection failures that retries were compensating for, so the retry default is not needed and is reverted to 0. Also raises the opensandbox lower bound to 0.1.15, the version exposing separate `resource`/`resource_requests` on Sandbox.create that the new provider_options.resource_requests support depends on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…limits Applies the new provider_options.resource_requests support to the SWE-bench harness config: limits 1 vCPU / 8Gi (the burst ceiling that keeps memory-spiky test suites from being OOM-killed mid-rollout) with scheduling requests kept at 0.5 vCPU / 2Gi so the cluster still packs sandboxes densely. Measured on 4-node GB200 SWE-bench Verified runs: raising the memory ceiling this way cut infra-caused rollout losses from 13.0% to 6.6% and lifted pass@1 from 58.0% to 67.1%, without increasing the per-sandbox cluster reservation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cut the explanatory comment blocks down to the constraint each setting actually needs, matching surrounding comment density. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
Author
|
Superseded by #2212 — identical content (verified byte-identical tree), with |
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.
Summary
Fixes two sandbox-infrastructure failure classes that silently zero out rollout rewards in high-concurrency sandboxed agent evals (diagnosed on 4-node GB200 SWE-bench Verified runs at concurrency 300–500 against an EKS OpenSandbox deployment; the same signatures appear at concurrency 8, just less often).
1.
Server disconnected without sending a responseThe SDK's default httpx pool keeps idle connections for 30s (
opensandbox.config.connection.with_transport_if_missing), but the OpenSandbox server's uvicorn keep-alive reaper closes idle sockets after ~5s. Agent workloads idle between sandbox commands (model think time), so commands routinely reuse a socket the server already closed — surfacing ashttpx.RemoteProtocolError→SandboxInternalException, which permanently kills the rollout.Fix: the provider injects a transport whose
keepalive_expirysits below the server's keep-alive timeout (default 3s,connection.keepalive_expiry_s), so pooled sockets are retired before the server can close them:connection.transport_backend: httpx(default) — stockhttpx.AsyncHTTPTransport(limits=..., retries=connect_retries). No new required dependencies.connection.transport_backend: aiohttp(opt-in) — aiohttpClientSession/TCPConnectorpool under the SDK's httpx surface viahttpx-aiohttp. Falls back to the httpx transport with a warning when the package is absent.keepalive_expiry_s: nulldisables injection entirely (SDK default transport).2. 502
could not connect to the backend sandbox endpoint='<podIP>:44772'With
create.skip_health_check: true,Sandbox.createreturns before the pod's exec daemon is listening; the first command races pod startup and the server proxy 502s. Under a create burst this killed 7–12 rollouts per run.Fix:
create.skip_health_check: false(create waits for readiness, bounded by the spec'sready_timeout_s;create.timeout_slowered 1200→900 to stay coherent with a 600s ready timeout).command_retriesstays at 0. An earlier revision of this branch raised it to 2; that is reverted. Retrying a command the server may have already started would execute it twice, and agent commands are frequently mutating (file writes, patch application, test-state changes). The keepalive bound removes the stale-connection failures retries were compensating for, so the risk buys nothing. Raise it only for idempotent workloads.3. Separate resource requests and limits
Sandbox.createaccepts distinctresource(limits) andresource_requestsmaps; a loneresourcemap is applied by the server as requests=limits. This exposes the requests side viasandbox_spec.provider_options.resource_requests(same keys asSandboxSpec.resources).Field motivation: SWE-bench test suites OOM-killed sandbox pods at 2Gi, but raising a single combined map to 8Gi would 4× the cluster reservation. With the split, limits go to 1 vCPU / 8Gi while requests stay 0.5 vCPU / 2Gi. Requires
opensandbox>=0.1.15(lower bound raised here).Validation
Measured on the EKS deployment across paired 4-node runs at concurrency 300–500, identical except for the variable under test:
With the full stack (keepalive bound + health-checked create + requests/limits split), a later run showed 3 infra failures in 70 rollouts (4.3%), zero
Server disconnectedevents, and zero OOMKilled pods — all three failures traced to a single misbehaving worker node rather than the client. A fleet sweep of execd logs across 92 sandbox pods found 101OnExecuteErrorevents, allCommandExecError: 1(the agent's own commands exiting nonzero) and zero exec-layer faults: no spawn failures, no timeouts, no OOM kills.Relationship to #2020
Complementary, no overlap: #2020 adds job attribution metadata (team/user/workload/run labels); this PR covers transport reliability, create hardening, and the resource split. Field-validated together — the attribution labels are what make post-cancellation sandbox garbage collection safely scoped to a single job.
Testing
tests/unit_tests/test_opensandbox_provider.py: newtest_connection_transport_backends(httpx default with keepalive expiry, custom pool settings, fallback whenhttpx_aiohttpis unavailable,keepalive_expiry_s: nulldisables injection),test_connection_transport_backend_aiohttp_opt_in(importorskip-guarded), andtest_direct_create_passes_resource_requests_to_sdk_create(requests/limits both reach the SDK, sanitized; bad-type and unknown-key rejection).test_connection_config_and_image_policyupdated for the injected transport kwarg.httpx-aiohttpinstalled.🤖 Generated with Claude Code