# Sandboxes for AI Agents | Volcano

URL: https://volcano.dev/features/sandboxes

> Sandboxes on Volcano: isolated VMs where AI agents run commands, work with files and serve HTTP. Run one command or keep a session, suspend it with its processes and files, start from a Python or Node.js preset or your own template, and drive it from the API, the CLI, the JavaScript, Python and Ruby SDKs or MCP.

## A workspace where AI agents run code

Give an agent an isolated VM to run commands, work with files and serve HTTP, for a single command or for a session that suspends and resumes with its files and processes intact.

```console
$ volcano cloud sandboxes exec --preset python3.12 -- python -c 'print(6 * 7)'
42
# one command, one temporary VM, terminated when it finished
$ volcano cloud sandboxes run --preset node22
{ "id": "7cdbb4e3-…", "state": "starting", "memory_mb": 1024 }
$ volcano cloud sandboxes exec 7cdbb4e3-… -- node -e 'console.log(process.version)'
v22.22.0
$ volcano cloud sandboxes suspend 7cdbb4e3-…
# processes and files kept, and suspended time is not charged
```

## Agents need somewhere to run what they write

An agent analyzing a dataset needs somewhere to execute Python. A coding assistant needs to edit files and run the tests. Run that code inside your own function or server and it can reach everything your code can; build your own fleet of throwaway machines and you are maintaining images, a scheduler, cleanup jobs and a way to reach a port inside each one.

Volcano Sandboxes put that environment inside your project, and each hosted session is its own isolated VM. Run one command and Volcano terminates the VM when it finishes, or keep a session whose files in /workspace and background processes carry over from one command to the next. Suspend it while the agent waits and the time is not charged. When a server runs inside, reach its port with a credential that lasts five minutes at most.

## One workspace across every step

*Sessions*

A one-shot command runs in a temporary sandbox that is terminated before the result comes back. A session keeps its files in /workspace and its background processes running between commands, so an agent can run an analysis and answer the follow-up from what it already saved. A nonzero exit comes back as data for the agent to read, not as an error.

- One-shot commands clean up after themselves
- Files up to 8 MiB per read or write
- A retried command returns its stored result

```console
sandbox> python /workspace/analyze.py /workspace/sales.csv
revenue by region   NA 4.2M · EMEA 3.1M · APAC 2.4M
saved /workspace/monthly.csv, /workspace/revenue.png
sandbox> python /workspace/growth.py --quarter 3
fastest growth in Q3: APAC, up 18%
✓ the second script read what the first one saved
```

## Paused while the agent waits

*Suspend and resume*

Suspend a session while the agent waits on a person, a model or a long-running job. Resume it later and the files in /workspace are still there and the server it started still answers. Suspended time uses no runtime allowance or credits, and the snapshot behind it is included. The session's lifetime keeps counting while it is suspended.

- Processes and files survive a suspend
- Suspended time is not charged
- Terminate when done, and its files go with it

```console
$ volcano cloud sandboxes suspend 7cdbb4e3-…
state  suspending → suspended
# no runtime charged while it waits
$ volcano cloud sandboxes resume 7cdbb4e3-…
state  resuming → running
$ volcano cloud sandboxes exec 7cdbb4e3-… -- ls /workspace
http.log  monthly.csv  revenue.png  sales.csv
✓ files kept, and the server on port 8080 still answers
```

## Reach a server inside the session

*HTTP access*

Start an app preview or an interactive tool in a session, then request a credential for that one session and port. Send it as X-Volcano-Sandbox-Token, or post it from a browser form to set a cookie, and HTTP, WebSocket, SSE and gRPC all use the same URL. A signed-in user of your app reaches only the sessions you granted to them.

- Scoped to one session and one port
- Expires within five minutes
- Revoking a grant closes its open connections

