Size the sandbox to the blast radius
Teams choose sandboxes for code-executing agents the way they choose JavaScript frameworks: by what's fashionable. "We use Firecracker because AWS does" and "Docker is fine, everyone uses Docker" are both the same mistake. Sandbox selection is a blast-radius calculation. Ask what the worst action you're willing to accept would cost, then buy exactly that much isolation — no less, and not much more.
The isolation ladder is well mapped. A plain container shares the host kernel; runc's CVE history — the 2019 file-descriptor escape, CVE-2024-21626, the mount-race escapes — is a standing reminder that shared-kernel isolation degrades to "one kernel bug away from the host." gVisor moves the boundary: its Sentry intercepts every syscall and services it from a userspace kernel written in Go, with the Gofer proxying filesystem access, so guest code never speaks to the host kernel directly. That costs roughly 30–50MB of memory and 200–500ms of startup per sandbox, against a full microVM's 512MB and multi-second boot. Kata and Firecracker put a hardware virtualization boundary underneath, which is where multi-tenant clouds land. Each rung buys a stronger boundary and pays in memory, cold-start latency, and syscall compatibility.
The question that picks the rung
Start from the worst accepted action, not the runtime brochure. An agent running pandas over data it was already given, single-tenant, no network: process-level isolation with resource quotas is genuinely adequate, because the worst case is a wasted CPU-hour. An agent executing generated code with outbound network access and ambient credentials: now escape means credential theft and lateral movement, and you want at least a userspace-kernel boundary plus egress allowlisting. Arbitrary code from multiple untrusted tenants on shared hardware: that's microVM territory, full stop, because the worst case is one customer reading another's data.
Both failure directions are real. Under-sandboxing is how code-executing agents become incidents — an agent that can be steered by injected instructions and can execute code is an attacker with a shell, and the sandbox is the only thing deciding what that shell reaches. But over-sandboxing quietly kills the product. WASM-based isolation is beautifully tight and won't run most of the Python data stack. Heavy per-execution environments push cold starts toward the 40–60 second range once you're downloading runtimes and installing dependencies; purpose-built agent sandboxes compete on exactly this, with snapshot/restore and sub-100ms starts, because agents make many short executions and latency compounds per tool call. A maximally isolated sandbox nobody can afford to invoke per-step is security theatre with a budget line.
The honest complication is that the ladder doesn't cover everything, so "buy exactly enough" has a floor of humility. gVisor's own threat model is explicit that it does not defend against side channels like Spectre/Meltdown or resource exhaustion — and no isolation tier prevents in-sandbox misbehaviour. An agent that exfiltrates sensitive data through a perfectly legitimate allowed network call never touches the isolation boundary at all. That's why egress control — DNS filtering, blocking the metadata service and internal ranges, allowlisting outbound domains — is often a bigger risk reduction than climbing a rung, and why production designs stack layers (host kernel, container, userspace kernel, network policy) rather than betting on one.
So the procedure I actually use: write down the worst accepted action in money and data terms. Pick the cheapest rung whose escape story makes that worst case tolerable. Spend the savings on egress policy and resource limits. Revisit when the agent's capabilities — not the fashion — change.