Skip to main content
Sandboxes are in private preview. APIs and features may change as we iterate. Sign up for the waitlist to get access.
The LangSmith SDK provides a programmatic interface to create and interact with sandboxes.

Install

# uv
uv add "langsmith[sandbox] @ git+https://github.com/langchain-ai/langsmith-sdk#subdirectory=python"

# pip
pip install "langsmith[sandbox] @ git+https://github.com/langchain-ai/langsmith-sdk#subdirectory=python"
The [sandbox] extra for Python installs websockets, which enables real-time streaming and timeout=0. Without it, run() falls back to HTTP automatically. For TypeScript, install the optional ws package for WebSocket streaming:
npm install ws

Create and run a sandbox

from langsmith.sandbox import SandboxClient

# Client uses LANGSMITH_ENDPOINT and LANGSMITH_API_KEY from environment
client = SandboxClient()

# Create a template (defines the container image)
client.create_template(
    name="python-sandbox",
    image="python:3.12-slim",
)

# Create a sandbox from the template and run code
with client.sandbox(template_name="python-sandbox") as sb:
    result = sb.run("python -c 'print(2 + 2)'")
    print(result.stdout)  # "4\n"
    print(result.success)  # True

Run commands

Every run() call returns an ExecutionResult with stdout, stderr, exit_code, and success.
with client.sandbox(template_name="my-sandbox") as sb:
    result = sb.run("echo 'Hello, World!'")

    print(result.stdout)     # "Hello, World!\n"
    print(result.stderr)     # ""
    print(result.exit_code)  # 0
    print(result.success)    # True

    # Commands that fail return non-zero exit codes
    result = sb.run("exit 1")
    print(result.success)    # False
    print(result.exit_code)  # 1

Stream output

For long-running commands, stream output in real time using callbacks or a CommandHandle.

Stream with callbacks

import sys

with client.sandbox(template_name="my-sandbox") as sb:
    result = sb.run(
        "make build",
        timeout=600,
        on_stdout=lambda s: print(s, end=""),
        on_stderr=lambda s: print(s, end="", file=sys.stderr),
    )
    print(f"\nBuild {'succeeded' if result.success else 'failed'}")

Stream with CommandHandle

Set wait=False to get a CommandHandle for full control over the output stream.
with client.sandbox(template_name="my-sandbox") as sb:
    handle = sb.run("make build", timeout=600, wait=False)

    print(f"Command ID: {handle.command_id}")

    for chunk in handle:
        prefix = "OUT" if chunk.stream == "stdout" else "ERR"
        print(f"[{prefix}] {chunk.data}", end="")

    result = handle.result
    print(f"\nExit code: {result.exit_code}")

Send stdin and kill commands

with client.sandbox(template_name="my-sandbox") as sb:
    handle = sb.run(
        "python -c 'name = input(\"Name: \"); print(f\"Hello {name}\")'",
        timeout=30,
        wait=False,
    )

    for chunk in handle:
        if "Name:" in chunk.data:
            handle.send_input("World\n")
        print(chunk.data, end="")

    result = handle.result
Kill a running command:
with client.sandbox(template_name="my-sandbox") as sb:
    handle = sb.run("python server.py", timeout=0, wait=False)

    for chunk in handle:
        print(chunk.data, end="")
        if "Ready" in chunk.data:
            break

    handle.kill()

Reconnect to a running command

If a client disconnects, reconnect using the command ID:
with client.sandbox(template_name="my-sandbox") as sb:
    handle = sb.run("make build", timeout=600, wait=False)
    command_id = handle.command_id

    # Later, possibly in a different process
    handle = sb.reconnect(command_id)
    for chunk in handle:
        print(chunk.data, end="")
    result = handle.result

File operations

Read and write files in the sandbox:
with client.sandbox(template_name="my-python") as sb:
    # Write a file
    sb.write("/app/script.py", "print('Hello from file!')")

    # Run the script
    result = sb.run("python /app/script.py")
    print(result.stdout)  # "Hello from file!\n"

    # Read a file (returns bytes)
    content = sb.read("/app/script.py")
    print(content.decode())  # "print('Hello from file!')"

    # Write binary files
    sb.write("/app/data.bin", b"\x00\x01\x02\x03")