```console
$ volcano cloud sandboxes exec 7cdbb4e3-… -- sh -c 'python -m http.server 8080 >/workspace/http.log 2>&1 &'
# then, from your backend
session.access(8080)              url, token · expires in 5m
GET  X-Volcano-Sandbox-Token: …    200 OK
WS   same URL, same token          101 Switching Protocols
✓ this session and this port only, for five minutes at most
```

## What every sandbox gives you

- **An isolated VM per session**: Each hosted session runs in its own virtual machine, with a private /workspace and 1 GiB of memory, or up to 2 GiB on Superagent.
- **Presets or your own image**: Start from the python3.12 or node22 preset, or deploy a Dockerfile fragment that Volcano builds and validates before any session uses it.
- **Suspend and resume**: Keep a session's processes and files while nothing is happening, without using runtime, and resume it when the work continues.
- **Ports you can reach**: Short-lived credentials per session and port, with HTTP, WebSocket, SSE and gRPC on the same URL.
- **Metered on memory and time**: Runtime is configured memory times running time, however many commands run. Hobby includes 5 GB-hours a month and Superagent 20.
- **API, CLI, SDKs and MCP**: The same sandboxes from the REST API, the CLI, the JavaScript, Python and Ruby SDKs, MCP tools and the Dashboard, and on your machine with volcano start.

## Start it, run it, reach it

### Agent session

```javascript
import { VolcanoClient } from '@volcano.dev/sdk';

// Sessions are managed with a service key, so this runs in a function or on
// your own server, never in the browser.
const client = new VolcanoClient({
  apiUrl: process.env.VOLCANO_API_URL ?? 'https://api.volcano.dev',
  anonKey: process.env.VOLCANO_ANON_KEY,
  accessToken: process.env.VOLCANO_SERVICE_KEY,
});
const projectId = process.env.VOLCANO_PROJECT_ID;

const created = await client.sandboxes.create(projectId, {
  preset: 'python3.12',
  region: 'us-east-1',
  maxDurationSeconds: 1800,
});
if (created.error) throw created.error;
const session = created.data;

try {
  // Creation returns while the VM is still starting. Poll, with a deadline.
  const deadline = Date.now() + 120_000;
  while (session.state !== 'running') {
    if (Date.now() >= deadline || ['unknown', 'terminated'].includes(session.state)) {
      throw new Error(`Session unavailable: ${session.state}`);
    }
    await new Promise((resolve) => setTimeout(resolve, 1000));
    const refreshed = await session.refresh();
    if (refreshed.error) throw refreshed.error;
  }

  // Files stay in /workspace for every later command in this session.
  const written = await session.files.write(
    '/workspace/analyze.py',
    new TextEncoder().encode(agentScript),
  );
  if (written.error) throw written.error;

  // Keep the request ID with the command. Retrying with it returns the stored
  // result, and a command whose outcome is unknown is never run again.
  const requestId = crypto.randomUUID();
  const run = await session.exec('python /workspace/analyze.py', { requestId });
  if (run.error) throw run.error;

  // A nonzero exit is data for the agent to read, not a platform error.
  console.log(run.data.exitCode, run.data.stdout, run.data.stderr);

  // Serve what it built, and reach it with a credential for this session and
  // port.
  const served = await session.exec('python -m http.server 8080 >/workspace/http.log 2>&1 &');
  if (served.error) throw served.error;
  const access = await session.access(8080);
  if (access.error) throw access.error;

  // The command returns before the server listens, and a request sent too early
  // gets a 502. Poll a read-only GET, with a deadline, before real traffic.
  const headers = { 'X-Volcano-Sandbox-Token': access.data.token };
  const ready = Date.now() + 30_000;
  let response = null;
  while (!response || response.status === 502) {
    if (Date.now() >= ready) throw new Error('Preview server did not start listening');
    await new Promise((resolve) => setTimeout(resolve, 500));
    response = await fetch(access.data.url, { headers }).catch(() => null);
  }
  console.log(response.status);
} finally {
  const stopped = await session.terminate();
  if (stopped.error) throw stopped.error;
}
```

