2.2 Sandboxed tools
Back in 0.2 we split tools into safe (read the world) and dangerous (change it). A demo agent runs both without a second thought. That’s failure mode #2 from 1.2: a tool can do anything.
The scary case is the most powerful tool of all: running code the model wrote. If
the model can ask to execute arbitrary code and something just… runs it, then a wrong
guess isn’t a bad answer — it’s rm -rf, a leaked secret, or a process that never
returns.
Whose job is safety?
Section titled “Whose job is safety?”Not the tool’s. A tool is just a function; it does what it’s told. If safety lived inside each tool, you’d have to make every tool paranoid, forever.
Instead, safety is the harness’s job. The harness sits between the model and every tool and gets to say “no” — or “yes, but in a box.” Two mechanisms:
- A policy — a gate that inspects every tool call before it runs and decides: allow, sandbox, or block.
- A sandbox — an isolated runtime with strict limits (time, memory, no network, no filesystem) where untrusted code can run without being able to hurt anything.
Watch the gate work
Section titled “Watch the gate work”Below, the model fires off five tool requests — some innocent, some not. Every one passes through the policy gate first. Step through and watch what the gate does with each.
Three behaviors to notice:
- Safe reads pass untouched — no ceremony for a document search.
- Model-written code is sandboxed. The infinite loop doesn’t hang the harness; the sandbox kills it at the time limit. Harmless code in the same sandbox runs fine — the box is a boundary, not a ban.
- Destructive writes are blocked and return a structured error instead of running. The model reads that error and self-corrects to a safe action.
What that looks like in code
Section titled “What that looks like in code”One choke point that every tool call flows through:
async function runToolCall(call) { const decision = policy.check(call); // inspect BEFORE running
if (decision === 'block') { // Hand back a structured error — the model can read it and try something else. return { error: 'blocked', reason: 'destructive action requires approval' }; }
if (decision === 'sandbox') { // Untrusted code runs isolated, with a hard timeout and no ambient access. return runInSandbox(call, { timeoutMs: 2000, network: false, fs: false }); }
return tools[call.name](call.args); // safe: run normally}The key line is the first one: nothing runs until the policy has seen it. That single choke point is what turns “a tool can do anything” into “a tool can do exactly what the harness permits.”
In production
Section titled “In production”Nobody hand-rolls a secure sandbox — getting isolation right is genuinely hard. The real tools:
- e2b, Firecracker microVMs, and hosted code-execution sandboxes for running untrusted/model-written code.
- Policy layers and permission systems in agent frameworks for the allow/block gate.
You built the toy version — a gate plus an isolated runner — so you can recognize what those products are doing and why they exist.