Give a CrewAI Agent a Code Interpreter That Runs in a MicroVM
Updated October 27, 2026 .md
Short answer: Write a CrewAI BaseTool whose _run sends the agent's Python to a Sandboxer microVM through sandboxer-sdk, then pass it in the agent's tools list. The sandbox starts on the first call and keeps files and packages between calls. CrewAI's own CodeInterpreterTool has been removed, and CrewAI now points you to a sandbox service.
What changed in CrewAI#
CrewAI used to ship a built-in CodeInterpreterTool. Per CrewAI's documentation, it ran code in a Docker container (image code-interpreter:latest), fell back to a restricted Python environment when Docker wasn't available, and had an unsafe_mode that ran code directly on the host. The same page notes that the container can see the current working directory. That page now says the tool has been removed from crewai-tools, that the agent's allow_code_execution and code_execution_mode settings are deprecated, and that you should use a dedicated sandbox service instead.
We checked in crewai 1.15.30: the Agent fields allow_code_execution and code_execution_mode describe themselves as "Deprecated. CodeInterpreterTool is no longer available."
Old CodeInterpreterTool |
The tool on this page | |
|---|---|---|
| Where code runs | Docker on your machine, or a restricted Python, or the host in unsafe_mode |
A Sandboxer microVM with its own kernel |
| What you install locally | Docker | Nothing beyond crewai and sandboxer-sdk |
| Can it see your files? | The container can see the working directory | No; only what you write into the sandbox |
| Packages | libraries_used; not allowed in the restricted fallback |
libraries_used, installed with pip in the VM |
| State between calls | Not covered in its docs | Files and packages stay until you delete the sandbox |
| Status in current CrewAI | Removed | A custom BaseTool, the documented extension point |
Install#
We used Python 3.12, crewai 1.15.30 and sandboxer-sdk 0.1.1.
python3.12 -m venv venv && . venv/bin/activate
pip install crewai sandboxer-sdk
export SANDBOXER_API_KEY=sbx_live_…Install sandboxer-sdk, not sandboxer: that PyPI name belongs to an unrelated project. The package imports as sandboxer.
The tool#
This follows CrewAI's custom tool pattern: a BaseTool subclass with a pydantic args_schema and a _run method. The argument names match the old tool's (code, libraries_used), so prompts written for it still fit.
import shlex
import uuid
from typing import List, Optional, Type
from crewai.tools import BaseTool
from pydantic import BaseModel, Field, PrivateAttr
from sandboxer import Sandbox, Sandboxer
class RunPythonInput(BaseModel):
code: str = Field(..., description="Python 3 code to run. Print what you want to see.")
libraries_used: List[str] = Field(
default_factory=list,
description="PyPI packages the code needs, installed with pip before it runs.",
)
class SandboxerCodeInterpreterTool(BaseTool):
name: str = "sandboxer_code_interpreter"
description: str = (
"Runs Python 3 code in an isolated cloud microVM (Ubuntu). "
"Files and installed packages persist between calls. Returns exit code, stdout and stderr."
)
args_schema: Type[BaseModel] = RunPythonInput
sandbox_name: str = "crewai-code"
sandbox_type: str = "small"
ttl_seconds: int = 1800
timeout_seconds: int = 300
_client: Optional[Sandboxer] = PrivateAttr(default=None)
_sandbox: Optional[Sandbox] = PrivateAttr(default=None)
def _get_sandbox(self) -> Sandbox:
if self._sandbox is None:
self._client = Sandboxer() # reads SANDBOXER_API_KEY
self._sandbox = self._client.create(
type=self.sandbox_type, ttl_seconds=self.ttl_seconds, name=self.sandbox_name
) # waits until ready
return self._sandbox
def _run(self, code: str, libraries_used: Optional[List[str]] = None) -> str:
sandbox = self._get_sandbox()
if libraries_used:
pkgs = " ".join(shlex.quote(p) for p in libraries_used)
install = sandbox.exec(
f"pip install -q --break-system-packages {pkgs}", timeout_seconds=self.timeout_seconds
)
if install.exit_code != 0:
return f"pip install failed (exit {install.exit_code})\n{install.stderr[-2000:]}"
path = f"crewai/{uuid.uuid4().hex[:8]}.py"
sandbox.files.write(path, code)
result = sandbox.exec(["python3", path], timeout_seconds=self.timeout_seconds)
out = f"exit code {result.exit_code}\n{result.stdout}"
if result.stderr:
out += f"\nstderr:\n{result.stderr}"
return out[-8000:]
def close(self, delete: bool = False) -> None:
if self._sandbox is not None:
self._sandbox.delete() if delete else self._sandbox.stop()
self._sandbox = NoneA few choices worth knowing:
- The code goes in as a file (
files.write) and runs aspython3 <file>, so it is never pasted into a shell command. - A non-zero exit is returned to the agent as text, not raised. The model sees the traceback and can fix its code.
ttl_seconds=1800makes the sandbox stop itself after 30 minutes if your program never callsclose().- The output is cut to the last 8,000 characters so a noisy script doesn't flood the model's context.
Call it directly#
This is the part we ran on a real small sandbox, without a model: three calls to the tool, the way CrewAI would make them.
tool = SandboxerCodeInterpreterTool()
try:
print(tool._run(code="import platform, os\nprint(platform.platform())\nprint('user', os.getuid())\nopen('notes.txt','w').write('42')"))
print(tool.run(code="import os, humanize\nprint(open('notes.txt').read())\nprint(humanize.naturalsize(os.path.getsize('/usr/bin/python3')))", libraries_used=["humanize"]))
print(tool.run(code="1/0"))
finally:
tool.close(delete=True)exit code 0
Linux-6.18.56-sandboxer-x86_64-with-glibc2.39
user 1000
exit code 0
42
8.0 MB
exit code 1
stderr:
Traceback (most recent call last):
File "/home/user/crewai/e1650c80.py", line 1, in <module>
1/0
~^~
ZeroDivisionError: division by zeroIn our single run on October 27, 2026, the first call took 4.7 seconds, including creating the sandbox. The second, which installed humanize and read the file the first call wrote, took 1.9 seconds. The kernel line shows the code ran in the VM, not on the machine running CrewAI.
Attach it to an agent#
Pass the tool in tools=[...] as in CrewAI's tools docs:
from crewai import Agent, Crew, Task
from sandbox_tool import SandboxerCodeInterpreterTool
code_tool = SandboxerCodeInterpreterTool(sandbox_name="crewai-analyst")
analyst = Agent(
role="Data analyst",
goal="Answer questions by writing and running Python",
backstory="You check every number by running code.",
tools=[code_tool],
)
task = Task(
description="Compute the 30th Fibonacci number and the sum of primes below 10,000.",
expected_output="Both numbers, with the code you ran.",
agent=analyst,
)
crew = Crew(agents=[analyst], tasks=[task])
try:
print(crew.kickoff())
finally:
code_tool.close() # stop: snapshot the disk and stop billingWe built this agent, task and crew with crewai 1.15.30 and confirmed the tool is registered, and that no sandbox is created until the tool is first called. We did not run crew.kickoff(), because it needs a model API key. So there's no model transcript here. The tool calls the model would make are the ones shown above.
Share one sandbox or give each agent its own#
One tool instance means one sandbox. Give two agents the same instance and they share files: one can write data.csv and the other can read it. Give each agent its own instance, with its own sandbox_name, when their work must stay apart.
For a crew that runs again later, keep tool._sandbox.id, and next time reopen it with Sandboxer().get(sandbox_id), calling resume() first if it has stopped. The files and installed packages are still there.
FAQ#
Do I need Docker for this?#
No. The code runs in a Sandboxer microVM in the cloud, so the machine running CrewAI only needs Python and the two packages.
What does a sandbox cost while the crew runs?#
A small sandbox is $0.018/hour, billed per second while it runs. A stopped sandbox costs nothing, so call close() when the crew finishes or let the TTL stop it.
Can the agent install packages?#
Yes. List them in libraries_used and the tool installs them with pip in the VM before it runs the code. They stay installed for later calls to the same sandbox.
Can the agent run shell commands, not just Python?#
Not with this tool as written. Add a second BaseTool that calls sandbox.exec(command), or connect the Sandboxer MCP server for the full tool set.
Does it work with the async API?#
Yes. sandboxer-sdk has AsyncSandboxer with the same methods, and BaseTool has an _arun method you can implement with it.
Related#
More guides
For running AI-written code we rank Sandboxer, Freestyle and E2B first: each gives code its own VM with an API or SDK and can pause with memory. Pick Daytona for Windows or GPUs, Modal or Northflank for GPUs at scale, E2B or Northflank to run in your own cloud, and Vercel Sandbox if your app lives on Vercel.
For coding agents we rank Sandboxer first: a full Ubuntu VM with Claude Code and Codex preinstalled, Docker, and stop-and-resume that keeps the agent's session in memory. Freestyle and Amp Orbs also keep memory across pauses. Pick E2B for prebuilt agent templates on open source, Docker Sandboxes for free local runs, and Codespaces if you live in GitHub.
The best Daytona alternatives depend on why you're switching. For a full Linux microVM on every sandbox with memory-saving stop and Docker, we rank Sandboxer first. E2B suits open source and self-hosting, Modal massive short-job fleets, Northflank bring-your-own-cloud with GPUs, and Vercel Sandbox apps on Vercel.
If you're leaving E2B for longer sessions or a fuller machine, we rank Sandboxer first, then Freestyle: both give each sandbox a full Linux VM that keeps memory across pauses, with Docker and no short session cap. Pick Daytona for Windows or GPUs, Northflank for bring-your-own-cloud, and stay on E2B if you need open source.