### Python

```python
import os
import time
from uuid import uuid4

from volcano_sdk import VolcanoClient

client = VolcanoClient(
    anon_key=os.environ["VOLCANO_ANON_KEY"],
    service_key=os.environ["VOLCANO_SERVICE_KEY"],
    api_url=os.environ.get("VOLCANO_API_URL", "https://api.volcano.dev"),
)
project_id = os.environ["VOLCANO_PROJECT_ID"]

# One command in a temporary sandbox, terminated before the result returns.
# Retrying with the same request_id never runs the code a second time.
request_id = str(uuid4())
result = client.sandboxes.exec(
    project_id,
    'python -c "print(42)"',
    region="us-east-1",
    preset="python3.12",
    request_id=request_id,
)
print(result.stdout, result.exit_code)

# A session for work that spans several commands. Leaving the block requests
# termination, even when the body raises.
with client.sandboxes.create(
    project_id,
    region="us-east-1",
    preset="python3.12",
    max_duration_seconds=300,
) as session:
    for _ in range(60):
        if session.refresh().state == "running":
            break
        time.sleep(1)
    else:
        raise TimeoutError("Sandbox did not become ready")

    session.files.write("/workspace/input.bin", bytes(range(256)))
    assert session.files.read("/workspace/input.bin") == bytes(range(256))
    print(session.exec("wc -c /workspace/input.bin").stdout)
```

### CLI

```bash
# One command in a temporary sandbox. Prints the output and exits with the
# command's exit code.
volcano cloud sandboxes exec --preset python3.12 -- python -c 'print(42)'

# A session that outlives the command. Check get until it is running.
volcano cloud sandboxes run --preset python3.12 --duration 1800
volcano cloud sandboxes get SESSION_ID
volcano cloud sandboxes files write SESSION_ID /workspace/input.bin < input.bin
volcano cloud sandboxes exec SESSION_ID -- sh -c 'python -m http.server 8080 >/workspace/http.log 2>&1 &'

# A prompt that runs one command per line. exit detaches and leaves it running.
volcano cloud sandboxes shell SESSION_ID

# Keep it for later with its files and processes, then bring it back
volcano cloud sandboxes suspend SESSION_ID
volcano cloud sandboxes resume SESSION_ID
volcano cloud sandboxes terminate SESSION_ID

# Save a preset and memory size as a template (2048 MB needs Superagent)
volcano cloud sandboxes templates create analysis --preset python3.12 --memory 2048
volcano cloud sandboxes run --template TEMPLATE_ID

# The same commands against Docker-backed sandboxes on your machine
volcano start
volcano sandboxes exec --preset node22 -- node -e 'console.log(42)'
```

### Custom template

```bash
# ./sandbox/Dockerfile is a fragment. Volcano supplies the base image and the
# supervisor, so it has no FROM, ENTRYPOINT or USER:
#
#   RUN dnf install -y python3.12 && dnf clean all
#   COPY app.py /workspace/app.py
#   CMD ["python3.12", "/workspace/app.py"]

# Upload the directory. The template ID and request ID print before the
# upload starts; reuse both to retry an upload whose result you never saw.
volcano cloud sandboxes templates deploy data-analyst --path ./sandbox --memory 1024 --ports 8080

# The image is built and validated, and port 8080 has to accept connections,
# before the template becomes active
volcano cloud sandboxes deployments get <template-id> <deployment-id>
volcano cloud sandboxes deployments logs <template-id> <deployment-id> --region us-east-1

# New sessions use the active image. Sessions already running keep theirs.
volcano cloud sandboxes run --template <template-id>

# Ship a new version of the same template
volcano cloud sandboxes templates deploy data-analyst --path ./sandbox --template <template-id>

# History, and the original source of an activated deployment
volcano cloud sandboxes deployments list <template-id>
volcano cloud sandboxes deployments source <template-id> <deployment-id> > source.tar.gz
```

