Back to course overview
Module 3Tool integration 12 min

Code execution

The most powerful tool and the most dangerous: why agents need to run code, and the sandbox rules that make it survivable.

Some tool requests can't be enumerated in advance: 'total the refunds across these three orders, minus the restocking fee on the second, prorated for the used portion.' You could build calculate_complex_refund_v7, or you could give the agent a code execution tool — it writes a small program, your sandbox runs it, the output returns as a result. One tool, unbounded compute precision. That generality is exactly why it's the most dangerous tool in the box.

Why it earns its place

  • Arithmetic becomes real. Models predict tokens; python computes. Any number that matters — refunds, dates, totals — should come from executed code, not from the model's head. (Foundations taught you this trap; agents finally get the calculator.)
  • Data transforms on demand: parse this CSV of returns, group by reason, find the outlier — without you pre-building every transform as a tool.
  • Verification logic: the agent can write its own checks ('does the refund exceed the order total?') — reflection with actual math.

The sandbox rules (non-negotiable)

  1. 1LLM-generated code is untrusted code. Period. It runs in an isolated environment — never in your application's process, never with your application's permissions.
  2. 2Cap CPU, memory, file-write size, and wall-clock. Infinite loops and gigabyte outputs are DoS-by-accident. The course snippet below does exactly this — resource.setrlimit for CPU/memory/file-size in a preexec_fn, plus a timeout.
  3. 3Strip environment variables (your keys live there!) — env={} on the subprocess. This the snippet also does.
  4. 4Block the network and confine the filesystem — and here the honest caveat: the subprocess snippet below does NOT do these two. It does not block network access, and it does not restrict filesystem reads outside its scratch dir. Network access from a sandbox is how a prompt-injected agent exfiltrates whatever it was given, so for untrusted code these are not optional — you get them from a container (Docker with --network none, a read-only or scratch-only mount) or a hosted code-execution sandbox. Treat the snippet as a resource-limiter, not a security boundary.
  5. 5Log every executed snippet with its task ID. When a number is questioned later, the actual code that produced it is the audit answer.
tools/run_python.pypython
import subprocess, tempfile, resource

CPU_SECONDS = 2                  # RLIMIT_CPU: kill runaway compute
ADDRESS_SPACE = 256 * 1024**2    # RLIMIT_AS: 256 MB memory ceiling
MAX_WRITE = 1 * 1024**2          # RLIMIT_FSIZE: 1 MB max file write

def _limits():                   # runs in the child, before exec (POSIX only)
    resource.setrlimit(resource.RLIMIT_CPU, (CPU_SECONDS, CPU_SECONDS))
    resource.setrlimit(resource.RLIMIT_AS, (ADDRESS_SPACE, ADDRESS_SPACE))
    resource.setrlimit(resource.RLIMIT_FSIZE, (MAX_WRITE, MAX_WRITE))

def run_python(code: str) -> dict:
    """Course-grade sandbox. What it DOES: caps CPU, memory, file-write size,
    and wall-clock, and strips environment variables (no keys leak in).
    What it does NOT do: it does not block network access, and it does not
    restrict filesystem READS outside the scratch dir. preexec_fn is POSIX-only
    (macOS/Linux). For untrusted code that must be network-isolated, run it in a
    container (e.g. Docker with --network none) or a hosted code-execution
    sandbox — never rely on this snippet as a full security boundary."""
    with tempfile.TemporaryDirectory() as scratch:
        try:
            out = subprocess.run(
                ["python3", "-I", "-c", code],          # -I: isolated mode
                capture_output=True, text=True, timeout=5,
                cwd=scratch, env={},                      # no env, no keys
                preexec_fn=_limits,                       # CPU / mem / fsize caps
            )
            return {"stdout": out.stdout[:4000], "stderr": out.stderr[:1000]}
        except subprocess.TimeoutExpired:
            return {"error": "timed out after 5s — simplify the computation"}
The temptation you must refuse

At some point the agent will need something the sandbox forbids — a network call, a file, a library — and the easy fix is loosening the sandbox. Refuse. The correct fix is a purpose-built tool with its own contract and stakes rating. The sandbox's poverty is a feature: everything powerful should arrive through a door you built and can guard.