Skip to content

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.

  1. Install

    Terminal window
    pip install baponi[deepagents]
  2. Set your API keys

    Terminal window
    export BAPONI_API_KEY=sk-your-key-here
    export ANTHROPIC_API_KEY=your-anthropic-key-here

    Get a Baponi key from console.baponi.ai. 1,000 free credits/month, no credit card.

  3. Create and run your agent

    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.

Your agent gets these tools automatically:

ToolExample
executeRun pip install pandas, compile code, start servers
read_fileInspect source files, configs, logs
write_fileGenerate code, create configs, save results
edit_fileRefactor code, fix bugs, update settings
lsExplore project structure
grepFind function definitions, error patterns, TODOs
globFind all *.py files, match naming patterns

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:

# 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")]})
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:

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

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.

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().

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

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

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

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():

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

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.

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.

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.

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

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

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.