Example
Code-sandbox agent — run model-written code in Docker

A real, runnable mini-project. Download it and run with Docker.

June 28, 2026

Code-sandbox agent — run model-written code in Docker

A ~50-line LangGraph ReAct agent with a single run_python tool. Give it a task that needs real computation — "what's the 30th Fibonacci number?" — and the model writes Python, the tool runs it inside a throwaway Docker container (no network, capped CPU/memory, auto-removed), and the agent reads the output and answers. The code never runs on the host. The model is routed through LiteLLM, so the same code works with Anthropic Claude, OpenAI, or Google AI Studio (Gemini) — change MODEL in .env, never the code.

Configure

cd samples/docker_1
cp .env.sample .env
# edit .env: set MODEL and the matching provider key

MODEL picks the provider:

Provider MODEL example Key in .env
Anthropic Claude claude-opus-4-8 ANTHROPIC_API_KEY
OpenAI gpt-4o OPENAI_API_KEY
Google AI Studio gemini/gemini-2.5-flash GEMINI_API_KEY

.env is gitignored — only .env.sample is committed. No sandbox API key: the tool is local Docker.

Run with Docker

You need Docker available. Pre-pull the sandbox image once:

docker pull python:3.12-slim

Run the agent itself in a container — Docker-out-of-Docker, mounting the host socket so the agent can spawn sibling sandbox containers:

docker build -t aas-code-sandbox .
docker run --rm --env-file .env \
  -v /var/run/docker.sock:/var/run/docker.sock \
  aas-code-sandbox "What is the 30th Fibonacci number? Use code."

The mounted socket means the docker run inside the agent talks to the host's Docker daemon, so each sandbox is a sibling container on the host, not a nested one. That's the Docker-out-of-Docker (DooD) pattern — the same way a dev container (like the one this repo runs in) hands an agent a Docker it can drive.

Run with Docker (in a devcontainer with DooD)

Under nested Docker-outside-of-Docker the foreground docker run may print nothing after the first moment — run detached and follow the logs; the daemon captures all of it:

docker logs -f "$(docker run -d --env-file .env \
  -v /var/run/docker.sock:/var/run/docker.sock \
  aas-code-sandbox "What is the 30th Fibonacci number? Use code.")"

Run locally

The agent shells out to your host's Docker directly:

pip install -r requirements.txt
python app.py "What is the 30th Fibonacci number? Use code."

How it works

flowchart LR
  model[Model] -- writes code --> tool[run_python]
  tool --> box[docker run python:3.12-slim]
  box --> out[(stdout / stderr)]
  out --> model
  box -. blocked .- host[host · network]

run_python doesn't exec the code in-process — it pipes it to a fresh container via docker run, with the isolation flags doing the real work:

Flag What it stops
--rm container is thrown away when the run ends
--network none no network — nothing can be exfiltrated
--memory 256m a memory blowup can't take the host down
--cpus 1 caps CPU so a busy loop can't hog the machine
--pids-limit 128 guards against fork bombs
--user 65534:65534 runs as nobody, not root
timeout=30 wall-clock kill switch for runaway code

This is the code execution role from the Sandboxing concept made concrete: the model proposes, the sandbox disposes.


Example run

The model writes the code and phrases the answer itself, so the wording can vary slightly run to run. One run with gemini/gemini-2.5-flash:

The 30th Fibonacci number is 832040.

Files

.env.sample
# Copy this file to `.env` and fill in the keys.
#   cp .env.sample .env

# Pick the model — LiteLLM routes the request based on MODEL. Examples:
#   claude-opus-4-8              Anthropic Claude   -> needs ANTHROPIC_API_KEY
#   gpt-4o                       OpenAI             -> needs OPENAI_API_KEY
#   gemini/gemini-2.5-flash      Google AI Studio   -> needs GEMINI_API_KEY
MODEL=claude-opus-4-8

# Set only the key for the provider you chose; leave the rest blank.
ANTHROPIC_API_KEY=
OPENAI_API_KEY=
GEMINI_API_KEY=

# The throwaway image the sandboxed code runs in.
# Pre-pull it once:  docker pull python:3.12-slim
SANDBOX_IMAGE=python:3.12-slim