How to give an LLM a code execution tool with function calling
Declare a run-command tool in your model's function-calling format, run each call in a sandbox, and return exit code and output.
On Runtime the tool definitions are already written. withruntime/tools
gives four tools bound to one sandbox, each as a name, a description, a JSON
Schema and a function: run a command, read a file, write a file, list a
directory. Integrations for eleven agent frameworks wrap them, and each ran in
its framework's own agent loop against real sandboxes on 23 September 2026
(frameworks). The
sandbox behind them is a Firecracker microVM billed on the CPU it uses, $0.025
per vCPU-hour.
How tool use with a sandbox works
Function calling is a contract between your application and the model. You list tools, each with a name, a description and a JSON Schema for its arguments. The model answers with a tool call instead of text. Your application runs the call and sends the result back, and the model continues. The model never runs anything itself; your code does, and where it runs is your choice.
Running the call in a sandbox means the command the model chose executes on a separate machine with its own kernel, disk and network rules. The split of control stays clean:
| Your application chooses | The model chooses |
|---|---|
| Which sandbox, its size and lifetime | The command to run |
| Its network rules and secrets | The file paths to read and write |
| The API key and its daily limit | The cwd and a per-call timeout |
| When the sandbox stops | When it has finished |
The short answer
Bind the tools to a sandbox, send their schemas to your model, and dispatch each tool call by name:
TypeScriptimport { Sandbox } from "withruntime";import { sandboxTools } from "withruntime/tools";type ToolCall = { id: string; name: string; arguments: string };await using sbx = await Sandbox.create({ network: { internet: false }, timeoutSeconds: 1800 });const tools = sandboxTools(sbx);// What your model's API needs: a name, a description and a JSON Schema each.const declarations = tools.map((t) => ({ name: t.name, description: t.description, schema: t.inputSchema,}));console.log(declarations.map((d) => d.name));async function dispatch(call: ToolCall) { const tool = tools.find((t) => t.name === call.name); if (!tool) return { id: call.id, output: `No tool named ${call.name}` }; // The model's arguments are untrusted JSON: the tool checks the fields it uses. const result = await tool.execute(JSON.parse(call.arguments) as never); return { id: call.id, output: JSON.stringify(result) };}// One call, as a model would send it:console.log( await dispatch({ id: "c1", name: "runtime_exec", arguments: '{"command":"python3 -c \\"print(6 * 7)\\""}', }),);Pythonimport jsonfrom withruntime import Sandboxfrom withruntime.tools import sandbox_toolswith Sandbox.create(network={"internet": False}, timeout_seconds=1800) as sbx: tools = {tool.__name__: tool for tool in sandbox_tools(sbx)} print(sorted(tools)) # runtime_exec, runtime_list_files, runtime_read_file, runtime_write_file def dispatch(name: str, arguments: str) -> str: if name not in tools: return f"No tool named {name}" return json.dumps(tools[name](**json.loads(arguments))) print(dispatch("runtime_write_file", '{"path": "hello.py", "content": "print(6 * 7)\\n"}')) print(dispatch("runtime_exec", '{"command": "python3 hello.py"}'))Rename the schema field to what your provider's API calls it. In Python the
tools are plain functions with type hints and docstrings, which is what most
Python frameworks turn into schemas themselves.
What the four tools do
| Tool | Arguments | Returns |
|---|---|---|
runtime_exec |
command, optional cwd and timeoutSeconds |
exitCode, stdout, stderr, timedOut |
runtime_read_file |
path |
The file's text |
runtime_write_file |
path, content |
How many bytes it wrote, and where |
runtime_list_files |
optional path and depth |
Entries with path, type and size |
Relative paths are under /workspace, and output is capped so one noisy
command cannot fill the model's context. Tell the model to check exitCode:
a call that returned is not a command that worked.
If you use a framework
The same four tools come prepared for most agent frameworks (frameworks):
| Framework | What you add |
|---|---|
| Vercel AI SDK, Mastra | runtimeTools(sbx) from withruntime/ai |
| Claude Agent SDK | runtimeMcpServer(sbx) from withruntime/claude-agent-sdk |
| OpenAI Agents SDK | A sandbox client for SandboxAgent, or four function tools |
| LangChain, LangGraph, CrewAI | sandbox_tools(sbx) wrapped with the framework's tool |
| Google ADK, Pydantic AI | sandbox_tools(sbx) as the agent's tools |
| LlamaIndex | FunctionTool.from_defaults over sandbox_tools(sbx) |
| Deep Agents | RuntimeSandbox(sbx) as the agent's backend |
A framework's own local shell and file tools keep running on your machine, so switch those off when the sandbox tools replace them.
Guard the tool, not the prompt
A system prompt that says "never delete files" is a request. The settings on the sandbox are enforced on the host, whatever the model writes:
network: { internet: false }, or anallowlist of the hosts the task needs, binds root inside the sandbox too.- A secret gives code an API key it can use but never read: the sandbox holds a placeholder, and the proxy adds the value on HTTPS requests to that key's own hosts (security).
timeoutSecondswithonLeaseEnd: "stop"bounds how long the machine lives, even if your process crashes mid-loop.maxCostMicrosrefuses a create whose first lease would cost more, and a daily spending limit on the key caps a whole day.
What it costs
Take 5,000 agent tasks a month. Each keeps a 2 vCPU, 4 GiB sandbox running for 4 minutes, and its 25 tool calls use 30 CPU-seconds in all:
TextCPU: 5,000 × 30 s / 3,600 × $0.025 = $1.04Memory: 5,000 × 240 s / 3,600 × 4 GiB × $0.0075 = $10.00Total: $11.04About two tenths of a cent a task. While the model thinks, the sandbox is billed its memory and a CPU floor of a twentieth of a vCPU, $0.03125 an hour at this size (pricing). Model tokens are billed by your model provider. New accounts get 50 free sandbox hours, no card.
Start
Terminalnpm install withruntimenpx withruntime sandbox run --trial -- python3 -c 'print(6 * 7)'The first run prints a link to approve in your browser; after that the SDK finds the connection by itself.
Related: a coding agent sandbox, code interpreter for chatbots, run untrusted LLM code safely, what an agent sandbox is.
Facts on this page were checked on 25 September 2026.