Per-User Agent Sandbox Design Patterns
Choose your sandbox technology based on what code the agent actually runs.

Four real options exist, and most teams default to the fastest one when they should be picking based on what the agent actually touches. That's the single most common mistake in this entire stack.
Standard containers (Docker with Linux namespaces and cgroups) give process-level isolation: a restricted view of the filesystem, the network, the process table. They share the host's kernel, and that's the whole tradeoff in one sentence. Overhead stays low, setup is fast, but a kernel exploit inside one container can, in principle, reach across to others. That's fine for low-risk work where speed matters more than a hard boundary. It's the wrong call the moment an agent runs code it didn't write itself, and plenty of teams make that wrong call anyway, because containers are what everyone already knows how to run.
gVisor adds a layer on top. It catches system calls before they hit the host kernel, putting a software wall between the container and the machine underneath it. Production platforms reach for it when they want stronger protection at the syscall level without paying for a full virtual machine. Pick this when the agent needs more than a container gives it but doesn't need a whole separate kernel.
Kata Containers and Firecracker microVMs go further: each sandbox gets its own kernel, a boundary enforced by hardware, not software. A compromised guest in one microVM can't touch the host or the sandbox sitting next to it. This is the right call, arguably the only defensible one, when the agent has broad tool access, runs code it didn't write, or handles banking data, health records, anything where "probably fine" doesn't cut it.
WebAssembly sits apart from the other three. It's a lightweight, portable sandbox, a restricted module with no direct line to the operating system underneath. It fits narrow, predictable jobs: deterministic tool calls, math, formatted output, work where the agent's behavior stays bounded and portability across runtimes actually matters.
Here's the position worth stating plainly: picking one technology and stopping there is a mistake, no matter which one you picked. A team running everything through Kata Containers is burning provisioning time and cost on tasks that never needed a hardware boundary in the first place. A team running everything through plain Docker is one syscall exploit away from a very bad week. Production systems need containers for fast provisioning, with gVisor or microVMs layered in wherever the security boundary actually counts. Defense in depth means several layers sharing the load. Provisioning time, cost per sandbox, and ongoing operational load differ enough across these four that the right mix depends on how risky the agent's job actually is, not on which option was easiest to spin up first. Whatever gets picked here becomes the foundation everything else sits on; persistence, resource limits, networking, all of it builds on top of that boundary.
Ephemeral vs. persistent sandbox patterns and when each one fits
Should the sandbox vanish when the task ends, or stick around as the user's own space? For most real products, the answer is persistent, and treating that as an open question past the prototype stage means nobody's thought hard enough about what the product actually promises its users.
Ephemeral sandboxes follow a simple loop: open, run the task, read the result, destroy the sandbox. Nothing sticks around, so nothing leaks, and the security story writes itself. This pattern suits one-shot work: code execution, document parsing, a single stateless tool call. The tradeoff is that every session starts from zero, with no memory of what happened last time, no leftover files, no context the agent picked up before.
Persistent sandboxes flip that. The environment survives across sessions, with its own kernel, filesystem, and network stack, tied to one user. Files stay put. Memory sticks around. Connected tools and conversation history carry forward from one interaction to the next. An agent that remembers a user's codebase, their preferences, the task left half-finished yesterday, feels like a different product than one that forgets everything overnight, and that difference is usually the whole reason someone pays for it in the first place. The cost lands on the operations side: persistent sandboxes pile up state over time, which means backup plans, recovery plans, and a clear answer for what happens to that sandbox the day a customer cancels.
Pause and resume offers the middle ground that makes persistence affordable. The sandbox goes quiet when the user steps away and picks back up on demand, holding onto state without burning compute the whole time nobody's using it. Scaling to zero on idle time matters once a fleet gets large, since coding agents and similar tools sit CPU-idle for the overwhelming majority of the time they're technically "running." Snapshots add a second benefit: point-in-time recovery, so a crash or a bad update doesn't wipe out a user's accumulated context.
Persistent sandboxes are the right default for most agent products, full stop. Continuity is the product experience; pause and resume is what keeps the compute bill manageable once the fleet hits the thousands.
Sandbox pooling and pre-warming for low-latency user onboarding
Spinning up a brand-new sandbox from scratch takes meaningful time, with latency that varies depending on which isolation technology sits underneath it. That delay lands at the worst possible moment: the first few seconds a new user spends with the product.
The fix is a warm pool. Keep a stack of pre-built, unassigned sandboxes sitting ready. When someone signs up, hand them one instantly instead of building one on the spot. The pool refills itself in the background; the moment one sandbox gets claimed, another starts warming up to take its place. This pattern is being formalized through a Sandbox custom resource, a warm pool construct paired with a claim mechanism that lets an application pull a ready sandbox instantly, pushing allocation latency down close to zero.
That same effort points at a bigger idea: declarative sandbox management. The goal is a standard API for workloads that need to behave like a long-running, stateful, single container, something with a stable identity, closer to a lightweight VM than a disposable process. State the goal once, say, keep a fixed number of warm sandboxes in the pool relative to those currently assigned to users, and a controller does the work of matching reality to that goal, instead of an engineer scripting each step by hand.
The bar to hit is simple: a new customer's agent should be running before they've finished the signup flow, not provisioned by hand hours later by whoever's on call that day. Platforms like Agent37, which provisions isolated per-user agents via a single API call, are built around exactly that expectation. Once the customer count passes what one person can provision by hand, pre-warming stops being a nice-to-have and becomes the thing standing between a product that works and one that doesn't. Anyone still hand-provisioning sandboxes past a few hundred users has already lost the argument, they just haven't felt it yet.
Resource controls and what "noisy neighbor" actually means in a per-user fleet
"Noisy neighbor" describes a specific failure: one user kicks off a long, heavy job, maybe a big coding task, and without limits in place, that job starts eating resources everyone else on the same hardware needs too.
CPU and memory limits stop this at the source. cgroups enforce hard ceilings at the kernel level, so a runaway process physically cannot take more than its share of the host machine. Sensible defaults set a per-sandbox ceiling at provisioning time, with room to raise that ceiling for customers paying for a higher tier.
Disk quotas solve a slower version of the same problem. Persistent sandboxes build up files over time, and without a quota, one user filling up a shared volume can crash workloads that have nothing to do with them. When a quota trips, the user should get a clear message about it, not a silent failure they have to guess at.
Billing doubles as a resource control here, alongside its role as a way to collect payment. Metering compute by the minute it actually runs, and disk by how long it's actually stored, ties cost directly to real usage instead of hiding heavy use behind a flat monthly fee. Per-user spending caps feed straight back into resource allocation: hit the cap, and the sandbox pauses cleanly instead of the user getting cut off mid-task with no warning.
Here's the part that runs against intuition, and it's worth saying plainly because most teams over-build for it: at real fleet scale, noisy neighbor shows up less often than people expect. Agentic workloads spend most of their running time sitting idle on CPU, waiting on a model response or a network call, not maxing out a core. A large fleet absorbs bursts better than a small one does; the panic over this problem is usually bigger than the problem itself. High availability still needs an answer for what happens when a sandbox host goes down, though. Leader election handles that handoff, so ownership of the sandbox transfers cleanly and the user doesn't lose their state. None of this works without visibility, either. Per-sandbox metrics and event logs matter for a specific reason: without them, tracking down why one specific user's experience degraded inside a fleet of thousands of sandboxes is close to impossible.
Zero-trust network design for agent sandboxes
Agents carry a different network risk than a typical containerized app, because making outbound calls isn't a bug, it's the job. Agents reach out to APIs, to services the user has connected, to the open web. That same necessary behavior is exactly what makes the network surface dangerous, and treating agent egress like ordinary app traffic is the mistake that gets people burned.
Default-deny egress is the baseline, not an optional hardening step. Block every outbound connection at the sandbox boundary unless it's explicitly allowed, then whitelist only the specific endpoints the agent actually needs. This forces a useful discipline: every outside dependency has to get named and reviewed, instead of quietly inherited from a permissive default nobody thought about.
DNS restrictions close a related gap. Limiting what hostnames a sandbox can resolve stops an agent from getting redirected toward internal services it was never supposed to see in the first place.
Segmentation keeps agent networks physically separate from production systems and sensitive data stores. One documented example of exactly this approach involved an internal background agent given a full working dev stack, including tools like Vite, Postgres, and Temporal, without any path into a real production system. The agent gets an environment realistic enough to do useful work in, without ever touching the real thing.
Per-sandbox network identity rounds this out. Give each user's sandbox its own stable URL and its own network identity, and per-user routing, per-user audit logs, and clean revocation when someone cancels all turn into something straightforward instead of a mess to untangle later.
This combination closes off specific, known attack paths, and the ClawHavoc incident shows exactly why that matters. A malicious skill distributed through ClawHub carried out live data exfiltration, the kind of attack that looks completely legitimate right up until it starts sending data somewhere it shouldn't. Egress filtering breaks that path even if the malicious tool runs exactly as written, because the outbound connection itself never gets to leave the sandbox. Prompt injection follows the same logic: a document with hidden instructions telling the agent to read SSH keys and mail them somewhere fails outright if traffic to that destination is blocked at the boundary before it ever gets attempted.
Least-privilege credential management and per-user OAuth at scale
Least privilege means giving an agent exactly the permission it needs for the task in front of it, scoped narrowly rather than granted as blanket read-and-write access to every connected service just in case something comes up later.
Short-lived credentials put a time limit on how much damage a leak can do. Issue a narrow, temporary token for each task instead of a permanent one. If it leaks, it's already dead weight by the time anyone could misuse it, because it's expired. In practice, that means scoping tokens to a single sandbox session and rotating them automatically rather than letting one credential live indefinitely.
Per-user OAuth is where this gets genuinely hard at scale, and it's the piece most teams underestimate until they're drowning in it. Connecting an agent to a user's Gmail, Slack, or GitHub account means handling that user's own consent flow, storing their token, refreshing it before it expires, tracking scopes and rate limits, and doing all of that separately for every user and every service they connect. Multiply that by thousands of users and a handful of integrations each, and credential lifecycle management turns into a serious piece of engineering on its own, separate from anything the agent itself does.
Composio splits this problem into two pieces: an auth config, a reusable template holding the app's credentials and permissions, and a connected account, the specific token tied to one user's login for that app. Authentication runs through a hosted flow: generate a Connect Link, the user signs in on the provider's own page, and Composio stores the resulting connection. The raw OAuth token never passes through the developer's own code, which is the detail that actually matters here. That covers a wide range of pre-built integrations, GitHub, Slack, Gmail, Notion, Jira, Salesforce among others, cutting out the need to hand-build an OAuth flow for each one. Composio also holds SOC 2 and ISO certification, and that's worth noting on its own: credential storage is exactly the piece of infrastructure a compliance audit is going to look at hardest.
