Est.

Sandbox API Integration for Code Interpreter Agents

Reporter · · 14 min read
Cover illustration for “Sandbox API Integration for Code Interpreter Agents”
Running AI-Generated Code Safely · August 25, 2026 · 14 min read · 3,247 words

Wiring a sandbox API into a code interpreter agent involves more than swapping subprocess.run() for an HTTP call and calling it done. The real work is figuring out how execution requests map to API primitives, how state and dependencies survive between agent turns, and what isolation guarantees actually hold once the code is running. Get this wrong early and you build architectural debt that shows up the moment your agent needs more than one turn to finish a task.

Most teams start the same way. Someone wires an agent loop to os.system or exec(), and it works, right there in the notebook, because the notebook is a single trusted process with one user and no adversarial input. Put that same pattern in production and the walls fall away fast: no isolation between the agent's code and the host, no way to manage state across calls, no concurrency model, and no recovery when something hangs. A sandbox API functions as a separate runtime, with its own lifecycle, its own state model, and its own security guarantees that a shell call never had. Understanding that distinction is the whole ballgame, and it's what this piece walks through.

The timing matters too. Gartner projects that 40% of enterprise applications will integrate task-specific AI agents by the end of 2026, up from under 5% in 2025. That is not a slow ramp. Teams that treated code execution as a side project are now finding out that the infrastructure question arrived faster than they planned for.

What a sandbox API actually exposes and how those primitives map to agent tool calls

Strip away the marketing and every production sandbox API offers roughly the same set of primitives. There's lifecycle management: create a sandbox, pause or snapshot it, resume it, destroy it. There's code execution: run a script, get back stdout, stderr, and an exit code. There's filesystem access: read, write, upload, download, all scoped to that sandbox. There's process management for anything long-running or backgrounded. And there's network control: egress rules, exposed ports, sometimes VPC or private connectivity.

None of that is surprising on its own. What's easy to miss is how these primitives map onto the tool calls an agent actually makes. A run_code tool call looks, from the model's point of view, like one clean API request. But what happens to the sandbox before and after that call is where most of the design work actually lives. Did the sandbox already exist, or did the harness spin up a fresh one? Is the working directory the same as last turn? These aren't edge cases, they're the default behavior you have to decide on.

File operations are a good example of where naive implementations go wrong. An agent that writes a CSV and reads it back three turns later needs the write and the read to hit the same filesystem, which means they're filesystem primitives, not just code execution wrapped around a print statement. Dependency installation is subtler still: pip install pandas is, technically, just another code execution call, but it has side effects that need to persist. If your integration treats every tool call as independent, that install evaporates the moment the call returns.

This is where the split between the agent's control plane and the sandbox's execution plane earns its keep. Tool routing, model calls, loop management: that stays in your trusted infrastructure. The sandbox only touches the execution-plane work: running commands, writing files, handling installs, exposing ports. Amazon Bedrock AgentCore draws this line explicitly, separating its Code Interpreter and Runtime tools, each backed by its own microVM session, with the Runtime tool maintaining shell state, meaning environment variables, working directory, and running processes, for the life of that session. Teams that skip this separation and treat sandbox execution as one black-box "run code" function tend to find out the hard way, usually when they try to bolt multi-turn support onto something that was never built to carry state between calls.

How sandbox isolation actually works and what the API guarantees at runtime

Here's the premise worth sitting with: an LLM writing code, and something executing that code, is a remote-code-execution surface by definition. That's not a hypothetical risk to plan for later. It's the starting condition. The sandbox exists because that surface exists, and the isolation it provides is the only thing standing between model-generated code and your infrastructure.

CVE-2025-34291, found in Langflow, is a useful reminder of what happens when that boundary goes missing or gets bypassed: arbitrary Python execution with no isolation at all. And it's worth being honest about the code itself, not just the execution path around it. Research on LLM-generated patches found that even when a model successfully fixes the bug it was asked to fix, the patch introduces a new vulnerability in 9.5% of cases. Code coming out of a language model is not trustworthy by default, even when it's ostensibly solving your problem correctly.

