Skip to content
E2B Docs
Agents

Vercel Eve

Run Vercel Eve agent sandboxes on E2B with the @e2b/eve-sandbox backend.

Vercel Eve is a filesystem-first framework for durable backend AI agents: you author an agent as files under agent/, and Eve compiles them into an app that runs on Vercel Functions. Every Eve agent has exactly one sandbox — the isolated bash environment rooted at /workspace that backs the built-in bash, read_file, write_file, glob, and grep tools.

The @e2b/eve-sandbox package is an E2B backend for that sandbox — the E2B counterpart to Eve’s built-in vercel(), docker(), microsandbox(), and justbash() backends. It implements the public SandboxBackend interface from eve/sandbox, so Eve itself needs no changes and no fork.

@e2b/eve-sandbox is experimental, and Eve is in beta. Both APIs may change.

Why E2B as the backend

E2B backendEve’s local backends
Where it runsE2B cloud microVMs, from local dev and production alikeDocker daemon, local VM, or a simulated shell on the host
Startup~150 ms from a snapshot or custom templateImage pull / VM boot on the developer machine
EnvironmentAny E2B template you buildEve’s ghcr.io/vercel/eve image, or a Docker image you supply
PersistenceAuto-pause and resume — filesystem survives days of idleContainer/VM tied to the machine that started it
Egress controlE2B firewall: domain allow-lists and header injectionDomain policies on vercel()/microsandbox(); docker() is all-or-nothing

Use it when you want the same sandbox behavior in local dev, CI, and production, on infrastructure you control the image for.

Prerequisites

  • Node.js 22 or later
  • An Eve project (npx eve@latest init my-agent)
  • An E2B API key

Install

npm i @e2b/eve-sandbox e2b

eve (>= 0.27) and ai (>= 7) are peer dependencies already present in an Eve project.

Set your key — the backend reads E2B_API_KEY unless you pass apiKey:

export E2B_API_KEY="e2b_***"

Configure the backend

Author defineSandbox and pass e2b() as the backend:

agent/sandbox.ts
import { defineSandbox } from 'eve/sandbox'
import { e2b } from '@e2b/eve-sandbox'

export default defineSandbox({
  backend: () => e2b({ template: 'base' }),
})

That is the whole integration. The agent’s bash tool now runs commands in an E2B sandbox, its file tools read and write the sandbox filesystem, and nothing executes on your app runtime.

Prefer the factory form (backend: () => e2b({...})) over backend: e2b({...}): it defers reading environment variables until first use and memoizes the backend, so its prewarmed-snapshot cache survives across calls.

