Sandbox

The agent's isolated bash environment, including built-in file tools, a seeded /workspace, backends, lifecycle, and network policy.

The sandbox is the agent's isolated bash environment: a filesystem rooted at /workspace where it can run shell commands, execute scripts, and read or write files without ever touching your app runtime. Every eve agent has exactly one. The built-in bash, read_file, write_file, glob, and grep tools already target it, and your authored code can too.

A working sandbox exists by default, with nothing to author. Override it only to add setup, seed files, pick a backend, or lock down the network.

The default sandbox is not a substitute for configuring network policy, credentials, retention, deletion, or other controls your application requires.

Using the sandbox

The model already has shell and file access through the default tools:

ToolDoes
bashrun a shell command in the sandbox
read_file / write_fileread/write files under /workspace
globfind files by pattern
grepsearch file contents

All of them run with /workspace as the working directory. The model-facing file tools accept both absolute paths and paths beginning with $HOME/; eve resolves the latter inside the sandbox before reading, writing, or searching. Any authored runtime function (a tool, a step, a model callback) can get a live sandbox handle with ctx.getSandbox().

agent/tools/run_analysis.ts
import { defineTool } from "eve/tools";
import { z } from "zod";

export default defineTool({
  description: "Run a Python analysis script and return its output.",
  inputSchema: z.object({ script: z.string() }),
  async execute({ script }, ctx) {
    const sandbox = await ctx.getSandbox();
    await sandbox.writeTextFile({ path: "analysis/run.py", content: script });
    const result = await sandbox.run({ command: "python analysis/run.py" });
    return { stdout: result.stdout };
  },
});

ctx.getSandbox() takes no arguments, is async, and only works inside authored runtime execution.

/workspace is one namespace across every backend, so /workspace/foo points at the same file whether the backend is local or Vercel. When you need to interpolate a path into a generated command, sandbox.resolvePath("repo/build.py") anchors a relative path to its absolute /workspace/repo/build.py form.

The handle does more than run and writeTextFile. In every method, relative paths resolve from /workspace and absolute paths pass through untouched:

MethodDoes
run({ command })run one command, block until it exits, return { stdout, stderr, ... }
spawn(options)launch a long-running process (server, watcher) and return a SandboxProcess handle
readTextFile / writeTextFileread/write a UTF-8 (or specified encoding) file; readTextFile supports 1-based line ranges
readBinaryFile / writeBinaryFileread/write raw bytes (images, archives, anything non-text)
readFile / writeFilestream a file in/out as bytes
removePath({ path, force, recursive })delete one file or directory; force ignores missing paths, recursive removes non-empty dirs
resolvePath(path)anchor a relative path to its absolute /workspace/... form
setNetworkPolicy(policy)change egress policy mid-turn (backend-dependent; see Network policy)

Since run blocks until the command exits, use spawn when the process should keep running while the agent does other work:

const sandbox = await ctx.getSandbox();
const server = await sandbox.spawn({ command: "python -m http.server 8000" });
// ...do other work against the server...
await server.kill();

A SandboxProcess exposes stdout/stderr byte streams, wait() (resolves with the exit code), and kill() (idempotent).

sandbox.id is a stable per-session identifier that persists across reconnects to the same logical session. Use it as the cache key for per-session state that must outlive individual step executions.

The option types (SandboxSpawnOptions, SandboxReadBinaryFileOptions, SandboxWriteBinaryFileOptions, and so on) are named exports from eve/sandbox, alongside SandboxProcess.

Seeding /workspace

Mount authored files into the sandbox at session start by placing them under agent/sandbox/workspace/. This requires the folder layout (agent/sandbox/sandbox.ts), not the top-level shorthand:

agent/sandbox/
  sandbox.ts                ← optional override (see below)
  workspace/
    schema.sql              ← lands at /workspace/schema.sql
    scripts/run.sh          ← lands at /workspace/scripts/run.sh

Every file under workspace/ mirrors into the sandbox cwd with its structure intact, and eve lists the top-level entries to the model in the prompt automatically. agent/skills/ files are materialized separately under $HOME/.agents/skills/, so agent/sandbox/workspace/skills/... is an ordinary workspace subtree when you choose to author one.

Overriding the sandbox