## What people run in sandboxes

### An analyst that keeps its working files

An assistant uploads a spreadsheet, runs the analysis and saves a chart, then answers the follow-up from the files it already wrote instead of starting over. Between questions the session is suspended, and suspended time is not charged.

### Coding agents that run the tests

A coding agent writes files into the session, runs the test suite and reads failures as exit codes and output. When the change works, it starts the app, and a grant lets a signed-in reviewer open that one session's preview.

### Running the code a model writes

A chat app sends each snippet a model writes to a one-shot command. Every command gets its own sandbox, terminated before the result comes back, and a retried request returns the stored result instead of running the code twice.

## Works with the rest of Volcano

- [Keep the key on the server](https://volcano.dev/features/edge-functions): Create sessions and run commands from a function, so the service key that manages sandboxes never reaches a browser.
- [Agent runs that outlast a request](https://volcano.dev/features/durable-functions): Make each sandbox command a durable step with a fixed request ID, so a retried step never runs the command a second time.
- [Share a session with a user](https://volcano.dev/features/authentication): Grant a signed-in user one session, so they can run commands in it or open its preview and reach no other.
- [Keep what the session made](https://volcano.dev/features/file-storage): Copy results out of /workspace into a bucket before you terminate the session, because its files go with it.

## Frequently asked questions

### Who can reach a sandbox?

Each hosted session runs in its own isolated virtual machine. Creating and managing sessions takes a platform user token, or a service key or project access token for that project, and anonymous keys cannot reach a session at all. A signed-in user of your app can only use a session you explicitly granted to them, and cannot create, suspend, resume, terminate or grant sessions. Local sandboxes from volcano start are Docker containers that share your machine's kernel, so use hosted sandboxes when you need VM isolation.

### When should I use a session instead of a one-shot command?

A one-shot command runs in a temporary sandbox and returns its result once that sandbox is terminated, which suits a single snippet of code. Use a session when several commands should share files, when a server has to stay up, or when the work runs longer than one request should wait: start it as a background process in the session and check its output or files with short requests.

### How long can a session run?

Up to one hour on Hobby or eight hours on Superagent, startup included, and one hour by default. The lifetime keeps counting while a session is suspended, and activity never extends it, so choose it for the task when you create the session. Hobby runs up to 5 sessions at once; Superagent has no plan limit, with a safety ceiling of 100.

### How are sandboxes billed?

On runtime: configured memory multiplied by running time, so a 1 GiB session running for half an hour uses 0.5 GB-hours however many commands it runs, and idle time while it runs counts. Hobby includes 5 GB-hours per account each monthly cycle; when they run out, starts and resumes are refused and running sessions are suspended. Superagent includes 20 GB-hours, then additional runtime in 5 GB-hour blocks of 25 credits. Suspended time, snapshots and local sandboxes are not charged.

### Can I bring my own dependencies?

Yes. Put a Dockerfile fragment and its files in a directory, using RUN, COPY, ENV, WORKDIR and CMD but no FROM, ENTRYPOINT or USER, because Volcano supplies the base image and supervisor. Deploy it as a tar.gz of up to 32 MiB from the Dashboard, the CLI, an SDK or the API. Volcano builds and validates the image, including that every declared port accepts connections, before the template becomes active, and sessions already running keep the image they started with.

### Can an agent drive a sandbox through MCP?

Yes. Volcano's MCP server has tools to start a session from a preset or template, run a one-shot command or a command in a session, read and write files, and suspend, resume or terminate sessions, with a full-scope project access token. A read-only token can list presets and inspect templates, sessions and files. The project always comes from the token, and a start or command retried with the same Idempotency-Key does not run twice. Port credentials and user grants stay with your backend.

## Get started

Ready to give your agent a sandbox? Create a free account at https://volcano.dev/signup, or have your coding agent follow the setup playbook at https://volcano.dev/setup.md.