Use the folder layout (agent/sandbox/sandbox.ts) instead if you also seed files from agent/sandbox/workspace/**.

Run the agent

pnpm dev        # or: npx eve dev

Ask the agent to run a command and it executes in a real E2B sandbox. Sandboxes created this way appear in your E2B dashboard.

Bootstrap and per-session setup

Eve has two lifecycle hooks, and the E2B backend maps each onto a different E2B primitive:

agent/sandbox/sandbox.ts
import { defineSandbox } from 'eve/sandbox'
import { e2b } from '@e2b/eve-sandbox'

export default defineSandbox({
  backend: () =>
    e2b({
      template: 'base',
      timeoutMs: 30 * 60 * 1000,
      envs: { NODE_ENV: 'production' },
    }),

  // Build time, once per template — baked into a reusable E2B snapshot.
  async bootstrap({ use }) {
    const sandbox = await use()
    await sandbox.run({ command: 'sudo apt-get install -y jq' })
  },

  // Once per durable session — tighten egress for the turn.
  async onSession({ use }) {
    await use({ networkPolicy: { allow: ['*.npmjs.org', 'github.com'] } })
  },
})
  • bootstrap runs during Eve’s build-time prewarm(). The backend creates a sandbox, runs your bootstrap, writes your workspace/ seed files, then captures an E2B snapshot. Later sessions fork that snapshot, so installs are paid once, not per session.
  • onSession runs once per durable session against a live sandbox, and is the right place for network policy, per-user credentials, and one-time markers.

Snapshots are named from Eve’s templateKey (which tracks your authored sandbox source, seed contents, and revalidationKey) plus a hash of the snapshot-affecting backend options — base template, baked envs, and network policy. Change any of those and the next build captures a fresh snapshot instead of reusing a stale one.

Session persistence

E2B sandboxes are created with lifecycle: { onTimeout: 'pause', autoResume: true }, and the default timeout is 30 minutes (E2B’s own 5-minute default would expire mid-turn). On timeout the sandbox pauses rather than dies: the filesystem is preserved and the next message resumes it.

On the next turn the backend reattaches in this order:

  1. The sandboxId persisted by Eve for that session.
  2. A still-running or paused sandbox whose E2B metadata matches eveBackend: "e2b" and the session’s eveSessionKey.
  3. Otherwise a fresh sandbox, forked from the prewarmed snapshot.

Two consequences worth knowing:

  • The backend’s shutdown() is intentionally a no-op. Killing the sandbox on server shutdown would drop background work and force Eve to recreate it without rerunning onSession. Sandboxes are left paused and expire on their own — kill them from the dashboard or the SDK if you want them gone sooner. Opt out with autoPause: false to kill on timeout instead.
  • Reconnects do not re-apply the configured networkPolicy. A resumed sandbox keeps its live policy, including any tightening onSession applied. Re-stamping the create-time default could silently loosen a locked-down session.

Network policy

Egress rules go on the factory (applied to each fresh session, before authored bootstrap runs) or in onSession’s use(). The backend translates Eve’s SandboxNetworkPolicy into an E2B firewall update:

Eve policyE2B update
"allow-all" (default)allowInternetAccess: true
"deny-all"allowInternetAccess: false
{ allow: ['github.com', '*.npmjs.org'] }allowOut: [...] + denyOut: ['0.0.0.0/0']
{ allow: ['*'], subnets: { deny: ['10.0.0.0/8'] } }denyOut: ['10.0.0.0/8']
{ allow: [] }allowInternetAccess: false

Restricting egress to an allow-list requires the catch-all in denyOut — E2B gives allow rules absolute precedence over deny rules, so the listed hosts pass and everything else is blocked. The backend adds that for you.

Because allow beats deny in E2B, some Eve policies cannot be expressed faithfully. The backend throws instead of silently weakening them:

  • subnets.deny combined with an allow-list — the “denied” hosts would stay reachable. Use { allow: ['*'], subnets: { deny: [...] } } for deny-lists, or drop subnets.deny (anything outside an allow-list is already blocked).
  • A per-rule match condition — E2B applies header transforms to every request to a host.
  • forwardURL request proxying — no E2B equivalent.
  • A header transform alongside a catch-all "*" allow — a transform host must be allow-listed, which an allow-all cannot also be.

Credential brokering

E2B can inject a header at the firewall so a secret authenticates egress without ever entering the sandbox. Eve expresses this as a per-domain transform:

async onSession({ use }) {
  await use({
    networkPolicy: {
      allow: {
        'github.com': [{ transform: [{ headers: { authorization: 'Basic your_base64_credentials_here' } }] }],
        'api.example.com': [],
      },
    },
  })
}

List every host explicitly. Eve’s documented "*": [] catch-all pattern is rejected by this backend for the reason above — pair transforms with an explicit allow-list instead.

To change policy mid-turn, call sandbox.setNetworkPolicy(...) on the live handle from any authored tool.

Custom templates

template accepts any E2B template ID or alias. Build a custom template with your runtimes, system packages, and toolchain pre-installed, and every session starts from it:

export default defineSandbox({
  backend: () => e2b({ template: 'your-template-id-or-name' }),
})

Templates and snapshots compose: the template is the base image, and Eve’s bootstrap layers your agent-specific setup into a snapshot on top of it. Put slow, stable work (compilers, system packages) in the template; put agent-specific work (cloning a baseline repo, installing project dependencies) in bootstrap.

Backend options

e2b({
  template: 'base',                  // E2B template ID or alias
  timeoutMs: 30 * 60 * 1000,         // auto-pause timeout; default 30 minutes
  envs: { NODE_ENV: 'production' },  // env vars baked into every sandbox
  metadata: { team: 'growth' },      // E2B metadata, create-time only
  tags: { tier: 'beta' },            // diagnostic tags, merged into metadata
  apiKey: process.env.E2B_API_KEY,   // defaults to E2B_API_KEY
  domain: 'e2b.dev',                 // self-hosted or region endpoint
  networkPolicy: 'allow-all',        // initial policy for each fresh session
  autoPause: true,                   // false → kill on timeout instead of pause
  createOptions: {},                 // raw E2B SandboxOpts escape hatch
})

createOptions is merged into every Sandbox.create() call, so anything the E2B JavaScript SDK supports is reachable even when this backend has no dedicated option for it. Explicit options win over createOptions.

How the integration works

ComponentResponsibility
Eve runtimeRuns turns, owns durable session state, calls the backend’s prewarm and create
defineSandboxDeclares the backend plus the bootstrap and onSession hooks
e2b() backendCreates, reconnects, and configures E2B sandboxes; maps network policy
E2B snapshotCaptures bootstrap + seed files once, forked per session
E2B templateBase OS, runtimes, and pre-installed dependencies

Paths line up because the backend anchors relative paths to /workspace, Eve’s cross-backend namespace — E2B’s own default working directory is /home/user, and the backend creates /workspace during base setup. Every E2B API call this backend makes is tagged with an eve-sandbox/<version> integration identifier.

Learn more

Was this page helpful?Suggest editsRaise issue