Skip to content

feat(copilot): E2B sandbox auto-pause between turns to eliminate idle billing - #12330

Merged
majdyz merged 37 commits into
devfrom
feat/e2b-autopause
Mar 9, 2026
Merged

feat(copilot): E2B sandbox auto-pause between turns to eliminate idle billing#12330
majdyz merged 37 commits into
devfrom
feat/e2b-autopause

Conversation

@majdyz

@majdyz majdyz commented Mar 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Before

  • E2B sandboxes ran continuously between CoPilot turns, billing for idle time
  • Sandbox timeout caused termination (kill), losing all session state
  • No explicit cleanup when sessions were deleted — sandboxes leaked
  • Single timeout concept with no separation between pause and kill semantics

After

  • Per-turn pause: pause_sandbox() is called in the finally block after every CoPilot turn, stopping billing instantly between turns (paused sandboxes cost $0 compute)
  • Auto-pause safety net: Sandboxes are created with lifecycle={"on_timeout": "pause"} (pause_timeout = 4h default) so they auto-pause rather than terminate if the explicit pause is missed
  • Auto-reconnect: AsyncSandbox.connect() in e2b SDK v2 auto-resumes paused sandboxes transparently — no extra code needed
  • Session delete cleanup: kill_sandbox() is now called in delete_chat_session() to explicitly terminate sandboxes and free resources
  • Two distinct timeouts: pause_timeout (4h, e2b auto-pause) vs redis_ttl (12h, session key lifetime)

Key Changes

File Change
pyproject.toml Bump e2b-code-interpreter 1.x2.x
e2b_sandbox.py Add pause_sandbox(), kill_sandbox(), _act_on_sandbox() helper; lifecycle={"on_timeout": "pause"}; separate pause_timeout / redis_ttl params
sdk/service.py Call pause_sandbox() in finally block before transcript upload; use walrus operator for type-safe e2b_api_key narrowing
model.py Call kill_sandbox() in delete_chat_session(); inline import to avoid circular dependency
config.py Add e2b_active property; rename e2b_sandbox_timeout default to 4h
e2b_sandbox_test.py Add test_pause_then_reconnect_reuses_sandbox test; update all sandbox_timeoutpause_timeout

Verified E2E

  • Used real E2B_API_KEY from k8s dev cluster to manually verify: sandbox created → paused → is_running() == False → reconnected via connect() → state preserved → killed

Test plan

  • poetry run pytest backend/copilot/tools/e2b_sandbox_test.py — all 19 tests pass
  • CI: test (3.11, 3.12, 3.13), types — all green
  • E2E verified with real E2B credentials

- Upgrade e2b-code-interpreter 1.x → 2.x (pulls in e2b v2 with pause/resume)
- Create sandboxes with lifecycle={on_timeout: pause} as safety net
- Add pause_sandbox() helper to pause between turns via AsyncSandbox.connect/pause
- Call pause_sandbox() in sdk/service.py finally block after each CoPilot turn
- Fix code_executor.py type: ignore for e2b v2 namespace shadowing issue
- Add TestPauseSandbox tests; assert lifecycle param on creation
@majdyz
majdyz requested a review from a team as a code owner March 8, 2026 05:56
@majdyz
majdyz requested review from Swiftyos and kcze and removed request for a team March 8, 2026 05:56
@github-project-automation github-project-automation Bot moved this to 🆕 Needs initial review in AutoGPT development kanban Mar 8, 2026
@coderabbitai

coderabbitai Bot commented Mar 8, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR modifies E2B sandbox lifecycle management by introducing per-turn pausing for billing control. It updates get_or_create_sandbox() to accept sandbox_timeout and redis_ttl parameters, adds a new pause_sandbox() function, reduces default sandbox timeout from 12 to 4 hours, and adds an e2b_active configuration property. E2B v2 compatibility is enabled via a dependency update.

Changes

Cohort / File(s) Summary
E2B Sandbox Lifecycle API
autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
Refactored sandbox lifecycle with new pause_sandbox() public function and _act_on_sandbox() internal helper. Updated get_or_create_sandbox() signature to accept sandbox_timeout and redis_ttl parameters; sandboxes now created with lifecycle={"on_timeout": "pause"}. Added constants _E2B_SANDBOX_TIMEOUT (4h) and _REDIS_SANDBOX_TTL (48h).
Service Layer Integration
autogpt_platform/backend/backend/copilot/sdk/service.py
Integrated pause_sandbox() calls around turn boundaries. Modified _setup_e2b to use config.e2b_active flag and pass sandbox_timeout to get_or_create_sandbox(). Added pausing logic in _next_msg and finally block before transcript upload with error-tolerant warning logging.
Test Coverage
autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
Added comprehensive tests for new pause_sandbox() function covering pause/no-op, creating-state, connection failures, and pause→reconnect scenarios. Updated existing tests to verify sandbox_timeout parameter usage and lifecycle={"on_timeout": "pause"} assertion on sandbox creation.
Configuration & Dependencies
autogpt_platform/backend/backend/copilot/config.py, autogpt_platform/backend/pyproject.toml
Reduced default e2b_sandbox_timeout from 43200 (12h) to 14400 (4h). Added new e2b_active property to ChatConfig that returns True when use_e2b_sandbox is enabled and e2b_api_key is present. Updated e2b-code-interpreter dependency from ^1.5.2 to ^2.0.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant Service
    participant Config
    participant E2B as E2B Sandbox
    participant Transcript as Transcript Upload

    Client->>Service: Start turn
    Service->>Config: Check e2b_active
    Config-->>Service: true (enabled + API key present)
    Service->>E2B: get_or_create_sandbox(sandbox_timeout, redis_ttl)
    E2B-->>Service: AsyncSandbox (with lifecycle pause)
    Service->>Service: _next_msg (compute response)
    Service->>E2B: pause_sandbox()
    E2B-->>Service: paused (or warning logged)
    Service->>Transcript: Upload turn transcript
    Transcript-->>Service: complete
    Service->>E2B: pause_sandbox() again
    E2B-->>Service: paused (or warning logged)
    Service-->>Client: Turn complete
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • kcze
  • Swiftyos
  • Bentlybro

Poem

🐰 Hop, hop—the sandboxes now pause between turns,
Billing bells quiet as the timeout gently churns.
Four hours of work, then a restful repose,
E2B v2 speeds through, as the system well knows! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 95.45% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title directly reflects the main objective of the PR: implementing E2B sandbox auto-pause between turns to eliminate idle billing, which matches the primary changes across all modified files.
Description check ✅ Passed The description comprehensively explains the before/after scenario, key changes with a detailed table, and test verification, all directly related to the changeset of implementing E2B sandbox pause functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/e2b-autopause

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Mar 8, 2026

Copy link
Copy Markdown
Contributor

🔍 PR Overlap Detection

This check compares your PR against all other open PRs targeting the same branch to detect potential merge conflicts early.

🔴 Merge Conflicts Detected

The following PRs have been tested and will have merge conflicts if merged after this PR. Consider coordinating with the authors.

🟢 Low Risk — File Overlap Only

These PRs touch the same files but different sections (click to expand)

Summary: 5 conflict(s), 0 medium risk, 11 low risk (out of 16 PRs with file overlap)


Auto-generated on push. Ignores: openapi.json, lock files.

Comment thread autogpt_platform/backend/backend/copilot/sdk/service.py Outdated

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py (1)

126-136: ⚠️ Potential issue | 🟠 Major

Keep the Redis sandbox mapping alive longer than the E2B timeout.

Line 136 still uses setex(..., timeout, ...), but the sandbox now pauses at timeout instead of terminating. That means the Redis key expires exactly when the paused sandbox still exists, so the next turn creates a fresh sandbox and loses prior filesystem state, and kill_sandbox() can no longer clean up the old paused one.

💡 Proposed fix
-    await redis.setex(redis_key, timeout, sandbox.sandbox_id)
+    # The sandbox now survives the runtime timeout by pausing, so keep the
+    # session→sandbox mapping until explicit cleanup (or use a longer
+    # session-retention TTL instead of the sandbox runtime timeout).
+    await redis.set(redis_key, sandbox.sandbox_id)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py` around lines
126 - 136, The Redis mapping is being set with redis.setex(redis_key, timeout,
sandbox.sandbox_id) but the sandbox now pauses at timeout instead of being
destroyed, so the key must outlive the E2B timeout; update the code that sets
the Redis TTL (the redis.setex call in e2b_sandbox.py after AsyncSandbox.create)
to use a longer TTL than timeout (e.g., timeout plus a configurable grace period
or multiply by a factor), and ensure the value stored is still
sandbox.sandbox_id so kill_sandbox() can find and clean paused sandboxes; make
this TTL configurable via a constant or parameter rather than hardcoding.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py`:
- Around line 164-180: The calls to AsyncSandbox.connect and sandbox.pause can
hang indefinitely; wrap both awaits with a timeout (e.g., using asyncio.wait_for
or asyncio.timeout) so the connect and pause ops don't block the service; update
the AsyncSandbox.connect(...) and sandbox.pause() invocations in the pause flow
to use bounded timeouts and handle TimeoutError in the except block to log a
clear timeout message and return False.