To add setup, seed files, or pick a backend, author defineSandbox. There are two layouts:

  • agent/sandbox.ts: shorthand. Use it when you need only a definition, no seeded files.
  • agent/sandbox/sandbox.ts: folder layout. Use it when you also seed agent/sandbox/workspace/**. If both exist, the folder layout wins.
agent/sandbox/sandbox.ts
import { defineSandbox } from "eve/sandbox";
import { vercel } from "eve/sandbox/vercel";

export default defineSandbox({
  backend: vercel({ resources: { vcpus: 2 } }),
  revalidationKey: () => "repo-bootstrap-v1",
  async bootstrap({ use }) {
    const sandbox = await use();
    await sandbox.run({ command: "sudo apt-get install -y jq" });
  },
  async onSession({ use }) {
    await use({ networkPolicy: "deny-all" });
  },
});

defineSandbox and defaultBackend live on eve/sandbox. Omit backend and the runtime falls back to defaultBackend() (see Backends).

Backends

The backend decides where the sandbox runs. eve ships four pinned factories from nested eve/sandbox/* imports plus an availability-aware default from eve/sandbox:

BackendRuns the sandbox
vercel()on Vercel Sandbox.
docker()locally in a Docker container, driven through the docker CLI.
microsandbox()locally in a lightweight microsandbox VM.
justbash()locally in the pure-JS just-bash interpreter (no daemon or VM, but no real binaries either).
defaultBackend()picks the best available: Vercel Sandbox on hosted Vercel → Docker → microsandbox → just-bash.

Configuring a pinned factory uses that backend unconditionally. docker() always requires a reachable Docker daemon, and vercel() always creates hosted sandboxes (including from local dev, with Vercel credentials).

With backend omitted, eve uses defaultBackend(), which resolves on first use in priority order:

  1. Vercel Sandbox when deploying on Vercel (process.env.VERCEL is set), since local container/VM runtimes can't run there.
  2. Docker when a daemon is reachable through a Docker-compatible docker CLI (Docker Desktop, OrbStack, Colima, Podman via its docker-compatible CLI; override the binary with EVE_DOCKER_PATH).
  3. microsandbox when the host supports it: macOS on Apple Silicon, or glibc Linux with KVM enabled.
  4. just-bash as the dependency-free fallback.

defaultBackend() also accepts a keyed bag so each inner backend gets its own typed create options:

import { defaultBackend, defineSandbox } from "eve/sandbox";

export default defineSandbox({
  backend: defaultBackend({
    vercel: { networkPolicy: "deny-all", resources: { vcpus: 4 } },
    docker: { image: "ghcr.io/vercel/eve:latest" },
    microsandbox: { memoryMiB: 2048 },
  }),
});

Docker

docker() drives the Docker CLI directly. The default base image is ghcr.io/vercel/eve:latest, eve's published sandbox runtime image. eve creates /workspace and verifies Bash during framework setup, before authored bootstrap code runs. Configure it through docker({ image, env, pullPolicy, networkPolicy }), and install authored runtime tools in sandbox bootstrap or provide them through a custom image. Templates are committed as local Docker images and reused across sessions when the sandbox source, seed files, revalidationKey, and Docker backend options still match. Sessions run as long-lived containers whose filesystems persist /workspace changes across turns for the same durable session. eve dev prunes stale template images in the background.

microsandbox

microsandbox() runs each sandbox in a lightweight local VM with snapshot-backed templates, a vercel-sandbox user, and a firewall capable of domain-level network policies and credential brokering. It is the closest local match to hosted Vercel Sandbox. The default base image is ghcr.io/vercel/eve:latest, eve's published sandbox runtime image. During framework setup, before authored bootstrap code runs, eve verifies Bash and creates /workspace and the sandbox user. Install authored runtime tools in sandbox bootstrap or provide them through a custom image. Supported hosts are macOS on Apple Silicon, or Linux (glibc) with KVM. The microsandbox npm package and its VM runtime are not bundled with eve, so eve dev installs both automatically when missing (disable with setup: { autoInstall: false }); production processes fail with actionable install errors instead.

just-bash

justbash() needs no daemon or VM, but commands run in a simulated bash with a virtual filesystem under .eve/sandbox-cache/, with no real binaries (git, node, package managers) and no network isolation. The just-bash package is an optional peer dependency, so eve dev installs it into your application automatically when missing (disable with autoInstall: false); production processes fail with an actionable install error instead.

You can also write your own backend. A SandboxBackend is an adapter object with a name, a create, and an optional prewarm. It can point at your own container runner, VM pool, internal sandbox service, or another isolation layer, as long as it returns the SandboxSession operations eve needs. Handles returned by create implement shutdown(), which stops the underlying compute at server shutdown. See the SandboxBackend* types on eve/sandbox.

Lifecycle

There are two hooks, scoped differently:

  • bootstrap({ use }) is template-scoped and runs once when the template is built. Put reusable setup here that every later session inherits, such as cloning a baseline repo, installing dependencies, or seeding files. Call use() to get a SandboxSession. Only template filesystem state and supported backend metadata carry into later sessions; config like network policy does not. If external inputs affect what bootstrap produces, set revalidationKey: () => string so eve knows when to rebuild the template (authored sandbox source and seed contents are already tracked for you).
  • onSession({ use, ctx }) is durable-session-scoped and runs once per session (and again if a sandbox definition change replaces the session's sandbox). Put per-session setup here, including network policy, resources, timeout, per-user credentials, and one-time markers. Because it runs inside the active runtime context, it can read ctx.session and derive the current principal without baking credentials into the template. Call use(opts?) to get a SandboxSession; opts flow to the backend's update path after create.

If you require a network policy or other configuration for every session, configure it on the backend factory or in onSession; do not rely on bootstrap-only configuration.

import { defineSandbox } from "eve/sandbox";
import { vercel } from "eve/sandbox/vercel";

export default defineSandbox({
  backend: vercel(),
  async onSession({ use, ctx }) {
    const sandbox = await use({ networkPolicy: "deny-all" });
    const user = ctx.session.auth.current;
    if (user === null) return;
    await sandbox.writeTextFile({ path: "SESSION_USER.txt", content: `${user.principalId}\n` });
  },
});

Sessions are persistent, and how the underlying runtime idles out depends on the backend. On the Vercel backend, the VM times out after a period of inactivity (default 30 minutes); eve preserves the filesystem and resumes the sandbox on the next message while the persisted sandbox remains available. The Docker backend keeps a long-lived container per durable session and persists /workspace across turns without that timeout, and the just-bash backend stores its virtual filesystem under .eve/sandbox-cache/.

Authored runtime callbacks can stop compute sooner through the handle returned by ctx.getSandbox():

const sandbox = await ctx.getSandbox();
await sandbox.stop();

Every built-in backend uses its native lifecycle operation without deleting the durable session. Treat the stop as the end of sandbox work in the current callback. On the next callback, ctx.getSandbox() reopens the same Docker container, microsandbox VM or snapshot, or just-bash filesystem and environment. Vercel can also automatically resume the same handle on its next I/O operation, just as it would after an inactivity timeout. No separate reconnect step or stop-specific state is needed. Lifecycle use() calls return the I/O-only SandboxSession because bootstrap and session initialization do not own runtime teardown.

Session sandboxes are keyed per durable session, not per deployment, so redeploying your app does not by itself discard them. A definition change to the authored sandbox source, workspace seed content, or revalidationKey replaces the sandbox on the next turn and runs onSession again.

Reattachment still depends on the backend retaining its physical sandbox state. If a persisted Vercel sandbox is no longer available, eve creates a replacement, using the current template when one is configured. Files and other changes made after the original sandbox was created are not restored automatically. Because the durable session still has the same sandbox key, onSession does not run again for this replacement. Persist important artifacts outside the sandbox, and do not rely on onSession as the only place that applies security-critical configuration.

When the eve server stops, no sandbox compute outlives it. eve dev stops the sandboxes it started when the dev server closes, and a self-hosted production server stops every open sandbox on shutdown (SIGTERM/SIGINT). Session state persists across the stop — the next server start reattaches each durable session from its stopped container, VM, or snapshot. Custom SandboxBackend adapters implement stop() for authored runtime calls and shutdown() for server teardown. Both stop the underlying compute while keeping the session reattachable from persisted state where the backend supports it; authored stop() failures reject, while process-wide shutdown collects and logs failures without blocking teardown.

Network policy

Egress rules go on the backend factory or in onSession's use(). There are three forms:

networkPolicy: "allow-all"; // default
networkPolicy: "deny-all";  // block all egress, including DNS

networkPolicy: {
  allow: ["ai-gateway.vercel.sh", "*.github.com"],
  subnets: { deny: ["10.0.0.0/8"] },
};

Default egress is allow-all. For non-public, sensitive, regulated, or production workloads, configure deny-all or an explicit allow-list before running untrusted tools or handling sensitive data.

Set it on the factory (vercel({ networkPolicy: "deny-all" })) and it applies before authored bootstrap code runs; framework-owned base setup may briefly keep egress open to install required packages. Set it in onSession's use() to override per-session. A provider-loss replacement with the same sandbox key does not rerun onSession, so enforce the security-critical baseline on the factory. If bootstrap needs network access, give the factory only the destinations it needs, then narrow the policy further in onSession. To change the policy mid-turn, call sandbox.setNetworkPolicy(...) on the live handle.

Domain-level allow-lists and credential brokering are supported by vercel() and microsandbox(). The Docker backend honors only "allow-all" and "deny-all" (at creation and via setNetworkPolicy); the just-bash backend rejects setNetworkPolicy entirely.

Credential brokering

Secrets never enter the sandbox. Instead, the network policy's per-domain transform injects credentials at the firewall, so a header can authenticate egress to a host while the secret stays out of the sandbox process entirely:

async onSession({ use }) {
  await use({
    networkPolicy: {
      allow: {
        "github.com": [{ transform: [{ headers: { authorization: "Basic your_base64_credentials_here" } }] }],
        "*": [],
      },
    },
  });
}

The "*": [] catch-all keeps general egress open while the transform applies only to github.com. For mid-turn brokering, call setNetworkPolicy with the same shape. The Vercel Sandbox docs cover the brokering mechanism itself.

  • Subagents: each subagent gets its own sandbox, independent of its parent.
  • Tools: authored tools run in the app runtime (full process.env); only sandbox tools run in the sandbox.
  • Security model: the app-runtime/sandbox trust boundary in full.
  • Vercel Sandbox: platform docs, including credential brokering and persistence limits.