sandbox.commands.run and sandbox.files so every command the model runs, and every file it writes, stays in an isolated VM instead of on your server. The model API key stays in your app; the sandbox never sees it.
Setup
llm. For example, set OPENAI_API_KEY and pass "openai/gpt-5.5"; OpenAI needs no extra. See LLMs in the CrewAI docs for the full list, and API keys to create a Superserve API key.
Wire the sandbox into the agent
The highlighted block defines abash tool backed by the sandbox. Everything else is standard CrewAI usage. The tool looks up sandbox only when the model calls it, so the sandbox is created after the rest of the setup, right before the try block that kills it.
CommandResult. CrewAI reads the return annotation and sends the model stdout, stderr, exit_code, and truncated as JSON, so the model sees failed commands as well as their output.
Run code with file tools
Add file tools next tobash to let the model write a script, run it, and read the result. max_iter caps the iterations the agent runs on a task (the default is 25). After that, the agent must give its best answer.
verbose=True, the crew prints each tool call and its result. Output from a run with claude-haiku-4-5 (trimmed, with the panel borders removed; the model’s wording varies):
sandbox.files paths must be absolute. See Read & write files for binary content and directory downloads.
Stream tool calls
Setstream=True on the crew, and kickoff() returns a CrewStreamingOutput that yields the model’s text as it arrives. To show each command and its result, listen for ToolUsageFinishedEvent on the CrewAI event bus.
kickoff() is typed as CrewOutput | CrewStreamingOutput, so the assert narrows the type for type checkers. Read streaming.result after the loop for the final CrewOutput. The event handler is registered globally, so it runs for every crew in the process. CrewAI runs it on a worker thread, so its output can interleave with the streamed text. A tool call that raises, such as a command that exceeds its timeout, emits ToolUsageErrorEvent instead. See Streaming Crew Execution and Event Listeners in the CrewAI docs, and Streaming to stream a single command’s output.
One sandbox per conversation
The examples above close over one module-level sandbox. To serve many conversations, build the crew for a given sandbox. Storesandbox.id with the conversation, reconnect on each run, and pause between runs so files and installed packages stay available without paying for an idle VM.
Sandbox.connect() resumes a paused sandbox, or one that is still pausing, before it returns. See Lifecycle and Connect.
The second run finds the file because it lives in the sandbox, not because the crew remembers the first run. To keep context between runs too, give the crew CrewAI memory scoped to the conversation, for example memory=Memory(llm="anthropic/claude-sonnet-5-5").scope(f"/conversation/{sandbox.id}") with Memory imported from crewai. Memory also needs an embedding model. The default is OpenAI’s, so set OPENAI_API_KEY or pass an embedder to Memory. Do not use memory=True here: crews without a name share one memory scope, so context can leak between conversations.
Good to know
- The model can call only the tools you define. Keep
kill(),pause(), andupdate()in your application code, not in a tool. - To limit what model-written code can reach, create the sandbox with network rules.
- Pass
timeout_secondstocommands.run. Without it, the SDK request times out after 30 seconds, so the tool call fails on longer commands such as package installs. - The default sandbox has
python3but nopython. Tell the agent in itsbackstory, or use a template with the tools you need. - As a backstop for crashes, set
timeout_secondsandauto_delete_secondswhen you create the sandbox. See Auto-delete.
Resources
CrewAI docs
Agents, tasks, crews, tools, and flows in CrewAI.
Run commands
Options for
commands.run, including environment variables and working directory.