---
title: "LangChain Deep Agents"
description: "Add sandboxed code execution to your LangChain Deep Agent in 3 lines. BaponiSandbox gives your agent file read/write, code execution, grep, and glob with persistent state, per-call overrides, real-time streaming, and lifecycle management."
url: https://baponi.ai/docs/guides/deep-agents
lastUpdated: 2026-03-22
---
# LangChain Deep Agents
Give your LangChain Deep Agent a full sandbox environment with file operations, code execution, and persistent state in 3 lines of code.

## Get started

1. **Install**

   ```bash
   pip install baponi[deepagents]
   ```

2. **Set your API keys**

   ```bash
   export BAPONI_API_KEY=sk-your-key-here
   export ANTHROPIC_API_KEY=your-anthropic-key-here
   ```

   Get a Baponi key from [console.baponi.ai](https://console.baponi.ai). 1,000 free credits/month, no credit card.

3. **Create and run your agent**

   ```python
   from baponi.deepagents import BaponiSandbox
   from deepagents import create_deep_agent

   with BaponiSandbox() as sandbox:
       agent = create_deep_agent("anthropic:claude-sonnet-4-6", backend=sandbox)

       result = agent.invoke({"messages": [("human", "Use Python to calculate the SHA-256 hash of 'hello world'")]})
       print(result["messages"][-1].content)
   ```

The agent writes a Python script, executes it inside an isolated sandbox, and returns the result. Files, installed packages, and environment changes persist across calls.

## What your agent can do

Your agent gets these tools automatically:

| Tool | Example |
|------|---------|
| **execute** | Run `pip install pandas`, compile code, start servers |
| **read_file** | Inspect source files, configs, logs |
| **write_file** | Generate code, create configs, save results |
| **edit_file** | Refactor code, fix bugs, update settings |
| **ls** | Explore project structure |
| **grep** | Find function definitions, error patterns, TODOs |
| **glob** | Find all `*.py` files, match naming patterns |

## Persistent sessions

Files and installed packages persist automatically across calls within the same sandbox. Your agent can install dependencies, write code, then test it, all building on previous steps.

To resume a session from a separate process (e.g., a new API request or a cron job), pass the same `thread_id`:

```python
# Process A
with BaponiSandbox(thread_id="my-project") as sandbox:
    agent = create_deep_agent("anthropic:claude-sonnet-4-6", backend=sandbox)
    agent.invoke({"messages": [("human", "Install pandas and write a CSV parser")]})

# Process B - picks up right where A left off
with BaponiSandbox(thread_id="my-project") as sandbox:
    agent = create_deep_agent("anthropic:claude-sonnet-4-6", backend=sandbox)
    agent.invoke({"messages": [("human", "Add error handling to the CSV parser you wrote")]})
```

## Configuration

```python
sandbox = BaponiSandbox(
    thread_id="data-pipeline-run-42",  # session ID for persistent state
    timeout=60,                         # default 60s, Pro plan supports up to 3600
    metadata={"user": "agent-1"},       # attached to every execution for audit
    env_vars={"ENV": "prod"},           # environment variables injected into every execution
    sub_paths=["/data/shared"],         # storage path scoping for BYOB mounts
)
```

For self-hosted Baponi deployments:

```python
sandbox = BaponiSandbox(
    base_url="https://baponi.internal.company.com",
    api_key="sk-your-enterprise-key",
)
```

```python
# Before
agent = create_deep_agent(model, backend=OtherSandbox())

# After
from baponi.deepagents import BaponiSandbox
agent = create_deep_agent(model, backend=BaponiSandbox())
```

## Per-call overrides

Override constructor defaults on individual `execute()` or `aexecute()` calls. Pass `None` (the default) to use the constructor value. Pass an explicit value to replace the constructor default for that call only - values are replaced, not merged.

```python
sandbox = BaponiSandbox(
    env_vars={"ENV": "prod"},
    metadata={"team": "data"},
    sub_paths=["/data/shared"],
)

# Override env_vars for this call only
result = sandbox.execute("echo $ENV", env_vars={"ENV": "staging"})

# Override metadata for this call only
result = sandbox.execute("cmd", metadata={"step": "cleanup"})

# Narrow storage scope to a specific subdirectory for this call only
result = sandbox.execute("ls /data/shared", sub_paths=["/data/shared/2026-03-22"])

# Override timeout for this call only
result = sandbox.execute("long-running-task", timeout=300)
```

All four parameters (`timeout`, `env_vars`, `metadata`, `sub_paths`) are available as per-call overrides on `execute()`, `aexecute()`, and `execute_stream()`.

## Context managers

`BaponiSandbox` supports `with` and `async with` to ensure HTTP connections are properly closed:

```python
# Sync
with BaponiSandbox(api_key="sk-...", thread_id="session-1") as sandbox:
    result = sandbox.execute("echo hello")
# connections closed automatically

# Async
async with BaponiSandbox(api_key="sk-...") as sandbox:
    result = await sandbox.aexecute("echo hello")
```

You can also call `sandbox.close()` or `await sandbox.aclose()` manually. Both are idempotent.

## Streaming

`execute_stream()` provides real-time NDJSON streaming for long-running commands. Events arrive as they happen rather than waiting for the full result.

```python
stream = sandbox.execute_stream("pip install -r requirements.txt && python train.py")
with stream:
    for event in stream:
        print(event)
    result = stream.get_final_result()
```

`execute_stream()` accepts the same per-call overrides as `execute()`:

```python
stream = sandbox.execute_stream(
    "python train.py",
    timeout=600,
    env_vars={"CUDA_VISIBLE_DEVICES": "0"},
)
```

Streaming requires the Pro plan or higher. See [pricing](/pricing) for details.

## Sandbox provider

`BaponiSandboxProvider` manages multiple sandbox instances by ID. Use it when your application needs to create, reuse, and clean up sandboxes programmatically - for example, one sandbox per user session in a server.

```python
from baponi.deepagents import BaponiSandboxProvider

provider = BaponiSandboxProvider(api_key="sk-...")

# Create or reuse sandbox by ID
sandbox = provider.get_or_create(sandbox_id="user-123")
result = sandbox.execute("echo hello")

# Different user gets a different sandbox
sandbox_b = provider.get_or_create(sandbox_id="user-456")

# Clean up when done
provider.delete("user-123")
```

The provider also has async variants: `await provider.aget_or_create()` and `await provider.adelete()`.

Constructor arguments (`timeout`, `metadata`, `env_vars`, `sub_paths`) are passed through to every sandbox the provider creates. Configuration falls back to `BAPONI_API_KEY` and `BAPONI_BASE_URL` environment variables when not provided explicitly.

## FAQ

### Do I need to manage sandbox lifecycles?

For most use cases, no. Create a `BaponiSandbox` and use it - Baponi handles provisioning, isolation, and cleanup. If you need to manage multiple sandboxes programmatically (e.g., one per user in a multi-tenant server), use `BaponiSandboxProvider`.

### What languages can my agent run?

Python, Node.js, and Bash are available in the default sandbox image.

### Does it support async and streaming?

Yes. `BaponiSandbox` works with both sync (`execute`) and async (`aexecute`) out of the box. For real-time output, use `execute_stream()` to get NDJSON events as they arrive (Pro plan required).

### Can I override settings per execution?

Yes. `execute()`, `aexecute()`, and `execute_stream()` accept `timeout`, `env_vars`, `metadata`, and `sub_paths` as keyword arguments. Passing a value overrides the constructor default for that call only.