---

Outside diff comments:
In `@autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py`:
- Around line 126-136: The Redis mapping is being set with
redis.setex(redis_key, timeout, sandbox.sandbox_id) but the sandbox now pauses
at timeout instead of being destroyed, so the key must outlive the E2B timeout;
update the code that sets the Redis TTL (the redis.setex call in e2b_sandbox.py
after AsyncSandbox.create) to use a longer TTL than timeout (e.g., timeout plus
a configurable grace period or multiply by a factor), and ensure the value
stored is still sandbox.sandbox_id so kill_sandbox() can find and clean paused
sandboxes; make this TTL configurable via a constant or parameter rather than
hardcoding.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 433ead1e-3e3a-4e96-b595-912479827092

📥 Commits

Reviewing files that changed from the base of the PR and between c304a49 and daa6a66.

⛔ Files ignored due to path filters (1)
  • autogpt_platform/backend/poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • autogpt_platform/backend/backend/blocks/code_executor.py
  • autogpt_platform/backend/backend/copilot/sdk/service.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
  • autogpt_platform/backend/pyproject.toml
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: types
  • GitHub Check: Seer Code Review
  • GitHub Check: Check PR Status
🧰 Additional context used
📓 Path-based instructions (8)
autogpt_platform/backend/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/backend/**/*.py: Use Python 3.11 (required; managed by Poetry via pyproject.toml) for backend development
Always run 'poetry run format' (Black + isort) before linting in backend development
Always run 'poetry run lint' (ruff) after formatting in backend development

Files:

  • autogpt_platform/backend/backend/blocks/code_executor.py
  • autogpt_platform/backend/backend/copilot/sdk/service.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
autogpt_platform/backend/backend/blocks/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/backend/backend/blocks/**/*.py: Inherit from 'Block' base class with input/output schemas when adding new blocks in backend
Implement 'run' method with proper error handling in backend blocks
Generate block UUID using 'uuid.uuid4()' when creating new blocks in backend
Write tests alongside block implementation when adding new blocks in backend

Files:

  • autogpt_platform/backend/backend/blocks/code_executor.py
autogpt_platform/backend/**/*.{py,txt}

📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)

Use poetry run prefix for all Python commands, including testing, linting, formatting, and migrations

Files:

  • autogpt_platform/backend/backend/blocks/code_executor.py
  • autogpt_platform/backend/backend/copilot/sdk/service.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
autogpt_platform/backend/backend/**/*.py

📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)

Use Prisma ORM for database operations in PostgreSQL with pgvector for embeddings

Files:

  • autogpt_platform/backend/backend/blocks/code_executor.py
  • autogpt_platform/backend/backend/copilot/sdk/service.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
autogpt_platform/backend/backend/blocks/*.py

📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)

autogpt_platform/backend/backend/blocks/*.py: When creating new blocks, inherit from the Block base class and define input/output schemas using BlockSchema
Implement blocks with an async run method and generate unique block IDs using uuid.uuid4()
When working with files in blocks, use store_media_file() from backend.util.file with appropriate return_format parameter: for_local_processing for local tools, for_external_api for external APIs, for_block_output for block outputs
Always use for_block_output format in store_media_file() for block outputs unless there is a specific reason not to
Never hardcode workspace checks when using store_media_file() - let for_block_output handle context adaptation automatically
When adding new blocks, analyze block interfaces to ensure inputs and outputs tie well together for productive graph-based editor connections

Files:

  • autogpt_platform/backend/backend/blocks/code_executor.py
autogpt_platform/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Format Python code with poetry run format

Files:

  • autogpt_platform/backend/backend/blocks/code_executor.py
  • autogpt_platform/backend/backend/copilot/sdk/service.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
autogpt_platform/backend/**/*_test.py

📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)

autogpt_platform/backend/**/*_test.py: Always review snapshot changes with git diff before committing when updating snapshots with poetry run pytest --snapshot-update
Use pytest with snapshot testing for API responses in test files
Colocate test files with source files using the *_test.py naming convention

Files:

  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
autogpt_platform/backend/**/*test*.py

📄 CodeRabbit inference engine (AGENTS.md)

Run poetry run test for backend testing (runs pytest with docker based postgres + prisma)

Files:

  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
🧠 Learnings (8)
📚 Learning: 2026-01-23T19:58:10.520Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 11796
File: autogpt_platform/backend/backend/blocks/video/loop.py:80-87
Timestamp: 2026-01-23T19:58:10.520Z
Learning: Ensure MoviePy is constrained to version ^2.1.2 (2.x) in pyproject.toml files where MoviePy is declared, so the backend video processing relies on a compatible API. This should cover all relevant pyproject.toml files (e.g., autogpt_platform/backend/pyproject.toml) to maintain consistency.

Applied to files:

  • autogpt_platform/backend/pyproject.toml
📚 Learning: 2026-02-04T16:49:42.490Z
Learnt from: CR
Repo: Significant-Gravitas/AutoGPT PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2026-02-04T16:49:42.490Z
Learning: Applies to autogpt_platform/backend/**/*.py : Use Python 3.11 (required; managed by Poetry via pyproject.toml) for backend development

Applied to files:

  • autogpt_platform/backend/pyproject.toml
📚 Learning: 2026-03-04T23:58:09.319Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12284
File: autogpt_platform/frontend/src/app/api/openapi.json:11897-11900
Timestamp: 2026-03-04T23:58:09.319Z
Learning: Repo: Significant-Gravitas/AutoGPT — PR `#12284`
Backend/frontend OpenAPI codegen convention: In backend/api/features/store/model.py, the StoreSubmission and StoreSubmissionAdminView models define submitted_at: datetime | None, changes_summary: str | None, and instructions: str | None with no default. This is intentional to produce “required but nullable” fields in OpenAPI (properties appear in required[] and use anyOf [type, null]). This matches Prisma’s submittedAt DateTime? and changesSummary String?. Do not flag this as a required/nullable mismatch.

Applied to files:

  • autogpt_platform/backend/pyproject.toml
📚 Learning: 2026-02-26T17:02:22.448Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12211
File: .pre-commit-config.yaml:160-179
Timestamp: 2026-02-26T17:02:22.448Z
Learning: Keep the pre-commit hook pattern broad for autogpt_platform/backend to ensure OpenAPI schema changes are captured. Do not narrow to backend/api/ alone, since the generated schema depends on Pydantic models across multiple directories (backend/data/, backend/blocks/, backend/copilot/, backend/integrations/, backend/util/). Narrowing could miss schema changes and cause frontend type desynchronization.

Applied to files:

  • autogpt_platform/backend/pyproject.toml
  • autogpt_platform/backend/backend/blocks/code_executor.py
  • autogpt_platform/backend/backend/copilot/sdk/service.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
📚 Learning: 2026-02-05T04:11:00.596Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 11796
File: autogpt_platform/backend/backend/blocks/video/concat.py:3-4
Timestamp: 2026-02-05T04:11:00.596Z
Learning: In autogpt_platform/backend/backend/blocks/**/*.py, when creating a new block, generate a UUID once with uuid.uuid4() and hard-code the resulting string as the block's id parameter. Do not call uuid.uuid4() at runtime; IDs must be constant across all imports and runs to ensure stability.

Applied to files:

  • autogpt_platform/backend/backend/blocks/code_executor.py
📚 Learning: 2026-03-05T15:42:08.207Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12297
File: .claude/skills/backend-check/SKILL.md:14-16
Timestamp: 2026-03-05T15:42:08.207Z
Learning: In Python files under autogpt_platform/backend (recursively), rely on poetry run format to perform formatting (Black + isort) and linting (ruff). Do not run poetry run lint as a separate step after poetry run format, since format already includes linting checks.

Applied to files:

  • autogpt_platform/backend/backend/blocks/code_executor.py
  • autogpt_platform/backend/backend/copilot/sdk/service.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
📚 Learning: 2026-03-04T08:04:35.881Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12273
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:216-220
Timestamp: 2026-03-04T08:04:35.881Z
Learning: In the AutoGPT Copilot backend, ensure that SVG images are not treated as vision image types by excluding 'image/svg+xml' from INLINEABLE_MIME_TYPES and MULTIMODAL_TYPES in tool_adapter.py; the Claude API supports PNG, JPEG, GIF, and WebP for vision. SVGs (XML text) should be handled via the text path instead, not the vision path.

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/service.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
📚 Learning: 2026-03-04T12:19:39.243Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12279
File: autogpt_platform/backend/backend/copilot/tools/base.py:184-188
Timestamp: 2026-03-04T12:19:39.243Z
Learning: In autogpt_platform/backend/backend/copilot/tools/, ensure that anonymous users always pass user_id=None to tool execution methods. The anon_ prefix (e.g., anon_123) is used only for PostHog/analytics distinct_id and must not be used as an actual user_id. Use a simple truthiness check on user_id (e.g., if user_id: ... else: ... or a dedicated is_authenticated flag) to distinguish anonymous from authenticated users, and review all tool execution call sites within this directory to prevent accidentally forwarding an anon_ user_id to tools.

