> ## Documentation Index
> Fetch the complete documentation index at: https://docs.langchain.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sandbox SDK usage

> Create and manage sandboxes programmatically with the Python or TypeScript SDK.

<Warning>
  Sandboxes are in private preview. APIs and features may change as we iterate. [Sign up for the waitlist](https://www.langchain.com/langsmith-sandboxes-waitlist?ref=docs.langchain.com) to get access.
</Warning>

The [LangSmith SDK](/langsmith/reference) provides a programmatic interface to create and interact with sandboxes.

## Install

<CodeGroup>
  ```bash Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  # 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"
  ```

  ```bash TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  npm install langsmith
  # or
  yarn add langsmith
  ```
</CodeGroup>

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:

```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
npm install ws
```

## Create and run a sandbox

Every sandbox boots from a **snapshot** — a filesystem image backed by a Docker image. Pick an existing snapshot or create a new one (see [Snapshots](/langsmith/sandbox-snapshots) for the full build / capture / CRUD flow); every example below uses a `snapshot_id` / `snapshotId` you already have.

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  from langsmith.sandbox import SandboxClient

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

  # Pick an existing snapshot, or build one with client.create_snapshot(...)
  snapshot_id = "550e8400-e29b-41d4-a716-446655440000"

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

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  import { SandboxClient } from "langsmith/experimental/sandbox";

  // Client uses LANGSMITH_ENDPOINT and LANGSMITH_API_KEY from environment
  const client = new SandboxClient();

  // Pick an existing snapshot, or build one with client.createSnapshot(...)
  const snapshotId = "550e8400-e29b-41d4-a716-446655440000";

  // Create a sandbox from the snapshot and run code
  const sandbox = await client.createSandbox(snapshotId);
  const result = await sandbox.run("node -e 'console.log(2 + 2)'");
  console.log(result.stdout); // "4\n"

  // Don't forget to clean up
  await sandbox.delete();
  ```
</CodeGroup>

## Run commands

Every `run()` call returns an `ExecutionResult` with `stdout`, `stderr`, `exit_code`, and `success`.

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  with client.sandbox(snapshot_id=snapshot_id) 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
  ```

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  const sandbox = await client.createSandbox(snapshotId);
  try {
    const result = await sandbox.run("echo 'Hello, World!'");

    console.log(result.stdout);     // "Hello, World!\n"
    console.log(result.stderr);     // ""
    console.log(result.exit_code);  // 0

    // Pass environment variables and working directory
    const envResult = await sandbox.run("echo $MY_VAR", {
      env: { MY_VAR: "test-value" },
      cwd: "/tmp",
    });
  } finally {
    await sandbox.delete();
  }
  ```
</CodeGroup>

## Stream output

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

### Stream with callbacks

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  import sys

  with client.sandbox(snapshot_id=snapshot_id) 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'}")
  ```

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  const result = await sandbox.run("make build", {
    timeout: 600,
    onStdout: (data) => process.stdout.write(data),
    onStderr: (data) => process.stderr.write(data),
  });
  console.log(`Exit code: ${result.exit_code}`);
  ```
</CodeGroup>

### Stream with CommandHandle

Set `wait=False` to get a `CommandHandle` for full control over the output stream.

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  with client.sandbox(snapshot_id=snapshot_id) 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}")
  ```

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  const handle = await sandbox.run("python train.py", {
    wait: false,
    timeout: 600,
  });

  console.log(`Command ID: ${handle.commandId}`);
  console.log(`PID: ${handle.pid}`);

  for await (const chunk of handle) {
    if (chunk.stream === "stdout") {
      process.stdout.write(chunk.data);
    } else {
      process.stderr.write(chunk.data);
    }
  }

  const result = await handle.result;
  console.log(`Exit code: ${result.exit_code}`);
  ```
</CodeGroup>

### Send stdin and kill commands

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  with client.sandbox(snapshot_id=snapshot_id) 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
  ```

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  const handle = await sandbox.run("python -i", { wait: false });

  // Send input to stdin
  handle.sendInput("print(2 + 2)\n");
  handle.sendInput("exit()\n");

  for await (const chunk of handle) {
    process.stdout.write(chunk.data);
  }
  ```
</CodeGroup>

Kill a running command:

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  with client.sandbox(snapshot_id=snapshot_id) 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()
  ```

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  const handle = await sandbox.run("sleep 300", { wait: false });
  handle.kill();

  const result = await handle.result;
  console.log(result.exit_code); // non-zero
  ```
</CodeGroup>

### Reconnect to a running command

If a client disconnects, reconnect using the command ID:

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  with client.sandbox(snapshot_id=snapshot_id) 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
  ```

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  const handle = await sandbox.run("long-task", { wait: false });
  const commandId = handle.commandId;

  // Later, or from a different client
  const newHandle = await sandbox.reconnect(commandId);
  for await (const chunk of newHandle) {
    process.stdout.write(chunk.data);
  }
  ```
</CodeGroup>

## File operations

Read and write files in the sandbox:

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  with client.sandbox(snapshot_id=snapshot_id) 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")
  ```

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  const sandbox = await client.createSandbox(snapshotId);
  try {
    // Write a file (string content)
    await sandbox.write("/app/script.py", "print('Hello from file!')");

    // Run the script
    const result = await sandbox.run("python /app/script.py");
    console.log(result.stdout);  // "Hello from file!\n"

    // Read a file (returns Uint8Array)
    const content = await sandbox.read("/app/script.py");
    console.log(new TextDecoder().decode(content));

    // Write binary files
    await sandbox.write("/app/data.bin", new Uint8Array([0x00, 0x01, 0x02, 0x03]));
  } finally {
    await sandbox.delete();
  }
  ```
</CodeGroup>

## Sandbox lifetime and retention

Sandboxes are governed by a two-stage retention model anchored to **idle
activity** and the **`stopped`** state.

| Field                       | What it controls                                                                                                                                                                                                                    | When it fires                                                           |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| `idle_ttl_seconds`          | The launcher stops the sandbox after this many seconds of inactivity. Any command execution or file I/O resets the timer. `0` disables the idle stop.                                                                               | Default `600` (10 minutes) when omitted.                                |
| `delete_after_stop_seconds` | Once the sandbox enters the `stopped` state, this timer starts. After it elapses, the sandbox row + filesystem clone are permanently deleted by a server-side sweep. `0` disables stop-anchored deletion (manual cleanup required). | Server applies its configured default (typically 14 days) when omitted. |

Both values must be multiples of 60 (minute resolution). The full lifecycle is:

```
running ──(idle for idle_ttl_seconds)──▶ stopped ──(delete_after_stop_seconds)──▶ deleted
```

You can also call `stop_sandbox` / `stopSandbox` explicitly — that also
populates `stopped_at` and starts the deletion timer.

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  # Default retention (server defaults: 10-min idle stop, 14-day delete)
  with client.sandbox(snapshot_id=snapshot_id) as sb:
      sb.run("echo hello")

  # Aggressive: stop after 5 min idle, delete 1 hour after stop
  sb = client.create_sandbox(
      snapshot_id=snapshot_id,
      idle_ttl_seconds=300,
      delete_after_stop_seconds=3600,
  )

  # Long-running: never auto-stop, delete 7 days after manual stop
  sb = client.create_sandbox(
      snapshot_id=snapshot_id,
      idle_ttl_seconds=0,
      delete_after_stop_seconds=604800,
  )

  # Update retention on an existing sandbox
  sb = client.update_sandbox(
      sb.name,
      idle_ttl_seconds=1800,
      delete_after_stop_seconds=2592000,  # 30 days
  )
  ```

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  // Default retention (server defaults applied)
  const sandbox = await client.createSandbox(snapshotId);

  // Aggressive: stop after 5 min idle, delete 1 hour after stop
  const sb = await client.createSandbox(snapshotId, {
    idleTtlSeconds: 300,
    deleteAfterStopSeconds: 3600,
  });

  // Long-running: never auto-stop, delete 7 days after manual stop
  const longRunning = await client.createSandbox(snapshotId, {
    idleTtlSeconds: 0,
    deleteAfterStopSeconds: 604800,
  });

  // Update retention on an existing sandbox
  await client.updateSandbox(sb.name, {
    idleTtlSeconds: 1800,
    deleteAfterStopSeconds: 2592000, // 30 days
  });
  ```
</CodeGroup>

## 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

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  with client.sandbox(snapshot_id=snapshot_id) 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,
      )
  ```

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  const sandbox = await client.createSandbox(snapshotId);
  try {
    // Long-running task: 30-min idle timeout, 1-hour session TTL
    const handle = await sandbox.run("python train.py", {
      timeout: 0,              // No command timeout
      idleTimeout: 1800,       // Kill after 30min with no clients
      ttlSeconds: 3600,        // Keep session for 1 hour after exit
      wait: false,
    });

    // Fire-and-forget: no idle timeout, infinite TTL
    const bg = await sandbox.run("python background_job.py", {
      timeout: 0,
      idleTimeout: -1,         // Never kill due to idle
      ttlSeconds: -1,          // Keep session forever
      wait: false,
    });
  } finally {
    await sandbox.delete();
  }
  ```
</CodeGroup>

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.

## Service URLs (Python)

Access an HTTP service running inside a sandbox via an authenticated URL. You can open it in a browser, call it from code, or share it with a teammate.

```python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
with client.sandbox(snapshot_id=snapshot_id) as sb:
    sb.run("python -m http.server 8000", timeout=0, wait=False)

    svc = sb.service(port=8000)

    # Open in a browser
    print(svc.browser_url)

    # Or make requests with built-in helpers (auth is injected automatically)
    resp = svc.get("/api/data")
    resp = svc.post("/api/data", json={"key": "value"})
```

For more details, including use cases, REST API access, and a full FastAPI example, see [Service URLs](/langsmith/sandbox-service-urls).

## 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.

```python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
import psycopg2

# Snapshot built from the official postgres:16 image
sb = client.create_sandbox(snapshot_id=postgres_snapshot_id)
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:

```python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
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:

```python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
from langsmith.sandbox import AsyncSandboxClient

async def main():
    async with AsyncSandboxClient() as client:
        async with await client.sandbox(snapshot_id=snapshot_id) 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

            # Async service URLs
            svc = await sb.service(port=8000)
            resp = await svc.get("/api/data")
            url = await svc.get_service_url()
            token = await svc.get_token()
```

## Trace sandbox activity

Pass LangSmith tracing environment variables through the `env` parameter on `run()` to send traces from code running inside a sandbox. Call `flush()` before the process exits to ensure all traces are delivered.

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  from langsmith.sandbox import SandboxClient

  client = SandboxClient()

  tracing_env = {
      "LANGSMITH_API_KEY": "lsv2_pt_...",
      "LANGSMITH_ENDPOINT": "https://api.smith.langchain.com",
      "LANGSMITH_TRACING": "true",
      "LANGSMITH_PROJECT": "my-sandbox-traces",
  }

  with client.sandbox(snapshot_id=snapshot_id) as sandbox:
      sandbox.run("pip install langsmith", timeout=120, env=tracing_env)
      result = sandbox.run("python3 my_agent.py", env=tracing_env)
      print(result.stdout)
  ```

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  import { SandboxClient } from "langsmith/experimental/sandbox";

  const client = new SandboxClient();

  const tracingEnv = {
    LANGSMITH_API_KEY: "lsv2_pt_...",
    LANGSMITH_ENDPOINT: "https://api.smith.langchain.com",
    LANGSMITH_TRACING: "true",
    LANGSMITH_PROJECT: "my-sandbox-traces",
  };

  const sandbox = await client.createSandbox(snapshotId);
  try {
    await sandbox.run("pip install langsmith", { timeout: 120, env: tracingEnv });
    const result = await sandbox.run("python3 my_agent.py", { env: tracingEnv });
    console.log(result.stdout);
  } finally {
    await sandbox.delete();
  }
  ```
</CodeGroup>

Inside the sandbox, any LangSmith-instrumented code (`@traceable`, LangChain, LangGraph) automatically picks up the tracing configuration from the injected environment variables.

<Warning>
  Always call `flush()` before the sandbox process exits — `langsmith.Client().flush()` in Python or `await new Client().flush()` in TypeScript. Without it, traces may be lost because the container is destroyed when the command finishes.
</Warning>

## Error handling

Both SDKs provide typed exceptions for specific error handling:

<CodeGroup>
  ```python Python theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  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(snapshot_id=snapshot_id) 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}")
  ```

  ```ts TypeScript theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
  import {
    LangSmithSandboxError,
    LangSmithResourceNotFoundError,
    LangSmithResourceTimeoutError,
    LangSmithSandboxConnectionError,
    LangSmithCommandTimeoutError,
    LangSmithQuotaExceededError,
  } from "langsmith/experimental/sandbox";

  try {
    const sandbox = await client.createSandbox("not-a-real-snapshot");
    await sandbox.delete();
  } catch (e) {
    if (e instanceof LangSmithResourceNotFoundError) {
      console.log(`${e.resourceType} not found: ${e.message}`);
    } else if (e instanceof LangSmithResourceTimeoutError) {
      console.log(`Timeout waiting for ${e.resourceType}: ${e.message}`);
    } else if (e instanceof LangSmithSandboxError) {
      console.log(`Error: ${e.message}`);
    }
  }
  ```
</CodeGroup>

<Note>
  For more details, see the sandbox SDK reference on GitHub for [Python](https://github.com/langchain-ai/langsmith-sdk/tree/main/python/langsmith/sandbox) or [TypeScript](https://github.com/langchain-ai/langsmith-sdk/tree/main/js/src/experimental/sandbox).
</Note>

***

<div className="source-links">
  <Callout icon="terminal-2">
    [Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
  </Callout>

  <Callout icon="edit">
    [Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/langsmith/sandbox-sdk.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
  </Callout>
</div>