So what does a sandbox actually guarantee? That depends entirely on which isolation tier it's built on, and the differences between tiers are not cosmetic.

At the strongest end sit hardware microVMs, the approach Firecracker popularized. Each sandbox gets its own guest kernel, so a kernel exploit inside the sandbox has no path to the host kernel. That's the deepest boundary available short of physically separate hardware. One step down is user-space kernel emulation, the approach gVisor's Sentry takes: syscalls get intercepted and re-implemented in user space, so the attack surface is the Sentry process, not the real host kernel. That's meaningful protection, though shallower than a true microVM boundary. At the shallow end are shared-kernel containers, using namespaces and cgroups for separation. That's process isolation, not kernel isolation, and a kernel exploit inside a container can, in principle, escape it. Fine for code you trust. Riskier when the code came from a model, or from a user you've never met.

The practical question this raises for integration: what does your threat model actually require? A sandbox running code from an internal, reviewed pipeline has different needs than one running whatever an end user's prompt convinced an LLM to write. The isolation tier you pick determines what you can safely hand to it, and what extra controls you need to bolt on around it if you go with something shallower.

One thing worth flagging directly: isolation, even the strongest kind, says nothing about network egress unless you configure it separately. A sandbox can be air-tight at the kernel level and still let code inside it phone home with your data, if outbound traffic isn't restricted. That's a distinct control, not a side effect of good isolation, and it's easy to leave unconfigured because it doesn't show up until someone tests for it.

The last piece is visibility. Every shell command, every file write, every network request the sandbox makes should generate a log entry that can't be quietly edited after the fact. That's what lets a team actually answer the question "what did this agent do" after something goes wrong, instead of guessing.

Managing state and dependencies across agent turns

Multi-turn agents create a specific kind of pressure that single-shot code execution never has to deal with. An agent installs a library in turn one, writes a file in turn two, reads that file back in turn three. All three of those need to happen in the same sandbox. If they don't, the agent's second and third turns fail in ways that have nothing to do with the model's reasoning and everything to do with infrastructure that quietly reset underneath it.

Stateless-by-default is the trap here, and it's an easy one to fall into because it's also the simplest thing to build. A new sandbox spun up per tool call means the agent reinstalls its dependencies every single turn. Files it wrote a moment ago are gone. Environment variables reset, working directory resets, and any long-running process the agent kicked off just disappears. None of this looks broken in a demo where the agent only ever takes one turn. It looks broken the first time someone builds a task that actually needs three or four.

The fix is straightforward to state, if not always straightforward to implement cleanly: one sandbox per agent session, created explicitly when the session starts and torn down when it ends, rather than one sandbox per tool call. That single design decision, made early, determines whether the rest of the state and dependency handling falls into place or has to be reconstructed later as a patch.

Dependency handling deserves its own attention rather than getting treated as a footnote to code execution. Pre-baking common libraries, pandas, numpy, whatever your agent reaches for most, into a custom sandbox image cuts setup time at the start of every session, since the agent isn't installing the same packages over and over. For anything installed on the fly during a session, that install should be tracked in session state, so the agent's second reference to that library doesn't trigger a redundant reinstall.

For workflows that stretch across sessions, snapshotting matters. An agent running a data pipeline that spans hours shouldn't have to re-run every prior step if the session gets interrupted; it should resume from a snapshot of exactly where it left off. AgentCore's Runtime tool handles this by keeping shell state, environment variables, working directory, running processes, alive for the life of the session. Daytona takes a related but distinct stance, building around persistent workspaces meant to carry state across sessions for agents running long or multi-step workflows, rather than optimizing for quick, disposable script runs.

There's a failure mode here that's easy to miss because it doesn't look like a bug at first: a team gets state management right within a session, but forgets to scope the sandbox's lifetime tightly to that specific user's session. One user's files or environment variables leaking into another user's sandbox is not just a correctness problem. It's a security incident waiting for someone to notice it.