Applied to files:

  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py

Comment thread autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
Comment thread autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py Outdated
- Add 10s timeout via asyncio.wait_for in pause_sandbox()
- Catch BaseException instead of Exception in service.py finally block
  so CancelledError (BaseException, not Exception) is properly handled

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py (1)

126-136: ⚠️ Potential issue | 🟠 Major

Keep the Redis lookup alive if timeout no longer kills the sandbox.

With lifecycle={"on_timeout": "pause"}, the sandbox pauses at the timeout but remains indefinitely in E2B (not auto-deleted). Line 136 still expires the Redis key at the same timeout moment: await redis.setex(redis_key, timeout, sandbox.sandbox_id). After that expiry, kill_sandbox() and the session-delete path can no longer find the still-paused sandbox, so abandoned sessions will strand paused sandboxes that E2B retains indefinitely but the application cannot access. Either extend the Redis TTL beyond the sandbox timeout, add an explicit reaper for paused sandboxes, or revert on_timeout to "kill".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py` around lines
126 - 136, The Redis key is set to expire at the same timeout even though
AsyncSandbox.create is called with lifecycle={"on_timeout": "pause"}, which
leaves a paused sandbox in E2B after the Redis entry expires and becomes
unreachable; update the logic around AsyncSandbox.create/redis.setex so that if
lifecycle.get("on_timeout") != "kill" (or specifically equals "pause") you
extend or skip the TTL (e.g., set a much longer TTL or use a non-expiring key)
for redis_key when storing sandbox.sandbox_id, otherwise keep the original
timeout behavior; locate the AsyncSandbox.create call and the subsequent await
redis.setex(redis_key, timeout, sandbox.sandbox_id) and change the TTL
calculation/operation accordingly (or add a reaper path) so paused sandboxes
remain discoverable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@autogpt_platform/backend/backend/copilot/sdk/service.py`:
- Around line 1465-1472: The pause_sandbox call is currently executed after a
potentially long-running asyncio.shield(upload_transcript(...)) when
claude_agent_use_resume is enabled, which delays stopping billing; move or
decouple the call so pause_sandbox(session_id=..., api_key=config.e2b_api_key)
executes before starting the no-timeout transcript upload (or run it
concurrently and await its completion separately) — locate the block that calls
asyncio.shield(upload_transcript(...)) (and the feature flag
claude_agent_use_resume) and ensure pause_sandbox is invoked immediately prior
to beginning the upload path (or scheduled as an independent task) so billing is
paused at turn end rather than after the upload finishes.

---

Outside diff comments:
In `@autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py`:
- Around line 126-136: The Redis key is set to expire at the same timeout even
though AsyncSandbox.create is called with lifecycle={"on_timeout": "pause"},
which leaves a paused sandbox in E2B after the Redis entry expires and becomes
unreachable; update the logic around AsyncSandbox.create/redis.setex so that if
lifecycle.get("on_timeout") != "kill" (or specifically equals "pause") you
extend or skip the TTL (e.g., set a much longer TTL or use a non-expiring key)
for redis_key when storing sandbox.sandbox_id, otherwise keep the original
timeout behavior; locate the AsyncSandbox.create call and the subsequent await
redis.setex(redis_key, timeout, sandbox.sandbox_id) and change the TTL
calculation/operation accordingly (or add a reaper path) so paused sandboxes
remain discoverable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 238c58d7-e7a7-4990-b109-df4271208cc1

📥 Commits

Reviewing files that changed from the base of the PR and between daa6a66 and ce22388.

⛔ Files ignored due to path filters (1)
  • autogpt_platform/backend/poetry.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • autogpt_platform/backend/backend/copilot/sdk/service.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: types
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.11)
  • GitHub Check: Seer Code Review
  • GitHub Check: Check PR Status
🧰 Additional context used
📓 Path-based instructions (4)
autogpt_platform/backend/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/backend/**/*.py: Use Python 3.11 (required; managed by Poetry via pyproject.toml) for backend development
Always run 'poetry run format' (Black + isort) before linting in backend development
Always run 'poetry run lint' (ruff) after formatting in backend development

Files:

  • autogpt_platform/backend/backend/copilot/sdk/service.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
autogpt_platform/backend/**/*.{py,txt}

📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)

Use poetry run prefix for all Python commands, including testing, linting, formatting, and migrations

Files:

  • autogpt_platform/backend/backend/copilot/sdk/service.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
autogpt_platform/backend/backend/**/*.py

📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)

Use Prisma ORM for database operations in PostgreSQL with pgvector for embeddings

Files:

  • autogpt_platform/backend/backend/copilot/sdk/service.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
autogpt_platform/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Format Python code with poetry run format

Files:

  • autogpt_platform/backend/backend/copilot/sdk/service.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
🧠 Learnings (4)
📚 Learning: 2026-02-26T17:02:22.448Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12211
File: .pre-commit-config.yaml:160-179
Timestamp: 2026-02-26T17:02:22.448Z
Learning: Keep the pre-commit hook pattern broad for autogpt_platform/backend to ensure OpenAPI schema changes are captured. Do not narrow to backend/api/ alone, since the generated schema depends on Pydantic models across multiple directories (backend/data/, backend/blocks/, backend/copilot/, backend/integrations/, backend/util/). Narrowing could miss schema changes and cause frontend type desynchronization.

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/service.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
📚 Learning: 2026-03-04T08:04:35.881Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12273
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:216-220
Timestamp: 2026-03-04T08:04:35.881Z
Learning: In the AutoGPT Copilot backend, ensure that SVG images are not treated as vision image types by excluding 'image/svg+xml' from INLINEABLE_MIME_TYPES and MULTIMODAL_TYPES in tool_adapter.py; the Claude API supports PNG, JPEG, GIF, and WebP for vision. SVGs (XML text) should be handled via the text path instead, not the vision path.

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/service.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
📚 Learning: 2026-03-05T15:42:08.207Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12297
File: .claude/skills/backend-check/SKILL.md:14-16
Timestamp: 2026-03-05T15:42:08.207Z
Learning: In Python files under autogpt_platform/backend (recursively), rely on poetry run format to perform formatting (Black + isort) and linting (ruff). Do not run poetry run lint as a separate step after poetry run format, since format already includes linting checks.

Applied to files:

  • autogpt_platform/backend/backend/copilot/sdk/service.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
📚 Learning: 2026-03-04T12:19:39.243Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12279
File: autogpt_platform/backend/backend/copilot/tools/base.py:184-188
Timestamp: 2026-03-04T12:19:39.243Z
Learning: In autogpt_platform/backend/backend/copilot/tools/, ensure that anonymous users always pass user_id=None to tool execution methods. The anon_ prefix (e.g., anon_123) is used only for PostHog/analytics distinct_id and must not be used as an actual user_id. Use a simple truthiness check on user_id (e.g., if user_id: ... else: ... or a dedicated is_authenticated flag) to distinguish anonymous from authenticated users, and review all tool execution call sites within this directory to prevent accidentally forwarding an anon_ user_id to tools.

Applied to files:

  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py

Comment thread autogpt_platform/backend/backend/copilot/sdk/service.py Outdated
- Split `timeout` param into `sandbox_timeout` (4h, e2b auto-pause) and
  `redis_ttl` (48h, allow reconnection to paused sandboxes)
- Add test for pause→reconnect cycle (addresses Sentry race-condition comment)
@majdyz

majdyz commented Mar 8, 2026

Copy link
Copy Markdown
Contributor Author

Addressing Sentry comment #2901401483: added a dedicated test for the pause→reconnect cycle (TestPauseSandbox.test_pause_then_reconnect_reuses_sandbox). Regarding the race condition: in e2b v2, AsyncSandbox.connect() awaits the full sandbox resume before returning, so is_running() immediately after should reflect the resumed state. The theoretical window is negligible in practice.

…inally

Pause must happen before asyncio.shield(upload_transcript) which has no
timeout, otherwise billing continues while the upload is in progress.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py (1)

204-242: Minor: Consider race between pause and kill operations.

When a session is deleted while a turn is ending, pause_sandbox and kill_sandbox can race on the same sandbox. The current implementation handles this gracefully—both operations are best-effort with exception handling, and the worst case is a paused sandbox that times out naturally.

For additional robustness, consider:

  1. Using a short Redis lock during pause/kill operations
  2. Having kill_sandbox check if pause is in progress