Command lifecycle and TTL

The sandbox daemon manages command session lifecycles with two timeout mechanisms:
  • Session TTL (finished commands): After a command finishes, its session remains in memory for a TTL period. During this window you can reconnect to retrieve output. After the TTL expires, the session is cleaned up.
  • Idle timeout (running commands): Running commands with no connected clients are killed after an idle timeout (default: 5 minutes). The idle timer resets each time a client connects. Set to -1 for no idle timeout.

Combine lifecycle options

with client.sandbox(template_name="my-sandbox") as sb:
    # Long-running task: 30-min idle timeout, 1-hour session TTL
    handle = sb.run(
        "python train.py",
        timeout=0,              # No command timeout
        idle_timeout=1800,      # Kill after 30min with no clients
        ttl_seconds=3600,       # Keep session for 1 hour after exit
        wait=False,
    )

    # Fire-and-forget: no idle timeout, infinite TTL
    handle = sb.run(
        "python background_job.py",
        timeout=0,
        idle_timeout=-1,        # Never kill due to idle
        ttl_seconds=-1,         # Keep session forever
        wait=False,
    )
Set kill_on_disconnect=True (Python) or killOnDisconnect: true (TypeScript) to kill the command immediately when the last client disconnects, instead of waiting for the idle timeout.

TCP tunnels (Python)

Access any TCP service running inside a sandbox as if it were local. The tunnel opens a local TCP port and forwards connections through a WebSocket to the target port inside the sandbox.
import psycopg2

# Template uses the official postgres:16 image
sb = client.create_sandbox(template_name="my-postgres")
pg_handle = sb.run(
    "POSTGRES_HOST_AUTH_METHOD=trust docker-entrypoint.sh postgres",
    timeout=0,
    wait=False,
)
import time; time.sleep(6)  # Wait for Postgres to start

try:
    with sb.tunnel(remote_port=5432, local_port=25432) as t:
        conn = psycopg2.connect(
            host="127.0.0.1",
            port=t.local_port,
            user="postgres",
        )
        cursor = conn.cursor()
        cursor.execute("SELECT version()")
        print(cursor.fetchone())
        conn.close()
finally:
    pg_handle.kill()
    client.delete_sandbox(sb.name)
Tunnels work with any TCP service (Redis, HTTP servers, etc.) and you can open multiple tunnels simultaneously:
with sb.tunnel(remote_port=5432, local_port=25432) as t1, \
     sb.tunnel(remote_port=6379, local_port=26379) as t2:
    # Use both Postgres and Redis simultaneously
    pass

Async support (Python)

The Python SDK provides a full async client:
from langsmith.sandbox import AsyncSandboxClient

async def main():
    async with AsyncSandboxClient() as client:
        await client.create_template(name="async-python", image="python:3.12-slim")

        async with await client.sandbox(template_name="async-python") as sb:
            result = await sb.run("python -c 'print(1 + 1)'")
            print(result.stdout)  # "2\n"

            await sb.write("/app/test.txt", "async content")
            content = await sb.read("/app/test.txt")
            print(content.decode())

            # Async streaming
            handle = await sb.run("make build", timeout=600, wait=False)
            async for chunk in handle:
                print(chunk.data, end="")
            result = await handle.result

Error handling

Both SDKs provide typed exceptions for specific error handling:
from langsmith.sandbox import (
    SandboxClientError,       # Base exception
    ResourceCreationError,    # Provisioning failed
    ResourceNotFoundError,    # Resource doesn't exist
    ResourceTimeoutError,     # Operation timed out
    SandboxNotReadyError,     # Sandbox not ready yet
    SandboxConnectionError,   # Network/WebSocket error
    CommandTimeoutError,      # Command exceeded timeout
    QuotaExceededError,       # Quota limit reached
)

try:
    with client.sandbox(template_name="my-sandbox") as sb:
        result = sb.run("sleep 999", timeout=10)
except CommandTimeoutError as e:
    print(f"Command timed out: {e}")
except ResourceNotFoundError as e:
    print(f"{e.resource_type} not found: {e}")
except SandboxClientError as e:
    print(f"Error: {e}")
For more details, see the sandbox SDK reference on GitHub for Python or TypeScript.