The missing piece in every LangChain tutorial
Every LangChain tutorial shows you how to build an agent that calls tools. But when it comes to code execution, they all do the same thing: use PythonREPLTool which runs exec() inline in your process, or point at a Jupyter kernel on your laptop. No isolation, no browsers, no real file system, no parallel execution.
The "code interpreter" tools in LangChain are toys. They run in your process memory. They can't install system packages, can't git clone, can't open a browser, can't persist files between runs. The moment you need your agent to do anything real — research the web, clone a repo, run a test suite — those tools break down.
Sandbox Platform fills this gap. Your LangChain agent gets tools that provision real Linux VMs, execute arbitrary shell commands in isolation, launch cloud browsers controllable via CDP, and read/write files to a persistent workspace.
Setup
# Install dependencies pip install langchain langchain-openai langchain-anthropic httpx # Set your keys export OPENAI_API_KEY="sk-..." # or ANTHROPIC_API_KEY for Claude export SANDBOX_API_KEY="ab0t_sk_live_..." export SANDBOX_URL="https://sandbox.dev.ab0t.com"
Building the tools
LangChain's @tool decorator turns any Python function into a tool the agent can call. The docstring becomes the tool description that the LLM reads to decide when to use it.
Sandbox client
# sandbox_tools.py import os, httpx from langchain_core.tools import tool _URL = os.environ.get("SANDBOX_URL", "https://sandbox.dev.ab0t.com") _KEY = os.environ["SANDBOX_API_KEY"] _client = httpx.Client(base_url=_URL, headers={"Authorization": f"Bearer {_KEY}"}, timeout=120) _sandbox_id: str | None = None
Tool definitions
@tool def create_sandbox(name: str, instance_type: str = "ab0t.medium") -> str: """Create an isolated Linux VM for code execution. Must be called before execute_command or save_file. Use ab0t.micro for light tasks, ab0t.medium for builds, ab0t.gpu for GPU. Returns sandbox ID when ready.""" global _sandbox_id r = _client.post("/api/sandboxes", json={ "name": name, "instance_type": instance_type, "auto_stop_minutes": 60 }) r.raise_for_status() d = r.json() _sandbox_id = d["sandbox_id"] return f"Sandbox {_sandbox_id} created ({d['status']}). Cost: ${d.get('hourly_cost','0.04')}/hr." @tool def execute_command(command: str) -> str: """Run a shell command in the active sandbox. Use for git, pip, npm, pytest, any CLI tool. Returns stdout, stderr, exit code. Install packages as needed.""" if not _sandbox_id: return "No sandbox. Call create_sandbox first." r = _client.post(f"/api/sandboxes/{_sandbox_id}/execute", json={"command": command}) r.raise_for_status() d = r.json() out = d.get("stdout", "") err = d.get("stderr", "") code = d.get("exit_code", 0) return f"{out}\n{err}\nExit: {code}".strip() @tool def launch_browser(url: str = "") -> str: """Launch a cloud Chrome browser. Returns a CDP URL for programmatic control and an access URL for visual viewing. Use for web research, scraping, testing.""" r = _client.post("/api/browsers", json={"browser_type": "chrome", "homepage_url": url}) r.raise_for_status() d = r.json() return f"Browser {d['container_id']} launched.\nCDP: {d.get('cdp_url','pending')}\nView: {d.get('access_url','pending')}" @tool def save_file(filename: str, content: str) -> str: """Write a file to the sandbox workspace. Use for scripts, configs, data.""" if not _sandbox_id: return "No sandbox." _client.post(f"/api/sandboxes/{_sandbox_id}/files", json={"filename": filename, "content": content}) return f"Saved /workspace/{filename}" @tool def stop_sandbox() -> str: """Stop the sandbox. Data persists. Billing stops.""" global _sandbox_id if _sandbox_id: _client.post(f"/api/sandboxes/{_sandbox_id}/stop") sid = _sandbox_id; _sandbox_id = None return f"Sandbox {sid} stopped." return "No active sandbox." ALL_TOOLS = [create_sandbox, execute_command, launch_browser, save_file, stop_sandbox]
Creating the ReAct agent
A ReAct agent reasons step by step and calls tools as needed. LangChain's create_react_agent handles the loop — the model decides what to do next based on tool outputs.
# agent.py from langchain_openai import ChatOpenAI from langchain.agents import create_react_agent, AgentExecutor from langchain_core.prompts import ChatPromptTemplate from sandbox_tools import ALL_TOOLS llm = ChatOpenAI(model="gpt-4.1", temperature=0) # Or: from langchain_anthropic import ChatAnthropic # llm = ChatAnthropic(model="claude-sonnet-4-6") prompt = ChatPromptTemplate.from_messages([ ("system", """You are a developer with access to cloud sandboxes. RULES: 1. Always create_sandbox before running commands. 2. Use execute_command for shell operations (git, pip, tests, builds). 3. Use launch_browser for web research, scraping, UI testing. 4. Use save_file to write code or data. 5. Always stop_sandbox when the task is done. You have full root access on the sandbox. Install anything you need."""), ("human", "{input}"), ("placeholder", "{agent_scratchpad}"), ]) agent = create_react_agent(llm, ALL_TOOLS, prompt) executor = AgentExecutor(agent=agent, tools=ALL_TOOLS, verbose=True, max_iterations=25)
Example: research + code in one task
This is the use case that shows why a LangChain agent needs both a browser and a terminal. The agent researches a topic on the web, then writes code based on what it found.
# run.py result = executor.invoke({ "input": """ 1. Create a sandbox. 2. Launch a browser and research the top 5 Python web frameworks in 2026. Visit their official sites and GitHub repos. Note star counts and key features. 3. In the sandbox terminal, write a Python script that generates a comparison table as a Markdown file, using the data you collected. 4. Run the script and show me the output. 5. Stop the sandbox. """ }) print(result["output"])
The agent will:
- Call
create_sandbox("research-workspace") - Call
launch_browser("https://github.com/topics/python-web-framework") - Navigate to each framework's page, extract star counts and features
- Call
save_file("compare.py", "...")with a Python script - Call
execute_command("python compare.py") - Call
stop_sandbox() - Return the Markdown comparison table
This is what makes the sandbox different from a code interpreter or a scraping API. The agent has a browser for the web AND a terminal for code, and it switches between them as the task requires. No other LangChain tool gives you both.
LangGraph version: stateful workflows
For more complex workflows, LangGraph gives you a directed graph with explicit state management. Each node in the graph can use sandbox tools, and the state persists between nodes.
from langgraph.graph import StateGraph, MessagesState from langgraph.prebuilt import ToolNode from sandbox_tools import ALL_TOOLS # Bind tools to the model model_with_tools = llm.bind_tools(ALL_TOOLS) def agent_node(state: MessagesState): return {"messages": [model_with_tools.invoke(state["messages"])]} def should_continue(state: MessagesState): last = state["messages"][-1] if last.tool_calls: return "tools" return "end" # Build the graph graph = StateGraph(MessagesState) graph.add_node("agent", agent_node) graph.add_node("tools", ToolNode(ALL_TOOLS)) graph.set_entry_point("agent") graph.add_conditional_edges("agent", should_continue, {"tools": "tools", "end": "__end__"}) graph.add_edge("tools", "agent") app = graph.compile() # Run it result = app.invoke({ "messages": [("human", "Create a sandbox, clone fastapi, run tests, report failures.")] }) print(result["messages"][-1].content)
Works with any model
The tools are model-agnostic. Swap the LLM and everything else stays the same:
| Provider | Import | Model |
|---|---|---|
| OpenAI | langchain_openai.ChatOpenAI | gpt-4.1, gpt-4o |
| Anthropic | langchain_anthropic.ChatAnthropic | claude-sonnet-4-6, claude-opus-4-6 |
langchain_google_genai.ChatGoogleGenerativeAI | gemini-2.5-pro | |
| Local | langchain_ollama.ChatOllama | llama3, codestral |
Opus 4.6 and GPT-4.1 are the most reliable at multi-step tool-calling workflows. Smaller models (Haiku, GPT-4o-mini) work for simple tasks but may need more explicit instructions for complex research-and-code sequences.
Cost
| Task | Sandbox | Duration | Cost |
|---|---|---|---|
| Research + code (browser + terminal) | ab0t.medium + Chrome | ~20 min | $0.03 |
| Clone + test a repo | ab0t.medium | ~15 min | $0.01 |
| 10-site parallel scrape | 10x Chrome | ~10 min | $0.07 |
| Long research session | ab0t.medium + Chrome | ~2 hrs | $0.12 |
Tips
Set max_iterations
AgentExecutor(max_iterations=25) prevents the agent from looping forever. For complex tasks, increase to 40-50. For simple tasks, 10 is enough.
Use verbose=True during development
It shows every tool call, every observation, every reasoning step. Essential for debugging when the agent goes off-track.
Break big tasks into sub-tasks
Instead of "research 50 companies and write a report", give the agent a list of 5 and call it 10 times. Each run gets a fresh sandbox and clean state.
Combine with LangChain memory
Use ConversationBufferMemory to let the agent remember results from previous tool calls in the same session. This helps it build on earlier findings without re-researching.
Troubleshooting
Agent doesn't create sandbox first
Put it in the system prompt in bold: "ALWAYS call create_sandbox before any other tool." Or make the first message explicitly ask for it.
Tool output too long
Command outputs can be thousands of lines. Truncate in the tool: return out[:3000]. LLMs don't need the full npm install log.
Agent gets stuck in a loop
max_iterations prevents infinite loops. If the agent retries the same failing command, add error-handling guidance to the system prompt: "If a command fails twice, try a different approach."
What's next
Give your LangChain agent a computer
Browsers, terminals, files. Real compute for real agents.
Get Started Free