DSPy code execution: give ReAct a secure Linux sandbox
Pass Runtime's four sandbox functions to dspy.ReAct as tools, and the program's shell commands run in a Firecracker microVM.
On Runtime a DSPy program can run real software, not only pure Python.
DSPy's own PythonInterpreter runs code in Pyodide, a WebAssembly build of
Python, under Deno on your machine. That keeps code off your filesystem, but
it offers no shell, no compiler and only the packages built for Pyodide. A Runtime sandbox is an
Ubuntu 24.04 microVM with its own kernel, Python 3.12, Node.js 24, gcc, pip
and sudo. It starts in 351 ms at the median (24 September 2026), which matters
when an optimizer runs a program hundreds of times. dspy 3.3.1 was the current
PyPI release on 25 September 2026.
Where DSPy runs code
| Module | Default interpreter | What the code can use |
|---|---|---|
dspy.ProgramOfThought |
PythonInterpreter (Deno and Pyodide) |
Pyodide's Python and its packages |
dspy.CodeAct |
PythonInterpreter |
Pyodide's Python, plus the tools you pass |
dspy.RLM |
PythonInterpreter |
Pyodide's Python, plus the tools you pass |
dspy.ReAct with Runtime tools |
A Runtime microVM | A bash shell, any package, files, the network you allow |
The PythonInterpreter docstring in dspy 3.3.1 describes it as a "local
interpreter for secure Python execution using Deno and Pyodide" with "no
access to the host filesystem, network, or environment by default", and it
needs Deno installed (pip install "dspy[deno]"). The three code modules take
an interpreter_factory argument for other interpreters.
ReAct with a sandbox
DSPy tools are plain Python functions with type hints and docstrings; ReAct
takes them in a list and converts them itself. sandbox_tools(sbx) returns four
such functions.
Terminalpip install dspy withruntimePythonimport dspyfrom withruntime import Sandboxfrom withruntime.tools import sandbox_toolsdef solve(task: str) -> dspy.Prediction: with Sandbox.create( network={"internet": True, "allow": ["pypi.org", "*.pythonhosted.org"]}, timeout_seconds=900, on_lease_end="stop", ) as sbx: agent = dspy.ReAct("task -> answer", tools=sandbox_tools(sbx), max_iters=12) return agent(task=task)Configure the language model first, as in any DSPy program, with
dspy.configure(lm=dspy.LM(...)). The result carries answer, reasoning
and a trajectory that records every thought, tool call and observation, so
you can see which commands ran:
Pythonresult = solve("Install sympy, then factor x**6 - 1 and print the factors.")for step, value in result.trajectory.items(): print(step, value)print(result.answer)This loop ran on 25 September 2026 with dspy 3.3.1 and dspy.utils.DummyLM
scripting the model: ReAct called runtime_exec, recorded the sandbox's exit
code and stdout as the observation, and finished with the answer.
Optimizing a program that runs code
Optimizers such as MIPROv2 and BootstrapFewShot call your program once per
training example and candidate, so give each call its own sandbox inside the
module. Every trial then starts from the same clean machine, and nothing one
trial installs can leak into the next score:
Pythonimport dspyfrom withruntime import Sandboxfrom withruntime.tools import sandbox_toolsclass Solver(dspy.Module): def __init__(self): super().__init__() self.signature = dspy.Signature("task -> answer") def forward(self, task: str) -> dspy.Prediction: with Sandbox.create(network={"internet": False}, timeout_seconds=300, on_lease_end="stop") as sbx: react = dspy.ReAct(self.signature, tools=sandbox_tools(sbx), max_iters=8) return react(task=task)What that costs: Runtime bills the CPU the commands use, at $0.025 per vCPU-hour, plus $0.0075 per GiB-hour of memory. A thousand one-minute runs at 2 vCPU and 4 GiB that each use 20 CPU-seconds cost $0.64, and a sandbox idling while the model thinks costs $0.03125 an hour (pricing). A paid account runs 100 sandboxes at once to start, so parallel evaluation threads each get a machine.
Keep keys and budgets safe
- The LM key stays with DSPy. DSPy calls the model from your process. The sandbox receives only the commands ReAct chooses, never the key.
- No network while scoring.
network={"internet": False}gives the sandbox no network card at all, so model-written code cannot send your training data anywhere. - Tokens the code needs are stored as Runtime secrets and seen inside the sandbox only as placeholders.
- A long optimization run cannot overspend. Put a daily spending limit on
the key the optimizer uses. When a create would pass it, the create fails with
spending_limit_reached, and nothing is charged (read-only keys and daily limits).
Pyodide or a microVM?
Keep PythonInterpreter when the code is short, pure Python and needs nothing
outside Pyodide. Use Runtime when a step needs a package Pyodide lacks, a
command-line tool, a test suite, several languages, or a file that must survive
from one step to the next. The trade-off is laid out in
Pyodide vs a server sandbox and
WebAssembly vs a microVM; evaluation at
scale is in agent evals and SWE-bench.
New accounts get 50 free sandbox hours, no card:
Terminalnpx withruntime sandbox run --trial -- python3 -c 'print(6 * 7)'Sources
- DSPy: ReAct and tools, plain functions as tools,
max_itersandtrajectory, read 25 September 2026 - dspy on PyPI, version 3.3.1; the
PythonInterpreterdocstring and theinterpreter_factorydefaults ofProgramOfThought,CodeActandRLMwere read from that release's source on 25 September 2026
Facts on this page were checked on 25 September 2026.