← logs/

Sandboxing agents: application-layer dry-run vs system-level isolation

When someone says an agent is "sandboxed", I want to know which failure mode they are defending. A bad plan that mutates production state and a shell escape that reads the host's SSH keys fail at completely different layers, and conflating them produces sandboxes that are either too weak or too expensive.

There are two kinds of containment. Semantic containment, at the application layer: simulate the world, check the plan before it touches anything real. Mechanical containment, at the system layer: isolate the execution so that whatever runs cannot hurt the host, whether the plan is right or wrong. One answers "is this plan wrong?", the other "can this code hurt me?". They compose, but they are funded differently and fail differently.

Close read: ToolSandbox, what a faithful simulation requires

ToolSandbox (Lu et al., Findings of NAACL 2025) is an evaluation benchmark, but it doubles as a design spec for simulation sandboxes. Previous tool benchmarks evaluated stateless web services, single turns, or off-policy trajectories. ToolSandbox adds stateful tool execution, implicit state dependencies between tools, a built-in user simulator that supports on-policy conversational evaluation, and dynamic scoring of intermediate and final milestones over arbitrary trajectories.

Two findings stand out — a significant capability gap between open and proprietary models, and state-defined tasks like State Dependency, Canonicalization, and Insufficient Information staying hard even for the most capable models — and I unpack both in the sandboxing design notes.

The lesson for dry-run design: a simulation sandbox is only as good as its state model. When one tool writes a file that another tool reads, a stateless mock will bless plans that fail in reality. The expensive part of a semantic sandbox is not the LLM; it is modeling the dependencies between tools truthfully enough that the simulation rejects what reality would reject.

Close read: ToolEmu, emulation as a dry-run engine

ToolEmu (Ruan et al., ICLR 2024) goes further: an LM emulates tool execution, and an LM-based safety evaluator grades the agent's behavior. The headline numbers — 68.8% of emulator-flagged failures judged valid real-world failures by a human evaluation, and even the safest agent failing 23.9% of the time per the safety evaluator, across 36 high-stakes tools and 144 test cases — get the full breakdown in the sandboxing design notes.

The appeal is cost and coverage. No tool implementations, no real side effects, red-teaming at scale. The risk is that the emulator shares the model's blind spots: it is a sampling of what tools would do, not a guarantee. Emulation is a filter, not a verdict. It catches the common, the plausible, and the stereotyped, and it can miss the long tail that a real environment would hit.

Close read: Firecracker, mechanical isolation at serverless scale

Firecracker (Agache et al., NSDI 2020) is the reference point for mechanical containment. AWS built a virtual machine monitor specialized for serverless workloads because the traditional tradeoff was unacceptable: virtualization with strong security and high overhead, or containers with weak security and minimal overhead. Firecracker is deployed in AWS Lambda and Fargate, supporting millions of production workloads and trillions of requests per month.

This is the pattern agent isolation is now copying. E2B's sandboxes are Firecracker microVMs, described in their own words as "a microVM made to run untrusted workflows": an on-demand Linux VM per agent, with up to 24 hours of runtime on paid tiers. The shape fits agents perfectly: throwaway, bursty, untrusted compute, where the host shares no kernel and no memory with the agent's code.

When each is right

question:        is the plan wrong?        can the code hurt me?
defense:         semantic dry-run          mechanical isolation
tooling:         ToolEmu-style simulation  containers, gVisor, microVMs
cost:            cheap, approximate        heavier, definitive
best for:        eval, red-teaming, gates  code execution, untrusted plugins

Dry-run is right when the question is about the plan's semantics and the side effects matter: evaluating candidate plans, red-teaming, training-time rollouts. System isolation is right when the question is about containment and you cannot predict what the code will do. You do not need to understand the code, you just need it contained.

The two compose. Dry-run filters candidate plans at low cost. Isolation contains whatever survives. Explicit gates make the remaining side effects visible and reviewable.

Two systems, two layers

At PiPlan.ai, the eval sandbox for candidate plans is a simulation sandbox, proposal-first: the agent writes a plan, the system computes the exact delta against current state, and a human signs the delta before anything lands:

draft plan -> simulate in sandbox -> render diff -> human commit -> real environment

The design choice is that the diff is the reviewable artifact. The agent never reaches the real state directly, so its mistakes are proposals, not mutations. The cost is human attention at the commit step, which is exactly where attention belongs. One honesty note on my own sandbox: the simulation validates a plan against the typed graph, and it does not capture external API drift or how the model behaves inside the plan while executing it, so the 68.8% discount I applied to ToolEmu applies to PiPlan too — a filter, not a verdict.

On the state side, SafeRoutes shows the invariant-enforcement pattern I keep coming back to: every state write is gated, and a failed check rolls back. Trip data lives in a reversible trip tree, and plan, update, undo, and status all share it. The model's role is limited to observing and evaluating; every edit to the route goes through a pure-function orchestrator that owns the state. The safety property does not depend on the model behaving, because the model does not hold the write path.

The connecting idea across both: decide in a simulation, gate every write, and make the human commit the last check. I wrote up the harness-side details in the sandboxing design notes.

Sources linked in this post were fetched and verified.