Given the best-effort semantics and the auto-pause timeout safety net, this is acceptable as-is.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py` around lines
204 - 242, Kill_sandbox can race with pause_sandbox; add a short Redis-based
lock around the pause/kill sequence to serialize operations: in kill_sandbox
(and in pause_sandbox) acquire a short-lived lock key based on
_SANDBOX_REDIS_PREFIX+session_id (use a SETNX-like acquire with a small TTL),
skip or retry the operation if lock cannot be obtained, and release the lock
when done; additionally, have kill_sandbox check the Redis value after acquiring
the lock (using the same redis_key read and compare against _CREATING or a new
PAUSING marker) to avoid killing a sandbox that pause_sandbox just marked for
pause.
autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py (1)

288-358: LGTM!

Excellent test coverage for pause_sandbox:

  • Happy path with successful pause
  • Edge cases: no sandbox, creating state, connection failure
  • The test_pause_then_reconnect_reuses_sandbox test validates the critical pause→resume cycle

Consider adding a test for timeout behavior (when asyncio.wait_for times out) for completeness, similar to test_kill_timeout_returns_false.

🧪 Optional: Add timeout test for pause_sandbox
+    def test_pause_timeout_returns_false(self):
+        """Returns False when E2B API calls exceed the 10s timeout."""
+        redis = _mock_redis(get_val="sb-abc")
+        with (
+            _patch_redis(redis),
+            patch(
+                "backend.copilot.tools.e2b_sandbox.asyncio.wait_for",
+                new_callable=AsyncMock,
+                side_effect=asyncio.TimeoutError,
+            ),
+        ):
+            result = asyncio.run(pause_sandbox("sess-123", _API_KEY))
+
+        assert result is False
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py` around
lines 288 - 358, Add a new unit test that asserts pause_sandbox returns False
when the pause operation times out: create a sandbox via _mock_sandbox and a
redis mock (like in other tests), patch
backend.copilot.tools.e2b_sandbox.AsyncSandbox.connect to return that sandbox,
then simulate a timeout (either by making the sandbox.pause AsyncMock raise
asyncio.TimeoutError or by patching asyncio.wait_for to raise TimeoutError) and
call pause_sandbox("sess-123", _API_KEY); verify the function returns False and
behaves like test_kill_timeout_returns_false. Reference pause_sandbox,
AsyncSandbox, _mock_sandbox, and test_kill_timeout_returns_false when locating
code to mirror.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py`:
- Around line 143-148: The sandbox creation call uses an invalid lifecycle
parameter; replace the AsyncSandbox.create(...) call with
AsyncSandbox.beta_create(...) and pass auto_pause=True along with the existing
timeout and api_key (e.g., AsyncSandbox.beta_create(auto_pause=True,
timeout=sandbox_timeout, api_key=api_key, template=template)) so the sandbox is
paused on timeout; update the call site where AsyncSandbox.create is used to
AsyncSandbox.beta_create and remove lifecycle={"on_timeout": "pause"}.

---

Nitpick comments:
In `@autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py`:
- Around line 288-358: Add a new unit test that asserts pause_sandbox returns
False when the pause operation times out: create a sandbox via _mock_sandbox and
a redis mock (like in other tests), patch
backend.copilot.tools.e2b_sandbox.AsyncSandbox.connect to return that sandbox,
then simulate a timeout (either by making the sandbox.pause AsyncMock raise
asyncio.TimeoutError or by patching asyncio.wait_for to raise TimeoutError) and
call pause_sandbox("sess-123", _API_KEY); verify the function returns False and
behaves like test_kill_timeout_returns_false. Reference pause_sandbox,
AsyncSandbox, _mock_sandbox, and test_kill_timeout_returns_false when locating
code to mirror.

In `@autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py`:
- Around line 204-242: Kill_sandbox can race with pause_sandbox; add a short
Redis-based lock around the pause/kill sequence to serialize operations: in
kill_sandbox (and in pause_sandbox) acquire a short-lived lock key based on
_SANDBOX_REDIS_PREFIX+session_id (use a SETNX-like acquire with a small TTL),
skip or retry the operation if lock cannot be obtained, and release the lock
when done; additionally, have kill_sandbox check the Redis value after acquiring
the lock (using the same redis_key read and compare against _CREATING or a new
PAUSING marker) to avoid killing a sandbox that pause_sandbox just marked for
pause.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 58dcde3a-33d3-41ac-90f1-d12fff495e47

📥 Commits

Reviewing files that changed from the base of the PR and between ce22388 and 1f7b2eb.

📒 Files selected for processing (3)
  • autogpt_platform/backend/backend/copilot/sdk/service.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: types
  • GitHub Check: Seer Code Review
  • GitHub Check: Check PR Status
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.12)
🧰 Additional context used
📓 Path-based instructions (6)
autogpt_platform/backend/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/backend/**/*.py: Use Python 3.11 (required; managed by Poetry via pyproject.toml) for backend development
Always run 'poetry run format' (Black + isort) before linting in backend development
Always run 'poetry run lint' (ruff) after formatting in backend development

Files:

  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
  • autogpt_platform/backend/backend/copilot/sdk/service.py
autogpt_platform/backend/**/*.{py,txt}

📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)

Use poetry run prefix for all Python commands, including testing, linting, formatting, and migrations

Files:

  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
  • autogpt_platform/backend/backend/copilot/sdk/service.py
autogpt_platform/backend/**/*_test.py

📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)

autogpt_platform/backend/**/*_test.py: Always review snapshot changes with git diff before committing when updating snapshots with poetry run pytest --snapshot-update
Use pytest with snapshot testing for API responses in test files
Colocate test files with source files using the *_test.py naming convention

Files:

  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
autogpt_platform/backend/backend/**/*.py

📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)

Use Prisma ORM for database operations in PostgreSQL with pgvector for embeddings

Files:

  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
  • autogpt_platform/backend/backend/copilot/sdk/service.py
autogpt_platform/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Format Python code with poetry run format

Files:

  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
  • autogpt_platform/backend/backend/copilot/sdk/service.py
autogpt_platform/backend/**/*test*.py

📄 CodeRabbit inference engine (AGENTS.md)

Run poetry run test for backend testing (runs pytest with docker based postgres + prisma)

Files:

  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
🧠 Learnings (4)
📚 Learning: 2026-02-26T17:02:22.448Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12211
File: .pre-commit-config.yaml:160-179
Timestamp: 2026-02-26T17:02:22.448Z
Learning: Keep the pre-commit hook pattern broad for autogpt_platform/backend to ensure OpenAPI schema changes are captured. Do not narrow to backend/api/ alone, since the generated schema depends on Pydantic models across multiple directories (backend/data/, backend/blocks/, backend/copilot/, backend/integrations/, backend/util/). Narrowing could miss schema changes and cause frontend type desynchronization.

Applied to files:

  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
  • autogpt_platform/backend/backend/copilot/sdk/service.py
📚 Learning: 2026-03-04T08:04:35.881Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12273
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:216-220
Timestamp: 2026-03-04T08:04:35.881Z
Learning: In the AutoGPT Copilot backend, ensure that SVG images are not treated as vision image types by excluding 'image/svg+xml' from INLINEABLE_MIME_TYPES and MULTIMODAL_TYPES in tool_adapter.py; the Claude API supports PNG, JPEG, GIF, and WebP for vision. SVGs (XML text) should be handled via the text path instead, not the vision path.

Applied to files:

  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
  • autogpt_platform/backend/backend/copilot/sdk/service.py
📚 Learning: 2026-03-04T12:19:39.243Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12279
File: autogpt_platform/backend/backend/copilot/tools/base.py:184-188
Timestamp: 2026-03-04T12:19:39.243Z
Learning: In autogpt_platform/backend/backend/copilot/tools/, ensure that anonymous users always pass user_id=None to tool execution methods. The anon_ prefix (e.g., anon_123) is used only for PostHog/analytics distinct_id and must not be used as an actual user_id. Use a simple truthiness check on user_id (e.g., if user_id: ... else: ... or a dedicated is_authenticated flag) to distinguish anonymous from authenticated users, and review all tool execution call sites within this directory to prevent accidentally forwarding an anon_ user_id to tools.

Applied to files:

  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
📚 Learning: 2026-03-05T15:42:08.207Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12297
File: .claude/skills/backend-check/SKILL.md:14-16
Timestamp: 2026-03-05T15:42:08.207Z
Learning: In Python files under autogpt_platform/backend (recursively), rely on poetry run format to perform formatting (Black + isort) and linting (ruff). Do not run poetry run lint as a separate step after poetry run format, since format already includes linting checks.

Applied to files:

  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
  • autogpt_platform/backend/backend/copilot/sdk/service.py
🔇 Additional comments (9)
autogpt_platform/backend/backend/copilot/sdk/service.py (3)

63-63: LGTM!

The import correctly adds pause_sandbox alongside get_or_create_sandbox from the e2b_sandbox module.


796-801: LGTM!

The call to get_or_create_sandbox correctly uses the renamed sandbox_timeout parameter, aligning with the updated function signature.


1465-1472: Pause implementation is correct; ordering concern already flagged.

The pause_sandbox call correctly:

  • Checks config flags before attempting pause
  • Handles all exceptions including BaseException
  • Logs failures as warnings without breaking the finally flow

The concern about pause occurring after the potentially unbounded transcript upload was raised in a previous review comment. The current ordering may be intentional to ensure transcript integrity before pausing.

autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py (4)

9-26: LGTM!

The updated docstring clearly documents the sandbox lifecycle stages and cost control mechanism. The relationship between per-turn pause (primary) and on_timeout pause (safety net) is well explained.


43-49: LGTM!

The constants are well-documented and the values are sensible:

  • 4-hour sandbox timeout as a safety net for runaway sessions
  • 48-hour Redis TTL ensures paused sandboxes remain reconnectable across sessions

71-89: LGTM!

The updated signature with sandbox_timeout and redis_ttl parameters is well-documented. The docstring clearly explains the purpose of each parameter and their relationship.


162-201: LGTM!

The pause_sandbox function is well-implemented:

  • Returns early for missing/creating sandboxes (lines 174-179)
  • Uses asyncio.wait_for with a 10-second timeout (line 187) to prevent indefinite blocking
  • Catches all exceptions and returns False gracefully (lines 194-201)
  • Properly logs outcomes for debugging

The timeout addresses the concern raised in the previous review about unbounded E2B API calls.

autogpt_platform/backend/backend/copilot/tools/e2b_sandbox_test.py (2)

13-20: LGTM!

The import statement correctly includes pause_sandbox alongside existing imports.


137-139: LGTM!

Good test coverage for the new lifecycle parameter. The assertion verifies that sandbox creation correctly passes {"on_timeout": "pause"} to the E2B SDK.

@majdyz

majdyz commented Mar 8, 2026

Copy link
Copy Markdown
Contributor Author

Addressing r2901435324: lifecycle={"on_timeout": "pause"} is valid in e2b v2.x. From the installed SDK (e2b 2.15.1) docstring for AsyncSandbox.create(): 'lifecycle: Sandbox lifecycle configuration — on_timeout: "kill" (default) or "pause"'. The beta_create(auto_pause=True) is the older beta path; the stable create(lifecycle={...}) is correct here — no change needed.

- Remove unnecessary # type: ignore on sandbox.run_code (e2b v2 types are correct)
- Add config.e2b_active property to centralize use_e2b_sandbox && e2b_api_key check
- Update e2b_sandbox_timeout default from 12h to 4h in config
- Extract _act_on_sandbox() helper to eliminate duplicate code in pause/kill
- Fix single 10s timeout wrapping both connect+action (was accidentally split into 2x10s)

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py`:
- Around line 144-149: The code currently calls AsyncSandbox.create(...,
lifecycle={"on_timeout": "pause"}, timeout=sandbox_timeout) which is unsupported
in e2b SDK v2; replace this call with AsyncSandbox.beta_create(...,
auto_pause=True, timeout=sandbox_timeout) and pass the same template and api_key
values (i.e., use AsyncSandbox.beta_create with template, api_key,
auto_pause=True and timeout set to sandbox_timeout) so the sandbox will pause on
timeout instead of failing to initialize.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dc9e5b73-dc86-4962-b87a-282d38676b1c

