Skip to content

feat(sandbox): implement E2B pause and resume - #3810

Merged
hemildesai merged 6 commits into
mainfrom
codex/e2b-pause-resume
Nov 4, 2026
Merged

hemildesai merged 6 commits into
mainfrom
codex/e2b-pause-resume

Conversation

@hemildesai

@hemildesai hemildesai commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

Implement E2B support for the public AsyncSandbox.pause() / resume() and synchronous Sandbox methods introduced in #2636. Previously these methods raised NotImplementedError for E2B.

Pause calls the SDK's public pause() method. Resume connects to the same sandbox ID and replaces the SDK handle only after success. Failed lifecycle calls are not retried by the provider, and the previous handle remains available for status checks or cleanup. Status uses control-plane metadata to report PAUSED without waking the guest.

This is independent of automatic pause/resume configuration in #3757 and changes no benchmark configs. Both PRs need the same control-plane status behavior. No separate issue is needed for this focused implementation of the existing public capability.

Validation

  • python -m pytest tests/unit_tests/test_e2b_provider.py tests/unit_tests/test_e2b_build.py tests/unit_tests/test_sandbox.py tests/unit_tests/test_sandbox_connect.py tests/unit_tests/test_sandbox_access.py --no-cov -q -o addopts='': 274 passed, 2 skipped with the locked E2B 2.46.0 on the branch rebased onto main; skips are existing opt-in live sandbox tests.
  • The two public async/sync lifecycle regression tests fail against the original provider on main.
  • pre-commit run --all-files: passed.
  • cd fern && npm run check -- --warnings: zero errors; redirect checking skipped because Fern authentication is unavailable.
  • git diff --check: passed.

CI classification: full (shared provider changes). The complete core/server suites were not run locally.

Rollouts

Live public-API smoke passed against AgentENV with osworld-slim, E2B 2.46.0, and certificate-verified HTTPS:

  • Public async pause reports PAUSED; repeated status checks do not resume it; repeated pause succeeds.
  • Resume preserves the sandbox ID, uploaded file contents, and a UUID held only in a running HTTP server's memory. The first post-resume command reads the same UUID.
  • Closing a paused sandbox deletes it, confirmed by a control-plane 404.
  • Public synchronous Sandbox.pause() / resume() preserves a file and permits subsequent exec.
  • All test sandboxes were cleaned up.

A model-driven agent/verifier rollout has not been run; the live smoke directly exercises the new public provider capability.

Compatibility and benchmark impact

No public API signature, creation policy, or benchmark configuration changes. Resume restarts the sandbox lifetime at create.timeout_s (the provider default); a per-sandbox SandboxSpec.ttl_s is not reapplied. Pause raises on a 409 Conflict, which E2B cloud sends for an already-paused sandbox and AgentENV sends during a snapshot or fork. Existing E2B descriptor connect behavior is unchanged. Status now reflects control-plane lifecycle state instead of guest-health reachability. Timeouts and transport errors can leave remote state unknown; check status before retrying. No dataset or scoring changes; benchmark-result impact is unmeasured.

Checklist

  • Contributing guidelines read; change is focused.
  • Tests and documentation updated.
  • Pre-commit passed.
  • SPDX headers preserved; no new source files.
  • Commits have DCO sign-off.

@hemildesai hemildesai added feature New capabilities, enhancements, or enablement work area:sandbox Sandbox providers, isolation, and sandbox runtime contracts labels Sep 30, 2026
@copy-pr-bot

copy-pr-bot Bot commented Sep 30, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@hemildesai

Copy link
Copy Markdown
Contributor Author

/ok to test f3df6f7

@hemildesai

Copy link
Copy Markdown
Contributor Author

/claude review

@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

@claude

claude Bot commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

SHIP — clean, well-scoped addition of E2B pause/resume with strong test coverage.

Reviewed nemo_gym/sandbox/providers/e2b/provider.py, its test file, and the docs page against the base and the sandbox provider contract.

What's solid:

  • pause/resume correctly implement the pre-existing SupportsSandboxPauseResume protocol; resume() only replaces handle.raw after a successful connect, so a failed resume leaves the old handle intact (verified by test_lifecycle_failure_is_not_retried_and_preserves_handle).
  • No retry wrapper on pause/resume is a deliberate, documented choice (both are non-idempotent w.r.t. state ambiguity) and matches the docstrings and the docs note about unknown remote state on failure.
  • status() switching from is_running() to control-plane get_info() correctly avoids waking a paused guest — asserted by the AssertionError-on-is_running mock. Not-found → STOPPED, transient → UNKNOWN preserved.
  • Closed-handle rejection, sync facade, and CancelledError all covered.

