# Sandboxes contain where agents run. A control plane decides what they do.

If your agents already run in Docker, a micro-VM, or your harness's built-in sandbox, is a control plane redundant? No — they answer different questions. The sandbox bounds the unauthorized; the control plane decides the authorized. Here's the layer split, and why you want both.

A question that comes up in almost every conversation about ACP: *"we already run our agents in Docker / a micro-VM / the harness's sandbox — isn't this redundant?"*

Fair question. The answer is no, and the reason is worth being precise about, because the two layers get conflated constantly.

## What a sandbox actually bounds

A sandbox — Docker, gVisor, a micro-VM like [pi's Gondolin](/blog/pi-acp-extension), a cloud execution environment, or the sandbox built into Claude Code or Codex — is an **OS boundary around where code runs**. It confines files, processes, and network on that host. If the agent runs a malicious binary, hits a kernel exploit in a dependency, or `rm -rf`s its working directory, the blast stops at the boundary.

That is real protection, and nothing about a control plane replaces it. If an agent's process is compromised *below* the tool-call seam — arbitrary syscalls from a poisoned dependency — you want the OS boundary. A control plane governs the call path; it is not a syscall filter.

## What the sandbox can't see

Here's the gap: **a sandbox has no opinion about actions made with valid credentials through legitimate channels.** That's most of what an agent does all day, and it's where the expensive failures live.

- The agent that merges the wrong PR did it with your GitHub token, over HTTPS, through an egress rule you allowed. The sandbox let it through because letting it through is the sandbox working as designed.
- In the [Amazon Q incident we recreated](/blog/recreated-amazon-q-filesystem-wipe), the injected instruction had the agent delete cloud resources *using the developer's own permissions*. Containerize that agent and the local wipe is contained — the `gcloud` deletions still go through, because the credentials were mounted and the API calls were well-formed.
- The run that cost $50 in a re-read loop never touched a file it shouldn't have. No sandbox meters tokens.
- And when something goes wrong, the sandbox has no ledger. It can tell you the process exited; it can't tell you which call did the damage, under whose identity, or why it was allowed.

There's a second-order problem too. Sandboxes are configuration, and configuration drifts. When we [read the source of fifteen harnesses and frameworks](/blog/what-survives-yolo-mode), we found CrewAI's code interpreter silently falling back to in-process `exec()` when Docker isn't present — fail-open on exactly the path that was supposed to be sandboxed. If the sandbox is the only layer, its silent absence is invisible. A control plane in the call path notices, because the calls are still there either way.

## The split, stated plainly

**The sandbox bounds the unauthorized. The control plane decides the authorized.**

The sandbox protects the machine the agent is on. The control plane protects everything the agent can reach *from* it — your repos, your cloud, your customers' inboxes, your model bill. One is a wall; the other is a decision at each door, with a record of every decision.

<img src="/assets/img/screenshots/activity-denied-dark.png" alt="ACP activity log filtered to denied calls: real deny rows with the rule that fired, identity, client, and latency" style="width:100%;height:auto;border:1px solid var(--line-2);border-radius:10px;box-shadow:0 20px 50px -24px rgba(0,0,0,0.9);margin:8px 0;" />

<p style="font-size:13px;color:var(--fg-3);margin-top:4px;">One workspace's denied calls, live in the activity log (Aug 18, 2026). None of these needed a sandbox to be stopped — and a sandbox would have stopped none of them. The <code>Bash.rm</code> row is the hardline floor: recursive delete of the root filesystem is blocked unconditionally, and no policy or approval can allow it.</p>

## They compose cleanly

The stack, inside-out: the model proposes an action → the harness turns it into a tool call → **the control plane decides it** → the sandbox contains its execution → whatever the call legitimately reaches beyond the sandbox is governed only by the decision that let it out.

Running ACP inside a sandbox costs nothing architecturally. The local hook is an ordinary process in the container; the cloud gateway needs only egress. pi's docs tell you to containerize if you want a boundary — [our pi extension](/blog/pi-acp-extension) runs happily inside that boundary and decides the calls before they run.

So: keep the sandbox. Tighten it, even. Then put a control plane at the seam it can't see.