Timeout, resource, and concurrency controls that prevent runaway agent execution

A stuck agent doesn't know it's stuck. That's the core problem: a loop that isn't converging, or a code execution call that's hanging, has no internal signal telling it to stop. Left alone, it keeps running, and the compute bill keeps climbing with it. Timeouts are the only thing standing between a bug and an open-ended cost.

Three layers of timeout are worth setting up, each catching a different failure. Per-tool-call timeouts catch a single code execution that's hung, an infinite loop or a blocking network call inside one script. Per-task-loop timeouts catch something different: an agent that keeps retrying a task it fundamentally cannot complete, burning turn after turn without ever escalating or giving up. Per-sandbox-lifetime timeouts are the hard backstop, tearing the whole sandbox down on a fixed schedule regardless of what's still running inside it.

Time isn't the only resource that needs a ceiling. CPU, memory, and disk quotas matter just as much, because one badly written script pulling unbounded memory can starve every other sandbox sharing that host. These limits belong at sandbox creation time, set as a matter of policy, not something a team notices is missing after a memory leak takes down a shared node.

Concurrency introduces its own set of problems once more than one user or agent is running sessions at the same time. If a sandbox API serializes sandbox creation, an agent that wants to fire off several tool calls in parallel just queues up and waits, and that bottleneck gets worse, not better, as usage grows.

Cold-start time compounds all of this in a way that's easy to underestimate until you've measured it. An agent making ten sequential code calls within one session pays the sandbox startup cost ten separate times if the implementation spins up a fresh sandbox per call, rather than reusing one sandbox across the session. Daytona targets roughly 90-millisecond startup; Vercel Sandbox, built on Firecracker microVMs, starts in the millisecond range as well. Both are useful benchmarks to hold a candidate provider against, because a startup time that seems trivial in isolation turns into a real bottleneck once it's multiplied across a high-frequency agent loop.

Resource and timeout configuration belongs in the sandbox creation call itself, decided up front, not patched in after something breaks.

Choosing a sandbox API for a code interpreter agent: what the tradeoffs actually are

Three questions narrow this down fast. What isolation tier does the threat model actually call for, microVM, gVisor, or plain containers? Does the agent need sandboxes that persist and carry state across a long workflow, or short-lived ones that spin up and disappear? And where does the compute need to physically sit, in a managed cloud, in your own infrastructure, or out at the edge?

On the managed cloud side, the options split by isolation depth and operating model. Amazon Bedrock AgentCore runs one microVM per session on Firecracker, scopes session state tightly, and doesn't persist anything between sessions by default; it supports VPC and PrivateLink for private connectivity, and it's a strong fit for teams already committed to AWS who want the strictest isolation available in a managed offering. Vercel Sandbox also runs on Firecracker microVMs, persists by default with snapshot support, starts in the millisecond range, and prices at $0.128 per vCPU-hour with active-CPU-only billing plus $0.60 per million sandbox creations, scaling up to 32 vCPUs and 64GB of memory. It reached general availability on January 30, 2026, though it currently runs out of a single region (iad1), which is worth weighing if your deployment needs to span geographies. Google's Agent Sandbox, now a CNCF project, takes a different shape entirely: an open-source Kubernetes controller offering gVisor or Kata Containers as the isolation layer, with a declarative API for stateful sandbox pods. It asks more of your infrastructure team, since it runs on your own cluster, but it hands you full control over the isolation layer in exchange. Cloudflare's Sandbox SDK is edge-native, with a TypeScript-first API and support for Python and Node.js; it fits lightweight edge workloads well, though its multi-second cold starts make it a weaker fit for compute-heavy agent loops running many sequential calls.