One non-blocking note (posted inline at provider.py status()): the {"running": ..., "paused": ...}.get(info.state, UNKNOWN) lookup is exact-case, and no test exercises the real SDK's get_info().state value — the sibling opensandbox provider deliberately lowercases state because "servers disagree on state casing." Matches E2B 2.36's lowercase-Literal contract today; normalizing would harden against SDK drift. Author's call.

Docs and tests are consistent with the code. No async-safety, public-API, or config-convention concerns.

@hemildesai
hemildesai marked this pull request as ready for review September 30, 2026 16:17
@yaoyu-33 yaoyu-33 added complexity:low Localized change in one scope with a small, straightforward review surface needs-review PR is ready for code review and waiting on a reviewer labels Sep 30, 2026
Comment thread tests/unit_tests/test_e2b_provider.py Resolved
Comment thread tests/unit_tests/test_e2b_provider.py Resolved
@yaoyu-33

Copy link
Copy Markdown
Contributor

Team review: SHIP WITH CARE. Thanks for this! The main item is @ananthsub's resume-lifetime thread on provider.py#L461 (replied there with more evidence). The inline comments are smaller follow-ups.

  • [PROCESS] The description says it "remains draft", but the PR isn't marked as a draft. Could you convert it, or note why a model rollout is N/A?
  • Overlap with feat(sandbox): support AgentENV auto-pause and resume #3757: the identical status() hunks merge cleanly, but test_e2b_provider.py has 3 conflicts and duplicate status tests. Whichever lands second should keep this PR's pause-aware FakeSandbox.get_info.

Generated by Claude Code

@hemildesai

Copy link
Copy Markdown
Contributor Author

/review model=claude

@hemildesai

Copy link
Copy Markdown
Contributor Author

/ok to test b71689e

@hemildesai

Copy link
Copy Markdown
Contributor Author

/ok to test 83ae6be

@hemildesai

Copy link
Copy Markdown
Contributor Author

/review model=claude

@hemildesai

Copy link
Copy Markdown
Contributor Author

/review model=claude

@svcnvidia-nemo-ci svcnvidia-nemo-ci left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SHIP: LGTM, no concerns found. My last review (5746662132, at b2082a4) had no findings. None of the three changes since then raise anything eligible for this follow-up.

This is a follow-up review. I compared the head with the tree I reviewed last time (/context/previous-review/, b2082a4). Only three changes landed since then, all in 79e7b90. They match ananthsub's three suggestions from review 5759053088.

Changes since b2082a4

  • Pause 409 message. pause() now says the sandbox "refused pause with 409 Conflict (already paused or busy); check status()".
    • Only the text changed. The exception type (RuntimeError), the is False branch and the handle state are all the same as at b2082a4 L610-L615.
    • No caller I found reads the message. The AsyncSandbox.pause facade calls the provider directly, and SupportsSandboxPauseResume doesn't specify an error message.
    • Both tests now use match="refused pause with 409", at L506 and L1376. Both strings match the new text.
  • Docs sentence. e2b.mdx#L204-L207 now also mentions E2B cloud's 409 for a sandbox in a transitional state.
    • The "which status reports as paused" clause still refers to the AgentENV snapshot/fork case.
    • The rewrap kept every other sentence, including the resume lifetime and ttl_s note, the no-retry note, and the ttl_s backstop paragraph at L215-L217.
  • Two new resume tests, at L1390-L1410. I traced both against the source; I didn't run them.
    • test_resume_prefers_the_configured_request_timeout: create() keeps request_timeout=90.0 on the handle. In resume(), setdefault then keeps the configured 12, so the call is timeout=3600, request_timeout=12. If setdefault became an assignment, the call would send 90.0 and the test would fail. The existing test at L1387 can't catch that, because its create and configured timeouts are both 12.
    • test_resume_sends_whole_second_lifetime: create() accepts 0.2 and 90.4 because it only requires a finite value above 0 (L447-L449). resume() sends the int values 91 and 1. Without ceil, the 90.4 case would send the float 90.4 and fail both assertions.
    • Both tests patch FakeSandbox.connect through monkeypatch, which undoes the patch after each test, so nothing leaks into other tests.

Other reviewers' threads that GitHub still marks current

  • ananthsub's request for these two resume tests (on test_e2b_provider.py L1387) is done; the tests are described above.
  • pthombre's ttl_s backstop docs request is still met at e2b.mdx L215-L217.
  • yaoyu-33's real-SDK test request is still met by test_real_sdk_pause_resume_and_status_use_only_the_control_plane (L472-L521).