📥 Commits

Reviewing files that changed from the base of the PR and between 6aa4b83 and 25484e8.

📒 Files selected for processing (3)
  • autogpt_platform/backend/backend/copilot/config.py
  • autogpt_platform/backend/backend/copilot/sdk/service.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: types
  • GitHub Check: Seer Code Review
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.13)
  • GitHub Check: Check PR Status
🧰 Additional context used
📓 Path-based instructions (4)
autogpt_platform/backend/**/*.py

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

autogpt_platform/backend/**/*.py: Use Python 3.11 (required; managed by Poetry via pyproject.toml) for backend development
Always run 'poetry run format' (Black + isort) before linting in backend development
Always run 'poetry run lint' (ruff) after formatting in backend development

Files:

  • autogpt_platform/backend/backend/copilot/config.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
  • autogpt_platform/backend/backend/copilot/sdk/service.py
autogpt_platform/backend/**/*.{py,txt}

📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)

Use poetry run prefix for all Python commands, including testing, linting, formatting, and migrations

Files:

  • autogpt_platform/backend/backend/copilot/config.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
  • autogpt_platform/backend/backend/copilot/sdk/service.py
autogpt_platform/backend/backend/**/*.py

📄 CodeRabbit inference engine (autogpt_platform/backend/CLAUDE.md)

Use Prisma ORM for database operations in PostgreSQL with pgvector for embeddings

Files:

  • autogpt_platform/backend/backend/copilot/config.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
  • autogpt_platform/backend/backend/copilot/sdk/service.py
autogpt_platform/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Format Python code with poetry run format

Files:

  • autogpt_platform/backend/backend/copilot/config.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
  • autogpt_platform/backend/backend/copilot/sdk/service.py
🧠 Learnings (6)
📓 Common learnings
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12284
File: autogpt_platform/frontend/src/app/api/openapi.json:11897-11900
Timestamp: 2026-03-04T23:58:09.319Z
Learning: Repo: Significant-Gravitas/AutoGPT — PR `#12284`
Backend/frontend OpenAPI codegen convention: In backend/api/features/store/model.py, the StoreSubmission and StoreSubmissionAdminView models define submitted_at: datetime | None, changes_summary: str | None, and instructions: str | None with no default. This is intentional to produce “required but nullable” fields in OpenAPI (properties appear in required[] and use anyOf [type, null]). This matches Prisma’s submittedAt DateTime? and changesSummary String?. Do not flag this as a required/nullable mismatch.
📚 Learning: 2026-03-04T23:58:09.319Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12284
File: autogpt_platform/frontend/src/app/api/openapi.json:11897-11900
Timestamp: 2026-03-04T23:58:09.319Z
Learning: Repo: Significant-Gravitas/AutoGPT — PR `#12284`
Backend/frontend OpenAPI codegen convention: In backend/api/features/store/model.py, the StoreSubmission and StoreSubmissionAdminView models define submitted_at: datetime | None, changes_summary: str | None, and instructions: str | None with no default. This is intentional to produce “required but nullable” fields in OpenAPI (properties appear in required[] and use anyOf [type, null]). This matches Prisma’s submittedAt DateTime? and changesSummary String?. Do not flag this as a required/nullable mismatch.

Applied to files:

  • autogpt_platform/backend/backend/copilot/config.py
📚 Learning: 2026-02-26T17:02:22.448Z
Learnt from: Pwuts
Repo: Significant-Gravitas/AutoGPT PR: 12211
File: .pre-commit-config.yaml:160-179
Timestamp: 2026-02-26T17:02:22.448Z
Learning: Keep the pre-commit hook pattern broad for autogpt_platform/backend to ensure OpenAPI schema changes are captured. Do not narrow to backend/api/ alone, since the generated schema depends on Pydantic models across multiple directories (backend/data/, backend/blocks/, backend/copilot/, backend/integrations/, backend/util/). Narrowing could miss schema changes and cause frontend type desynchronization.

Applied to files:

  • autogpt_platform/backend/backend/copilot/config.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
  • autogpt_platform/backend/backend/copilot/sdk/service.py
📚 Learning: 2026-03-04T08:04:35.881Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12273
File: autogpt_platform/backend/backend/copilot/tools/workspace_files.py:216-220
Timestamp: 2026-03-04T08:04:35.881Z
Learning: In the AutoGPT Copilot backend, ensure that SVG images are not treated as vision image types by excluding 'image/svg+xml' from INLINEABLE_MIME_TYPES and MULTIMODAL_TYPES in tool_adapter.py; the Claude API supports PNG, JPEG, GIF, and WebP for vision. SVGs (XML text) should be handled via the text path instead, not the vision path.

Applied to files:

  • autogpt_platform/backend/backend/copilot/config.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
  • autogpt_platform/backend/backend/copilot/sdk/service.py
📚 Learning: 2026-03-05T15:42:08.207Z
Learnt from: ntindle
Repo: Significant-Gravitas/AutoGPT PR: 12297
File: .claude/skills/backend-check/SKILL.md:14-16
Timestamp: 2026-03-05T15:42:08.207Z
Learning: In Python files under autogpt_platform/backend (recursively), rely on poetry run format to perform formatting (Black + isort) and linting (ruff). Do not run poetry run lint as a separate step after poetry run format, since format already includes linting checks.

Applied to files:

  • autogpt_platform/backend/backend/copilot/config.py
  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
  • autogpt_platform/backend/backend/copilot/sdk/service.py
📚 Learning: 2026-03-04T12:19:39.243Z
Learnt from: majdyz
Repo: Significant-Gravitas/AutoGPT PR: 12279
File: autogpt_platform/backend/backend/copilot/tools/base.py:184-188
Timestamp: 2026-03-04T12:19:39.243Z
Learning: In autogpt_platform/backend/backend/copilot/tools/, ensure that anonymous users always pass user_id=None to tool execution methods. The anon_ prefix (e.g., anon_123) is used only for PostHog/analytics distinct_id and must not be used as an actual user_id. Use a simple truthiness check on user_id (e.g., if user_id: ... else: ... or a dedicated is_authenticated flag) to distinguish anonymous from authenticated users, and review all tool execution call sites within this directory to prevent accidentally forwarding an anon_ user_id to tools.

Applied to files:

  • autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
🔇 Additional comments (8)
autogpt_platform/backend/backend/copilot/config.py (1)

114-123: LGTM! Centralized E2B activation logic is clean.

The new e2b_active property correctly encapsulates the two-field check (use_e2b_sandbox and bool(e2b_api_key)) into a single source of truth. The timeout reduction from 12h to 4h aligns with the per-turn pause strategy described in the PR.

Note: The relevant code snippet from routes.py:269 still uses the old config.use_e2b_sandbox and config.e2b_api_key pattern instead of config.e2b_active. Consider updating that call site for consistency (outside this file's scope).

autogpt_platform/backend/backend/copilot/sdk/service.py (3)

63-63: LGTM!

Import correctly updated to include the new pause_sandbox function.


794-800: LGTM!

Clean migration to the centralized e2b_active property and correctly passing sandbox_timeout to get_or_create_sandbox.


1419-1429: LGTM! Pause placement correctly addresses billing concerns.

The pause is now executed BEFORE the transcript upload (which has no timeout via asyncio.shield), ensuring billing stops at turn end regardless of upload duration. This correctly addresses the previous review concern.

One minor observation: pause_sandbox returns bool and swallows exceptions internally via _act_on_sandbox, so the except BaseException here will typically not catch anything from pause_sandbox itself. However, keeping the try-except is harmless and defensive.

autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py (4)

1-26: LGTM!

Clear and comprehensive module docstring explaining the lifecycle: turn start (connect/auto-resume), execution, turn end (pause), and session delete (kill). Cost control section explains the safety-net behavior of on_timeout: pause.


44-50: LGTM!

Good separation of concerns: _E2B_SANDBOX_TIMEOUT (4h) for e2b running time before auto-pause, and _REDIS_SANDBOX_TTL (48h) for Redis key retention. The longer Redis TTL ensures paused sandboxes remain reconnectable across the session lifetime.


163-213: LGTM! Timeout added to connect+action path.

The _act_on_sandbox helper correctly addresses the previous review concern by wrapping the connect and action calls in asyncio.wait_for(..., timeout=10). This prevents indefinite hangs in the finally block.

The consolidated error handling and optional Redis cleanup (delete_redis flag) cleanly separates pause (no Redis delete) from kill (Redis delete) behavior.


216-236: LGTM!

Clean public API for pause_sandbox and kill_sandbox using the shared _act_on_sandbox helper. The lambda approach for passing the async method is appropriate.

Comment thread autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py Outdated
Remove standalone _REDIS_SANDBOX_TTL constant; pass session_ttl from
service.py so the sandbox Redis key expires with the session lifecycle.
Comment thread autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py Outdated
…data

Reverts the ChatSession.metadata DB column approach in favour of a simpler
Redis-based design:

- sandbox_id stored at copilot:e2b:sandbox:{session_id} (30-day TTL)
- Redis creation lock (SET NX) still used to serialise concurrent creates
- lifecycle={"on_timeout": "pause"} with 1-hour timeout as safety net for
  a missed explicit pause; paused sandboxes are free and cleaned up by the
  E2B project-level "paused sandbox lifetime" setting — no scheduler needed
- pause_sandbox() is now fire-and-forget (asyncio.create_task) in service.py
  so it does not block the response or the transcript upload
- kill_sandbox() clears the Redis key on success; on failure the key stays
  so the kill can be retried
- Removes: ChatSessionMetadata model, schema.prisma metadata column,
  migration, 4 DB functions in db.py + db_manager.py, scheduler cleanup job
- No-op migration SQL left in place so Prisma history stays intact
Comment thread autogpt_platform/backend/backend/copilot/tools/e2b_sandbox.py
- Remove separate _CREATION_LOCK_PREFIX key; the sandbox key now doubles
  as a creation lock via a "creating" sentinel value (SET NX)
- Add on_timeout param to get_or_create_sandbox (Literal["kill","pause"])
  wired to new ChatConfig.e2b_sandbox_on_timeout field (default: "pause")
- Rename pause_timeout -> timeout in get_or_create_sandbox signature to
  match the E2B SDK param name
- Fix TTLs: _E2B_TIMEOUT=1h (was 4h in config), _SANDBOX_ID_TTL=48h (was 30d)
- Update tests: sentinel-based waiting, on_timeout coverage, sentinel no-op
  tests for kill/pause
Comment thread autogpt_platform/backend/backend/copilot/sdk/service.py Outdated
majdyz added 2 commits March 8, 2026 21:36
- Update module docstring: on_timeout is now configurable, not hardcoded
- Simplify _act_on_sandbox inner coroutine (_connect_and_act -> _run, one-liner)
…ut to 3h

E2B timeout is wall-clock (not idle-based), so 1h was too conservative
for long-running agent turns. Raise to 3h to match the 48h Redis key TTL
and give sessions room to breathe between explicit per-turn pauses.

Also drops the empty no-op migration that was left as a placeholder.
@majdyz
majdyz requested a review from ntindle March 8, 2026 14:55
majdyz added 3 commits March 8, 2026 22:21
… type hints

- _try_reconnect now calls _set_stored_sandbox_id after a successful
  reconnect to reset the 48h TTL, preventing active sessions from
  losing their sandbox key right before expiry
- Fix docstring: "default: 1 h" → "default: 3 h" in get_or_create_sandbox
- Replace Coroutine (unparameterized) with Awaitable[Any] for _act_on_sandbox fn param
- Simplify _setup_e2b: single walrus check instead of two separate if-branches
- Update tests: assert redis.set called on reconnect (TTL refresh)
Add pause_sandbox_direct() which takes the already-connected sandbox
object and calls sandbox.pause() directly, skipping the Redis lookup and
AsyncSandbox.connect() round-trip that pause_sandbox() would make.

Use it in service.py turn teardown (fire-and-forget) where e2b_sandbox
is already in scope, saving ~100-300ms of E2B HTTP latency per turn.
pause_sandbox() is kept for callers that only have session_id (e.g.
external cleanup jobs).

Add 3 tests for pause_sandbox_direct (success, failure, timeout).

@autogpt-reviewer autogpt-reviewer left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

PR #12330 — feat(copilot): E2B sandbox auto-pause between turns to eliminate idle billing

Author: majdyz (Zamil Majdy) | Files: 8 changed (+591/−206) | CI: ✅ All green


🎯 Verdict: APPROVE_WITH_CONDITIONS

What This PR Does

Introduces per-turn sandbox pausing for E2B sandboxes in the CoPilot, eliminating idle compute billing between user turns. Paused sandboxes cost $0 and auto-resume transparently on the next turn via AsyncSandbox.connect(). Also adds explicit sandbox cleanup on session deletion and upgrades the E2B SDK from v1 to v2.


Specialist Findings

🛡️ Security ✅ — No issues. API keys properly handled (never logged/exposed), Redis keys use server-generated UUIDs, error handling doesn't leak sensitive info, resource cleanup is thorough with safety nets.

🏗️ Architecture ✅ — Clean lifecycle abstraction via _act_on_sandbox() helper. Dual-timeout model (4h E2B / 48h Redis) correctly addresses paused-sandbox-outlives-key issue. e2b_active centralizes activation logic. Minor: routes.py:269 still uses old pattern instead of e2b_active; inline import in model.py masks coupling.

Performance ✅ — Net positive. Eliminates idle billing (primary win). Per-turn pause adds negligible latency (runs after response, in finally block). Resume adds ~1-3s to turn start (acceptable trade-off). All operations bounded by 10s asyncio.wait_for timeout. No N+1 or scaling concerns.

🧪 Testing ⚠️ — Good coverage (+271 lines) for pause/kill lifecycle. Gap: no timeout test for pause_sandbox (equivalent exists for kill_sandbox). Missing service-layer test verifying pause ordering (pause before transcript upload). Missing e2b_active property unit tests.

📖 Quality ✅ — High quality. Comprehensive module docstring, clear constant naming, proper function docstrings, consistent error handling. Minor nitpick: hardcoded 10 timeout could be a named constant.

📦 Product ✅ — Strong alignment. Significant cost savings (potentially 90%+ reduction in sandbox compute costs). Transparent to users — state preserved across pause/resume. E2B SDK v2 migration is contained.

📬 Discussion ✅ — All substantive discussions resolved. CodeRabbit's concerns about Redis TTL, operation timeouts, pause ordering, and API validity were all addressed by the author. CodeRabbit repeatedly (incorrectly) claimed lifecycle={"on_timeout": "pause"} was invalid — author confirmed it's the stable v2 API (e2b 2.15.1). Routine merge conflicts with dependabot PRs remain.

🔎 QA ✅ — Environment healthy. Frontend loads correctly, backend responds, no regressions. E2B-specific pause/resume cycle requires real API keys (author verified E2E manually with real credentials).


Conditions for Merge

  1. Add pause_sandbox timeout test (e2b_sandbox_test.py) — follows existing pattern from test_kill_timeout_returns_false. Easy fix, ensures parity.

Should Fix (Follow-up OK)

  1. routes.py:269 — Use config.e2b_active instead of config.use_e2b_sandbox and config.e2b_api_key for consistency
  2. e2b_sandbox.py:196 — Extract hardcoded 10 timeout to _E2B_API_TIMEOUT_SECONDS constant
  3. config.py — Add unit tests for e2b_active property (3 cases: both true, missing key, disabled)
  4. service.py — Add integration test verifying pause-before-transcript-upload ordering

Risk Assessment

Merge risk: LOW | Rollback: EASY (feature-flagged via use_e2b_sandbox + e2b_api_key)

The change is well-contained to the copilot E2B sandbox module. The dual safety nets (explicit per-turn pause + auto-pause on timeout) ensure robustness. The E2B SDK v2 migration only touches the sandbox module and a trivial rename in code_executor.py.


Reviewed by 8 automated specialists: Security, Architecture, Performance, Testing, Quality, Product, Discussion, QA.

@autogpt-reviewer autogpt-reviewer left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

PR #12330 — feat(copilot): E2B sandbox auto-pause between turns to eliminate idle billing

Author: majdyz (Zamil Majdy) | Files: 8 changed (+591/−206) | CI: ✅ All green (tests 3.11/3.12/3.13, types, CodeQL, Snyk)


🎯 Verdict: APPROVE_WITH_CONDITIONS

What This PR Does

Introduces per-turn sandbox pausing for E2B sandboxes in the CoPilot, eliminating idle compute billing between user turns. Paused sandboxes cost $0 and auto-resume transparently on the next turn via AsyncSandbox.connect(). Also adds explicit sandbox cleanup on session deletion and upgrades the E2B SDK from v1 to v2.


Specialist Findings

🛡️ Security ✅ — No issues. API keys properly handled (never logged/exposed), Redis keys use server-generated UUIDs with user-scoped session ownership enforced at DB layer. _CREATING_SENTINEL can't collide with real sandbox IDs (sb-* format). SET NX creation lock prevents duplicate sandbox creation. E2B SDK v2 transitive deps (dockerfile-parse, wcmatch, bracex, rich) have no known CVEs.

🏗️ Architecture ✅ — Clean lifecycle abstraction via _act_on_sandbox() helper. Dual-timeout model (4h E2B auto-pause / 48h Redis TTL) correctly addresses paused-sandbox-outlives-key issue. e2b_active centralizes activation logic. Fire-and-forget asyncio.create_task() pattern for pause is correctly anchored via _background_tasks set. Minor: routes.py:269 still uses old pattern instead of e2b_active; inline import in model.py masks coupling.

Performance ✅ — Net positive. Eliminates idle billing (primary win — potentially 90%+ cost reduction). Per-turn pause adds negligible latency (runs after response in finally block via fire-and-forget task). Resume adds ~1-3s to turn start (acceptable trade-off). All operations bounded by 10s asyncio.wait_for timeout. No N+1 or scaling concerns.

🧪 Testing ⚠️ — Good coverage (+271 lines) for pause/kill lifecycle including happy path, no-sandbox, creating state, connection failure, and pause→reconnect cycle. Gaps: no timeout test for pause_sandbox (equivalent exists for kill_sandbox), missing service-layer test verifying pause ordering, missing e2b_active property unit tests.

📖 Quality ✅ — High quality. Comprehensive module docstring documenting 4 lifecycle stages, clear constant naming (_E2B_SANDBOX_TIMEOUT, _REDIS_SANDBOX_TTL), proper function docstrings, consistent error handling with actionable warning-level messages. Walrus operator for type-safe e2b_api_key narrowing is idiomatic.

📦 Product ✅ — Strong alignment. Significant cost savings with transparent user experience — state preserved across pause/resume. E2B SDK v2 migration is contained (sandbox module + trivial code_executor.py rename). Feature-flagged via use_e2b_sandbox + e2b_api_key.

📬 Discussion ✅ — All substantive discussions resolved. Author addressed CodeRabbit concerns about Redis TTL (added separate redis_ttl param), operation timeouts (added asyncio.wait_for), pause ordering (moved before transcript upload), and API validity (confirmed lifecycle={"on_timeout": "pause"} is stable v2 API in e2b 2.15.1, not beta). Routine merge conflicts with dependabot PRs remain.

🔎 QA ✅ — Environment healthy. Frontend loads correctly, backend responds, Storybook renders, no regressions. E2B-specific pause/resume cycle requires real API keys — author verified E2E manually with real credentials.


Conditions for Merge

  1. Add pause_sandbox timeout test (e2b_sandbox_test.py) — follows existing pattern from test_kill_timeout_returns_false. Ensures parity in edge case coverage.

Should Fix (Follow-up OK)

  1. routes.py:269 — Use config.e2b_active instead of config.use_e2b_sandbox and config.e2b_api_key for consistency with the new centralized property
  2. e2b_sandbox.py — Extract hardcoded 10 timeout to _E2B_API_TIMEOUT_SECONDS constant
  3. config.py — Add unit tests for e2b_active property (3 cases: both true, missing key, disabled)
  4. service.py — Add integration test verifying pause-before-transcript-upload ordering
  5. Verify E2B project-level "paused sandbox lifetime" setting aligns with _SANDBOX_ID_TTL (48h) to prevent orphaned paused sandboxes accumulating

Risk Assessment

Merge risk: LOW | Rollback: EASY (feature-flagged via use_e2b_sandbox + e2b_api_key)

The change is well-contained to the copilot E2B sandbox module. Dual safety nets (explicit per-turn pause + auto-pause on timeout) ensure robustness. The E2B SDK v2 migration only touches the sandbox module and a trivial rename in code_executor.py. Best-effort pause/kill semantics handle race conditions gracefully.

@ntindle Clean, well-designed cost optimization PR. One condition: add the missing pause_sandbox timeout test for parity with kill_sandbox. Otherwise ready to merge.


Reviewed by 8 automated specialists: Security ✅, Architecture ✅, Performance ✅, Testing ⚠️, Quality ✅, Product ✅, Discussion ✅, QA ✅

…autopause

- Extract hardcoded `10` E2B API timeout to `_E2B_API_TIMEOUT_SECONDS` constant
- Add `e2b_active` bool property to `ChatConfig` as single source of truth
- Update `routes.py` DELETE /sessions to use `cfg.e2b_active` for consistency
- Add `test_pause_timeout_returns_false` to `TestPauseSandbox` (parity with kill)
- Add `config_test.py` with 3 unit tests for `e2b_active` (both-set, missing-key, disabled)
- Add pause-before-transcript ordering test to `service_test.py`

@autogpt-reviewer autogpt-reviewer left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

PR #12330 — feat(copilot): E2B sandbox auto-pause between turns to eliminate idle billing

Author: majdyz (Zamil Majdy) | Files: 10 changed (+687/−203) | CI: ✅ All green (tests 3.11/3.12/3.13, types, CodeQL, Snyk)
Re-review iteration 2 — Delta: 1 commit (8b39c77c423b1e)


🎯 Verdict: APPROVE

All conditions from the previous review (APPROVE_WITH_CONDITIONS) have been addressed by commit c423b1e.

What This PR Does

Introduces per-turn sandbox pausing for E2B sandboxes in the CoPilot, eliminating idle compute billing between user turns. Paused sandboxes cost $0 and auto-resume transparently on the next turn via AsyncSandbox.connect(). Also adds explicit sandbox cleanup on session deletion and upgrades the E2B SDK from v1 to v2.

Previous Conditions — All Resolved ✅

Condition Status
Add pause_sandbox timeout test test_pause_timeout_returns_false added — verifies False return + Redis entry preserved
Should-fix: routes.py use e2b_active ✅ Now uses _e2b_cfg.e2b_active with assert for type narrowing
Should-fix: Extract timeout constant _E2B_API_TIMEOUT_SECONDS = 10 used consistently
Should-fix: e2b_active property tests ✅ 3 tests in new config_test.py (both enabled, missing key, disabled)
Should-fix: Pause-before-upload ordering test test_pause_task_scheduled_before_transcript_upload added

Specialist Findings

🛡️ Security ✅ — No issues. API keys properly handled, Redis keys use server-generated UUIDs, assert pattern is type-narrowing only (no security gate — guarded by e2b_active check). E2B SDK v2 transitive deps have no known CVEs.

🏗️ Architecture ✅ — Clean. e2b_active property is a good single-source-of-truth. _act_on_sandbox() helper properly separates pause (keep Redis) vs kill (clear Redis) semantics. assert in routes.py is standard type-narrowing pattern. Minor: active_e2b_api_key property doesn't filter empty strings (inconsistent with e2b_active's bool() check) — non-blocking since callers handle it.

Performance ✅ — No behavioral change from delta. _E2B_API_TIMEOUT_SECONDS constant extraction is purely cosmetic. Overall profile remains solid: fire-and-forget pause adds zero latency, all E2B API calls bounded by 10s timeout.

🧪 Testing ✅ — Previous condition fully satisfied. test_pause_timeout_returns_false mirrors test_kill_timeout_keeps_redis. Commit goes beyond minimum: adds 12+ new tests covering happy paths, error handling, timeouts, sentinel filtering, pause→resume cycle, lifecycle parameter passthrough, and leak prevention. New config_test.py and service ordering test are well-structured.

📖 Quality ✅ — High quality. Constants properly named, docstrings comprehensive, test methods descriptive. Minor: _e2b_cfg leading underscore on local var is unusual (cosmetic). _E2B_TIMEOUT constant duplicates config.e2b_sandbox_timeout default (both 10800) — consider removing the dead constant in a follow-up.

📦 Product ✅ — Zero functional changes in the delta. All additions are tests and code quality improvements. No user-facing impact.

📬 Discussion ✅ — All conditions from our previous review addressed. Author correctly dismissed repeated CodeRabbit/Sentry false positives about lifecycle={"on_timeout": "pause"} and sb.pause() being invalid (confirmed stable API in e2b SDK 2.15.1). No new human reviewer concerns.

🔎 QA ✅ — Frontend loads correctly, signup works, copilot UI operational, backend healthy. E2B sandbox pause/resume cannot be tested end-to-end without live API keys, but comprehensive unit tests cover all branches.

QA Screenshots

landing-page
copilot-dashboard
copilot-ready

Nice-to-have (Follow-up OK)

  1. config.py:136active_e2b_api_key should use self.e2b_active guard instead of raw self.use_e2b_sandbox for consistency (handles empty string edge case)
  2. e2b_sandbox.py:61_E2B_TIMEOUT = 10800 duplicates config.e2b_sandbox_timeout default; consider removing dead constant
  3. routes.py:271_e2b_cfg leading underscore on local variable is non-standard; plain e2b_cfg is more natural
  4. Verify E2B project-level "paused sandbox lifetime" setting aligns with _SANDBOX_ID_TTL (48h)

Risk Assessment

Merge risk: LOW | Rollback: EASY (feature-flagged via use_e2b_sandbox + e2b_api_key)

The change is well-contained to the copilot E2B sandbox module. Dual safety nets (explicit per-turn pause + auto-pause on timeout) ensure robustness. Best-effort pause/kill semantics handle race conditions gracefully. All previous conditions resolved.

@ntindle Previous conditions met — all clear to merge. Clean cost optimization with comprehensive test coverage.


Reviewed by 8 automated specialists: Security ✅, Architecture ✅, Performance ✅, Testing ✅, Quality ✅, Product ✅, Discussion ✅, QA ✅

…ew comments

- active_e2b_api_key: use self.e2b_active guard (handles empty-string edge case)
- Remove dead _E2B_TIMEOUT constant (duplicated config.e2b_sandbox_timeout default);
  make get_or_create_sandbox timeout param required (all callers already pass it)
- routes.py: rename _e2b_cfg -> e2b_cfg (no leading underscore on plain local var)
- _SANDBOX_ID_TTL comment already documents 48h alignment with E2B paused lifetime
@majdyz
majdyz requested a review from Pwuts March 9, 2026 07:42
@majdyz
majdyz enabled auto-merge March 9, 2026 09:13
@majdyz
majdyz added this pull request to the merge queue Mar 9, 2026
@github-project-automation github-project-automation Bot moved this from 🆕 Needs initial review to 👍🏼 Mergeable in AutoGPT development kanban Mar 9, 2026
Merged via the queue into dev with commit aef5f6d Mar 9, 2026
23 checks passed
@majdyz
majdyz deleted the feat/e2b-autopause branch March 9, 2026 15:29
@github-project-automation github-project-automation Bot moved this from 👍🏼 Mergeable to ✅ Done in AutoGPT development kanban Mar 9, 2026
okxint pushed a commit to okxint/AutoGPT that referenced this pull request Mar 24, 2026
… billing (Significant-Gravitas#12330)

## Summary

### Before
- E2B sandboxes ran continuously between CoPilot turns, billing for idle
time
- Sandbox timeout caused **termination** (kill), losing all session
state
- No explicit cleanup when sessions were deleted — sandboxes leaked
- Single timeout concept with no separation between pause and kill
semantics

### After
- **Per-turn pause**: `pause_sandbox()` is called in the `finally` block
after every CoPilot turn, stopping billing instantly between turns
(paused sandboxes cost \$0 compute)
- **Auto-pause safety net**: Sandboxes are created with
`lifecycle={"on_timeout": "pause"}` (`pause_timeout` = 4h default) so
they auto-pause rather than terminate if the explicit pause is missed
- **Auto-reconnect**: `AsyncSandbox.connect()` in e2b SDK v2
auto-resumes paused sandboxes transparently — no extra code needed
- **Session delete cleanup**: `kill_sandbox()` is now called in
`delete_chat_session()` to explicitly terminate sandboxes and free
resources
- **Two distinct timeouts**: `pause_timeout` (4h, e2b auto-pause) vs
`redis_ttl` (12h, session key lifetime)

### Key Changes

| File | Change |
|------|--------|
| `pyproject.toml` | Bump `e2b-code-interpreter` `1.x` → `2.x` |
| `e2b_sandbox.py` | Add `pause_sandbox()`, `kill_sandbox()`,
`_act_on_sandbox()` helper; `lifecycle={"on_timeout": "pause"}`;
separate `pause_timeout` / `redis_ttl` params |
| `sdk/service.py` | Call `pause_sandbox()` in `finally` block
**before** transcript upload; use walrus operator for type-safe
`e2b_api_key` narrowing |
| `model.py` | Call `kill_sandbox()` in `delete_chat_session()`; inline
import to avoid circular dependency |
| `config.py` | Add `e2b_active` property; rename `e2b_sandbox_timeout`
default to 4h |
| `e2b_sandbox_test.py` | Add `test_pause_then_reconnect_reuses_sandbox`
test; update all `sandbox_timeout` → `pause_timeout` |

### Verified E2E
- Used real `E2B_API_KEY` from k8s dev cluster to manually verify:
sandbox created → paused → `is_running() == False` → reconnected via
`connect()` → state preserved → killed

## Test plan
- [x] `poetry run pytest backend/copilot/tools/e2b_sandbox_test.py` —
all 19 tests pass
- [x] CI: test (3.11, 3.12, 3.13), types — all green
- [x] E2E verified with real E2B credentials
okxint pushed a commit to okxint/AutoGPT that referenced this pull request Mar 24, 2026
… billing (Significant-Gravitas#12330)

## Summary

### Before
- E2B sandboxes ran continuously between CoPilot turns, billing for idle
time
- Sandbox timeout caused **termination** (kill), losing all session
state
- No explicit cleanup when sessions were deleted — sandboxes leaked
- Single timeout concept with no separation between pause and kill
semantics

### After
- **Per-turn pause**: `pause_sandbox()` is called in the `finally` block
after every CoPilot turn, stopping billing instantly between turns
(paused sandboxes cost \$0 compute)
- **Auto-pause safety net**: Sandboxes are created with
`lifecycle={"on_timeout": "pause"}` (`pause_timeout` = 4h default) so
they auto-pause rather than terminate if the explicit pause is missed
- **Auto-reconnect**: `AsyncSandbox.connect()` in e2b SDK v2
auto-resumes paused sandboxes transparently — no extra code needed
- **Session delete cleanup**: `kill_sandbox()` is now called in
`delete_chat_session()` to explicitly terminate sandboxes and free
resources
- **Two distinct timeouts**: `pause_timeout` (4h, e2b auto-pause) vs
`redis_ttl` (12h, session key lifetime)

### Key Changes

| File | Change |
|------|--------|
| `pyproject.toml` | Bump `e2b-code-interpreter` `1.x` → `2.x` |
| `e2b_sandbox.py` | Add `pause_sandbox()`, `kill_sandbox()`,
`_act_on_sandbox()` helper; `lifecycle={"on_timeout": "pause"}`;
separate `pause_timeout` / `redis_ttl` params |
| `sdk/service.py` | Call `pause_sandbox()` in `finally` block
**before** transcript upload; use walrus operator for type-safe
`e2b_api_key` narrowing |
| `model.py` | Call `kill_sandbox()` in `delete_chat_session()`; inline
import to avoid circular dependency |
| `config.py` | Add `e2b_active` property; rename `e2b_sandbox_timeout`
default to 4h |
| `e2b_sandbox_test.py` | Add `test_pause_then_reconnect_reuses_sandbox`
test; update all `sandbox_timeout` → `pause_timeout` |

### Verified E2E
- Used real `E2B_API_KEY` from k8s dev cluster to manually verify:
sandbox created → paused → `is_running() == False` → reconnected via
`connect()` → state preserved → killed

## Test plan
- [x] `poetry run pytest backend/copilot/tools/e2b_sandbox_test.py` —
all 19 tests pass
- [x] CI: test (3.11, 3.12, 3.13), types — all green
- [x] E2E verified with real E2B credentials
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

3 participants