# Vercel Eve (/docs/agents/eve)

<!-- agent-signals: reading_time_min: 10 · est_tokens: 4049 · updated: 2026-07-30 -->
Related: [Amp](/docs/agents/amp.md), [Claude Code](/docs/agents/claude-code.md), [Claude Managed Agents](/docs/agents/claude-managed-agents.md), [Codex](/docs/agents/codex.md), [Crabbox with E2B](/docs/agents/crabbox.md), [Devin](/docs/agents/devin.md)

[Vercel Eve](https://vercel.com/docs/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](https://eve.dev/docs/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`](https://github.com/e2b-dev/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.

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

## Why E2B as the backend [#why-e2b-as-the-backend]

|                | E2B backend                                                                           | Eve's local backends                                                         |
| -------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| Where it runs  | E2B cloud microVMs, from local dev and production alike                               | Docker daemon, local VM, or a simulated shell on the host                    |
| Startup        | \~150 ms from a snapshot or [custom template](/docs/template/quickstart)              | Image pull / VM boot on the developer machine                                |
| Environment    | Any [E2B template](/docs/template/quickstart) you build                               | Eve's `ghcr.io/vercel/eve` image, or a Docker image you supply               |
| Persistence    | [Auto-pause and resume](/docs/sandbox/persistence) — filesystem survives days of idle | Container/VM tied to the machine that started it                             |
| Egress control | E2B firewall: domain allow-lists and header injection                                 | Domain 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 [#prerequisites]

* Node.js 22 or later
* An Eve project (`npx eve@latest init my-agent`)
* An [E2B API key](https://e2b.dev/dashboard?tab=keys)

## Install [#install]

```bash
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`:

```bash
export E2B_API_KEY="e2b_***"
```

## Configure the backend [#configure-the-backend]

Author `defineSandbox` and pass `e2b()` as the backend:

```typescript title="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 [#run-the-agent]

```bash
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](https://e2b.dev/dashboard).

## Bootstrap and per-session setup [#bootstrap-and-per-session-setup]

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

```typescript title="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 [#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](/docs/sandbox/persistence): 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](https://e2b.dev/dashboard) or the [SDK](/docs/sandbox) 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 [#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](/docs/network/internet-access) update:

| Eve policy                                            | E2B 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.

<Warning>
  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.
</Warning>

### Credential brokering [#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`:

```typescript
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 [#custom-templates]

`template` accepts any E2B template ID or alias. Build a [custom template](/docs/template/quickstart) with your runtimes, system packages, and toolchain pre-installed, and every session starts from it:

```typescript
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 [#backend-options]

```typescript
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](/docs/sdk-reference/js-sdk) supports is reachable even when this backend has no dedicated option for it. Explicit options win over `createOptions`.

## How the integration works [#how-the-integration-works]

| Component       | Responsibility                                                                     |
| --------------- | ---------------------------------------------------------------------------------- |
| Eve runtime     | Runs turns, owns durable session state, calls the backend's `prewarm` and `create` |
| `defineSandbox` | Declares the backend plus the `bootstrap` and `onSession` hooks                    |
| `e2b()` backend | Creates, reconnects, and configures E2B sandboxes; maps network policy             |
| E2B snapshot    | Captures bootstrap + seed files once, forked per session                           |
| E2B template    | Base 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 [#learn-more]

* [`@e2b/eve-sandbox` on GitHub](https://github.com/e2b-dev/eve-sandbox) — source, unit tests, and a runnable Next.js example
* [Eve sandbox documentation](https://eve.dev/docs/sandbox) — session handle API, lifecycle hooks, and backends
* [Eve on Vercel](https://vercel.com/docs/eve) — how Eve maps onto Vercel Functions, Workflows, and AI Gateway

## Related guides [#related-guides]

<CardGroup cols="3">
  <Card title="Templates" icon="<svg xmlns=&#x22;http://www.w3.org/2000/svg&#x22; viewBox=&#x22;0 0 24 24&#x22; fill=&#x22;none&#x22;><path d=&#x22;M15 18H13C12.0681 18 11.6022 18 11.2346 17.8478C10.7446 17.6448 10.3552 17.2554 10.1522 16.7654C10 16.3978 10 15.9319 10 15C10 14.0681 10 13.6022 10.1522 13.2346C10.3552 12.7446 10.7446 12.3552 11.2346 12.1522C11.6022 12 12.0681 12 13 12H15C15.9319 12 16.3978 12 16.7654 12.1522C17.2554 12.3552 17.6448 12.7446 17.8478 13.2346C18 13.6022 18 14.0681 18 15C18 15.9319 18 16.3978 17.8478 16.7654C17.6448 17.2554 17.2554 17.6448 16.7654 17.8478C16.3978 18 15.9319 18 15 18Z&#x22; stroke=&#x22;currentColor&#x22; stroke-linecap=&#x22;round&#x22; stroke-linejoin=&#x22;round&#x22; stroke-width=&#x22;1.5&#x22;/><path d=&#x22;M10 13C9.06812 13 8.60218 13 8.23463 12.8478C7.74458 12.6448 7.35523 12.2554 7.15224 11.7654C7 11.3978 7 10.9319 7 10C7 9.06812 7 8.60218 7.15224 8.23463C7.35523 7.74458 7.74458 7.35523 8.23463 7.15224C8.60218 7 9.06812 7 10 7H12C12.9319 7 13.3978 7 13.7654 7.15224C14.2554 7.35523 14.6448 7.74458 14.8478 8.23463C15 8.60218 15 9.06812 15 10C15 10.9319 15 11.3978 14.8478 11.7654&#x22; stroke=&#x22;currentColor&#x22; stroke-linecap=&#x22;round&#x22; stroke-linejoin=&#x22;round&#x22; stroke-width=&#x22;1.5&#x22;/><path d=&#x22;M16.5 21.5C17.4293 21.5 17.894 21.5 18.2804 21.4231C19.8671 21.1075 21.1075 19.8671 21.4231 18.2804C21.5 17.894 21.5 17.4293 21.5 16.5M7.5 2.5C6.57069 2.5 6.10603 2.5 5.71964 2.57686C4.13288 2.89249 2.89249 4.13288 2.57686 5.71964C2.5 6.10603 2.5 6.57069 2.5 7.5M7.5 21.5C6.57069 21.5 6.10603 21.5 5.71964 21.4231C4.13288 21.1075 2.89249 19.8671 2.57686 18.2804C2.5 17.894 2.5 17.4293 2.5 16.5M16.5 2.5C17.4293 2.5 17.894 2.5 18.2804 2.57686C19.8671 2.89249 21.1075 4.13288 21.4231 5.71964C21.5 6.10603 21.5 6.57069 21.5 7.5&#x22; stroke=&#x22;currentColor&#x22; stroke-linecap=&#x22;round&#x22; stroke-linejoin=&#x22;round&#x22; stroke-width=&#x22;1.5&#x22;/></svg>" href="/docs/template/quickstart">
    Build custom sandbox templates with pre-installed dependencies
  </Card>

  <Card title="Sandbox persistence" icon="<svg xmlns=&#x22;http://www.w3.org/2000/svg&#x22; viewBox=&#x22;0 0 24 24&#x22; fill=&#x22;none&#x22;><circle cx=&#x22;12&#x22; cy=&#x22;12&#x22; r=&#x22;10&#x22; stroke=&#x22;currentColor&#x22; stroke-width=&#x22;1.5&#x22;/><path d=&#x22;M12 8V12L14 14&#x22; stroke=&#x22;currentColor&#x22; stroke-linecap=&#x22;round&#x22; stroke-linejoin=&#x22;round&#x22; stroke-width=&#x22;1.5&#x22;/></svg>" href="/docs/sandbox/persistence">
    Auto-pause, resume, and manage sandbox lifecycle
  </Card>

  <Card title="Internet access" icon="<svg xmlns=&#x22;http://www.w3.org/2000/svg&#x22; viewBox=&#x22;0 0 24 24&#x22; fill=&#x22;none&#x22;><path d=&#x22;M18.7088 3.49534C16.8165 2.55382 14.5009 2 12 2C9.4991 2 7.1835 2.55382 5.29116 3.49534C4.36318 3.95706 3.89919 4.18792 3.4496 4.91378C3 5.63965 3 6.34248 3 7.74814V11.2371C3 16.9205 7.54236 20.0804 10.173 21.4338C10.9067 21.8113 11.2735 22 12 22C12.7265 22 13.0933 21.8113 13.8269 21.4338C16.4576 20.0804 21 16.9205 21 11.2371L21 7.74814C21 6.34249 21 5.63966 20.5504 4.91378C20.1008 4.18791 19.6368 3.95706 18.7088 3.49534Z&#x22; stroke=&#x22;currentColor&#x22; stroke-linecap=&#x22;round&#x22; stroke-linejoin=&#x22;round&#x22; stroke-width=&#x22;1.5&#x22;/></svg>" href="/docs/network/internet-access">
    Restrict sandbox egress with domain allow-lists
  </Card>
</CardGroup>
