open-swe/agent/utils/sandbox.py
Ramon Nogueira 5925a90a95
feat: migrate LangSmith sandbox creation to snapshot API (#1201)
* feat: migrate LangSmith sandbox creation to snapshot API

Replaces the template-based sandbox flow (DEFAULT_SANDBOX_TEMPLATE_NAME /
DEFAULT_SANDBOX_TEMPLATE_IMAGE) with the new snapshot-based flow.

- New required env var DEFAULT_SANDBOX_SNAPSHOT_ID (UUID of a pre-built
  LangSmith snapshot; build out-of-band via UI or SandboxClient.create_snapshot)
- Optional DEFAULT_SANDBOX_SNAPSHOT_FS_CAPACITY_BYTES overrides the root FS
  size at boot (default 32 GiB)
- Startup-time validation via a FastAPI lifespan hook: the server refuses
  to boot with a clear ValueError if SANDBOX_TYPE=langsmith and
  DEFAULT_SANDBOX_SNAPSHOT_ID is unset, so failures surface in boot logs
  rather than on the first thread
- Reconnect-to-existing-sandbox path unchanged
- Docs (INSTALLATION.md, CUSTOMIZATION.md) updated to describe the new
  snapshot workflow

* fix: format create_sandbox_snapshot.py to pass ruff

---------

Co-authored-by: aran-yogesh <yogesh.mahendran@langchain.dev>
2026-04-21 12:17:19 -07:00

49 lines
1.8 KiB
Python

import os
from agent.integrations.daytona import create_daytona_sandbox
from agent.integrations.langsmith import create_langsmith_sandbox
from agent.integrations.local import create_local_sandbox
from agent.integrations.modal import create_modal_sandbox
from agent.integrations.runloop import create_runloop_sandbox
SANDBOX_FACTORIES = {
"langsmith": create_langsmith_sandbox,
"daytona": create_daytona_sandbox,
"modal": create_modal_sandbox,
"runloop": create_runloop_sandbox,
"local": create_local_sandbox,
}
def create_sandbox(sandbox_id: str | None = None):
"""Create or reconnect to a sandbox using the configured provider.
The provider is selected via the SANDBOX_TYPE environment variable.
Supported values: langsmith (default), daytona, modal, runloop, local.
Args:
sandbox_id: Optional existing sandbox ID to reconnect to.
Returns:
A sandbox backend implementing SandboxBackendProtocol.
"""
sandbox_type = os.getenv("SANDBOX_TYPE", "langsmith")
factory = SANDBOX_FACTORIES.get(sandbox_type)
if not factory:
supported = ", ".join(sorted(SANDBOX_FACTORIES))
raise ValueError(f"Invalid sandbox type: {sandbox_type}. Supported types: {supported}")
return factory(sandbox_id)
def validate_sandbox_startup_config() -> None:
"""Validate the configured sandbox provider's env vars at server startup.
Raises ValueError if the active provider's configuration is invalid.
Called from the FastAPI lifespan hook so errors surface at boot rather
than on the first sandbox creation.
"""
sandbox_type = os.getenv("SANDBOX_TYPE", "langsmith")
if sandbox_type == "langsmith":
from agent.integrations.langsmith import LangSmithProvider
LangSmithProvider.validate_startup_config()