Daytona sits in a different category: purpose-built for AI agent workloads specifically, with sub-90-millisecond cold starts, Docker-native OCI compatibility, and the option to step up to Kata Containers for stronger isolation when needed. It offers stateful, persistent workspaces, runs on customer-managed compute in your own cloud, and carries SOC 2, HIPAA, and GDPR compliance. For teams that want the transparency of an open-source runtime, being able to see and audit what's actually executing underneath the API rather than trusting a closed system on faith, while also meeting enterprise compliance requirements without giving up control of where their data lives, that combination is hard to find elsewhere.

One edge case worth naming on its own: GPU support inside the sandbox. If your agent needs to run local inference or GPU-bound image processing inside the execution environment, that narrows the field sharply, since only a handful of providers support it today, and it tends to come with a real pricing premium attached.

The market context is worth keeping in view too. G2's 2025 AI Agents Insights Report puts 57% of companies as already running AI agents in production. That number tells you something concrete: teams evaluating sandbox APIs today are increasingly doing it under production constraints from the start, not prototyping in a vacuum and worrying about scale later.

And it's worth being direct about the open-source question, since it shapes how much you can actually verify. A black-box execution service asks you to trust its isolation and its behavior without being able to look inside. An open-source or otherwise transparent runtime lets your team audit what's actually running under the API surface, which matters more the closer your agent gets to handling anything sensitive.

Wiring the integration: concrete patterns for connecting a sandbox API to an agent loop

Theory only gets you so far. Here's what the wiring actually looks like once you sit down to build it.

Start with session-scoped sandbox creation. Create the sandbox once, at the start of the agent session, pass its ID through the rest of the loop, and destroy it when the session ends. Do not create a sandbox inside the tool call handler itself; that single mistake is probably the most common one teams make, and it quietly breaks every multi-turn workflow that depends on state surviving between calls.

The tool call handler itself has a shape worth getting right. It takes code from the model, sends it to the sandbox's execution endpoint, and captures stdout, stderr, and the exit code, returning all of it back to the model as a structured result. Exit code and stderr matter as much as stdout does, arguably more: the model needs a real failure signal to reason about, not just whatever printed to standard output on a successful run. Large outputs should get truncated or streamed rather than dumped wholesale into the context window; a multi-megabyte stdout blob helps no one and burns tokens for nothing.

Dependency installation splits into two paths. Known, common dependencies, pandas, numpy, whatever your agent reaches for regularly, belong pre-installed in a custom sandbox template, so the session doesn't waste time installing them fresh every time. Anything the agent requests on the fly during a session should run through the execution API with an explicit success check before the agent moves on, and once installed, it should get tracked in session state so it doesn't get reinstalled on the next turn for no reason.

File exchange deserves its own path too, separate from code execution. Input files should get uploaded through the filesystem API at session start, not written into place via a code snippet. Output files should come back the same way, through the filesystem API, rather than getting printed to stdout and parsed out of a text blob. The sandbox filesystem is the shared workspace; trying to route large data through the model's context window is slow, wasteful, and unnecessary when a proper file API exists for exactly this.

Timeout and error handling need their own wrapper around every execution call. Timeouts should surface to the model as structured failures the agent can act on, retrying or reformulating its approach, rather than the loop just silently hanging. And it's worth distinguishing two categories of failure that look similar but aren't: an execution error, where the code ran and failed, versus an infrastructure error, where the sandbox itself didn't respond. The right recovery path differs for each, and collapsing them into one generic "it failed" signal makes it harder for the agent, or a human debugging it later, to figure out what actually went wrong.

For tasks that stretch across multiple sessions, snapshotting at checkpoints lets a long pipeline resume exactly where it left off, rather than re-running work that already completed. This is where a sandbox built for statefulness from the ground up pays for itself; bolting snapshot behavior onto a system designed to be stateless is a much harder retrofit.

If you want one test that exposes nearly every mistake covered here, try this: run a multi-turn agent that installs a dependency in turn one, writes a file in turn two, reads and transforms that file in turn three, and returns a download link in turn four. Watch where it breaks. That's usually exactly where the integration was cutting corners.

Sources

  1. modal.com
  2. aws.amazon.com
  3. dev.to

More in Running AI-Generated Code Safely