ananthsub's "Outside this PR" items (e2b 2.55 compatibility and the missing lifetime bound for paused sandboxes) cover code that already existed before b2082a4. They aren't in scope for this follow-up.

Coverage note

  • I split this follow-up into three topics, each handled by a delegated child: the pause 409 message and its callers, the e2b.mdx changes, and the new and updated tests.
  • All three children finished, but only the tests child's report reached me. I checked the other two topics myself:
    • I compared the e2b.mdx Pause and Resume section line by line against b2082a4.
    • I read pause() in both trees, the AsyncSandbox pause/resume facades and the pause/resume protocol.
  • I couldn't run tests, search the whole repo for other uses of the message text, or read the E2B SDK or AgentENV source.

Review generated by claude. This was a follow-up review. Request a full review with /review reset=true.

Review generated by claude. This was a findings-only review. Request a full review with /review reset=true.

@ananthsub ananthsub left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick turnaround. All three suggestions are in, and the E2B tests pass on 79e7b90 with the locked e2b 2.46.0. The e2b 2.55 compatibility and paused-sandbox cleanup items from the summary comment can be tracked separately.

hemildesai and others added 6 commits November 3, 2026 14:36
Signed-off-by: Hemil Desai <hemildesai@users.noreply.github.com>
Resume now passes create.timeout_s to connect, so a resumed sandbox keeps
the provider lifetime instead of the SDK's 300 s default, and keeps the
request timeout the SDK retained from create. Pause confirms the state
when the SDK returns False, since the SDK maps every 409 to False.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Hemil Desai <hemildesai@users.noreply.github.com>
Drives the real e2b SDK through a mock transport: status maps the SDK's
SandboxState enums, a 409 on an already-paused sandbox is a no-op, resume
sends the create lifetime, and no request reaches envd.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Hemil Desai <hemildesai@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Hemil Desai <hemildesai@users.noreply.github.com>
AgentENV answers pause with 409 during a snapshot or fork, and its
control-plane status reports those states as paused although the VM
returns to running. The get_info fallback therefore reported a pause
that did not happen. Raise on every 409 instead.

Document that a paused sandbox no longer counts down its timeout, so
ttl_s stops acting as a cleanup backstop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Hemil Desai <hemildesai@users.noreply.github.com>
The pause error now says the request was refused (already paused or
busy) and points at status(), instead of claiming the sandbox was not
paused. The docs list E2B cloud's transitional-state 409s too.

Add tests that a configured request timeout wins on resume and that
the resume lifetime is sent as whole seconds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Hemil Desai <hemildesai@users.noreply.github.com>
@hemildesai
hemildesai force-pushed the codex/e2b-pause-resume branch from 79e7b90 to a04b523 Compare November 3, 2026 22:36
@hemildesai

Copy link
Copy Markdown
Contributor Author

/ok to test 79e7b90

@copy-pr-bot

copy-pr-bot Bot commented Nov 3, 2026

Copy link
Copy Markdown

/ok to test 79e7b90

@hemildesai, there was an error processing your request: E2

See the following link for more information: https://docs.gha-runners.nvidia.com/cpr/e/2/

@hemildesai

Copy link
Copy Markdown
Contributor Author

/ok to test a04b523

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

🔄 Merge queue validation started! You can track the progress here: https://github.com/NVIDIA-NeMo/Gym/actions/runs/41756396552

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

🔄 Merge queue validation started! You can track the progress here: https://github.com/NVIDIA-NeMo/Gym/actions/runs/41757954180

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

🔄 Merge queue validation started! You can track the progress here: https://github.com/NVIDIA-NeMo/Gym/actions/runs/41759760746

Merged via the queue into main with commit 1e5a12e Nov 4, 2026
63 of 66 checks passed
@hemildesai
hemildesai deleted the codex/e2b-pause-resume branch November 4, 2026 02:34
hemildesai added a commit that referenced this pull request Nov 4, 2026
…ilience

Resolve the E2B docs page against #3810: keep the new-sandbox grace
paragraph before the control-plane status note and the new Pause and
Resume section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Hemil Desai <hemildesai@users.noreply.github.com>

This branch was previously deployed

1 inactive deployment
public — a04b523e Deployed Nov 4, 2026 by copy-pr-bot[bot] via release / finalize / notify #9810
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:sandbox Sandbox providers, isolation, and sandbox runtime contracts complexity:low Localized change in one scope with a small, straightforward review surface feature New capabilities, enhancements, or enablement work needs-review PR is ready for code review and waiting on a reviewer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants