# zidane/contexts/e2b

> A sandbox provider backed by E2B micro-VMs, hosted or self-hosted.

Import: `import { … } from 'zidane/contexts/e2b'`. Source: [src/contexts/e2b.ts](https://github.com/Tahul/zidane/blob/main/src/contexts/e2b.ts).

Use `createE2BProvider` for isolation off your machine. It returns a `SandboxProvider` that you wrap with `createSandboxContext`, needs the optional `e2b` package, and reads `E2B_API_KEY` when you pass no key. The other exports cover readiness checks, template and environment resolution, and the setup script that runs before the first turn.

```ts
import { createSandboxContext } from 'zidane/contexts/sandbox'
import { createE2BProvider } from 'zidane/contexts/e2b'

const context = createSandboxContext(createE2BProvider({ cwd: '/home/user/project', onDestroy: 'pause' }))
const handle = await context.spawn()
await context.exec(handle, 'bun --version')
await context.destroy(handle)
```

## Functions

### `createE2BProvider`

Function.

Build a [`SandboxProvider`](https://zidane.si/docs/reference/contexts-sandbox.md#SandboxProvider) backed by E2B.

The provider owns a registry of live `E2BSandbox` instances keyed by
sandbox id, so the per-call `exec`/`readFile`/… hooks reuse the same warm
connection rather than reconnecting each time.

```ts
function createE2BProvider(options?: E2BProviderOptions): SandboxProvider
```

**Parameters**

- `options?` (`E2BProviderOptions`, default `{}`): API key, domain, template, sandbox id, working directory, timeout, environment and readiness settings.

**Returns** `SandboxProvider`

**Example**

```ts
import { createE2BProvider } from 'zidane/contexts/e2b'
import { createSandboxContext } from 'zidane/contexts/sandbox'

const execution = createSandboxContext(
  createE2BProvider({ apiKey: process.env.E2B_API_KEY, timeoutSeconds: 600 }),
)
```

Source: [src/contexts/e2b.ts:350](https://github.com/Tahul/zidane/blob/main/src/contexts/e2b.ts#L350)

### `resolveE2BEnv`

Function.

Merge the factory-level `E2BProviderOptions.env` with the per-spawn
`SpawnConfig.env`, per-spawn winning on key conflicts. Returns `undefined`
when the merge is empty so callers can skip setting `envs` on the create
options entirely (rather than handing the SDK an empty object).

```ts
function resolveE2BEnv(
  optionEnv: Record<string, string> | undefined,
  perSpawnEnv: Record<string, string> | undefined,
): Record<string, string> | undefined
```

**Parameters**

- `optionEnv` (`Record<string, string> | undefined`): Environment from the provider options.
- `perSpawnEnv` (`Record<string, string> | undefined`): Environment from the spawn config. Wins on key conflicts.

**Returns** `Record<string, string> | undefined`

Source: [src/contexts/e2b-sdk.ts:129](https://github.com/Tahul/zidane/blob/main/src/contexts/e2b-sdk.ts#L129)

### `resolveE2BTemplate`

Function.

Resolve the effective E2B template, highest precedence first: the per-spawn
`SpawnConfig.sandbox.template`, then the factory `template` option, then the
`E2B_TEMPLATE` env var. Returns `undefined` (→ SDK's `base` default) when
none is set. The E2B SDK reads `E2B_API_KEY`/`E2B_DOMAIN` from the env but
NOT `E2B_TEMPLATE`, so this provider has to honor it itself.

```ts
function resolveE2BTemplate(
  perSpawnTemplate: string | undefined,
  optionTemplate: string | undefined,
  env?: Record<string, string | undefined>,
): string | undefined
```

**Parameters**

- `perSpawnTemplate` (`string | undefined`): Template from the spawn config.
- `optionTemplate` (`string | undefined`): Template from the provider options.
- `env?` (`Record<string, string | undefined>`, default `process.env`): Environment to read `E2B_TEMPLATE` from.

**Returns** `string | undefined`

Source: [src/contexts/e2b.ts:320](https://github.com/Tahul/zidane/blob/main/src/contexts/e2b.ts#L320)

### `runE2BPregame`

Function.

Upload a setup ("pregame") script into a ready sandbox and execute it.

Writes the script to `/tmp/<name>`, then `chmod +x`'s and runs it directly
(so a `#!/bin/bash` / `#!/usr/bin/env python3` shebang chooses the
interpreter) in `cwd` with `envs` applied — the same env the sandbox was
created with, so the script sees the run's `--env` / `--pass-env`. A
generous `PREGAME_TIMEOUT_MS` ceiling is used so long `apt`/`pip`
installs aren't killed by E2B's 60s command default. A non-zero exit
(whether E2B returns it or raises on it) throws, so the caller can tear the
sandbox down rather than prompt against a broken environment.

```ts
function runE2BPregame(
  sandbox: E2BSandbox,
  logger: Logger,
  opts: { content: string; cwd?: string; envs?: Record<string, string>; name: string },
): Promise<void>
```

**Parameters**

- `sandbox` (`E2BSandbox`): Sandbox to run the script in.
- `logger` (`Logger`): Logger for progress and failure lines.
- `opts` (`{ content: string; cwd?: string; envs?: Record<string, string>; name: string }`): Script `name` (file name under `/tmp`), `content`, and optional `cwd` and `envs` for the run.

**Returns** `Promise<void>`

Source: [src/contexts/e2b.ts:209](https://github.com/Tahul/zidane/blob/main/src/contexts/e2b.ts#L209)

### `waitForE2BReady`

Function.

Block until an E2B sandbox is ready to run commands, or throw if it never
becomes ready within the deadline.

The E2B control plane can hand back a sandbox id before the sandbox's `envd`
is accepting commands; the first real command then absorbs that cold start
and may time out. This polls a trivial `true` command on a short backoff,
logging a `waiting…` line up front and a `ready` line (with `elapsedMs`) on
success. On deadline exhaustion it best-effort kills the sandbox and throws.

`now`/`sleep` are injectable purely for deterministic tests; in production
they default to `Date.now` and a real timer.

```ts
function waitForE2BReady(
  sandbox: E2BSandbox,
  logger: Logger,
  opts: E2BReadinessOptions,
): Promise<void>
```

**Parameters**

- `sandbox` (`E2BSandbox`): Sandbox to probe.
- `logger` (`Logger`): Logger for progress lines.
- `opts` (`E2BReadinessOptions`): Deadline and probe settings.

**Returns** `Promise<void>`

Source: [src/contexts/e2b.ts:277](https://github.com/Tahul/zidane/blob/main/src/contexts/e2b.ts#L277)

## Interfaces

### `E2BLifecycle`

Interface.

What E2B does when a sandbox's timeout elapses (e2b SDK `SandboxLifecycle`).

```ts
interface E2BLifecycle
```

**Properties**

- `autoResume?` (`boolean`): Resume a paused sandbox on the next connect / request (needs `onTimeout: 'pause'`).
- `onTimeout` (`'pause' | 'kill'`): Action when the sandbox timeout elapses: `pause` keeps its state, `kill` destroys it.

Source: [src/contexts/e2b.ts:50](https://github.com/Tahul/zidane/blob/main/src/contexts/e2b.ts#L50)

### `E2BProviderOptions`

Interface.

Options for [`createE2BProvider`](https://zidane.si/docs/reference/contexts-e2b.md#createE2BProvider).

```ts
interface E2BProviderOptions
```

**Properties**

- `apiKey?` (`string`): E2B API key. Falls back to the `E2B_API_KEY` env var when omitted.
- `cwd?` (`string`): Default working directory, applied when a spawn doesn't set its own
  `SpawnConfig.cwd`. Created on spawn. Defaults to E2B's `/home/user`.
- `domain?` (`string`): E2B API domain. Set this to point at a self-hosted / on-prem cluster
  (e.g. `e2b.my-company.internal`). Falls back to the `E2B_DOMAIN` env var,
  then to E2B's hosted default.
- `env?` (`Record<string, string>`): Environment variables baked into every sandbox at create time (passed to
  E2B's `Sandbox.create({ envs })`), so every command run in the sandbox
  sees them. Per-spawn `SpawnConfig.env` is merged over these.
- `keepAliveSeconds?` (`number`): Keep the sandbox alive while it's used: at most once a minute, any
  command or file call pushes its timeout out to this many seconds from
  now (`sandbox.setTimeout`). An idle sandbox still times out. Off when
  unset.
- `lifecycle?` (`E2BLifecycle`): What happens when a created sandbox's timeout elapses. `{ onTimeout:
  'pause', autoResume: true }` makes a timeout a pause instead of a kill —
  what long-lived sessions want. Default: E2B's (kill).
- `logger?` (`Logger`): Logger for provider-level lifecycle lines (notably the readiness wait).
  Defaults to a [`consoleSink`](https://zidane.si/docs/reference/index.md#consoleSink)-backed logger so the messages reach
  stderr without the caller wiring one up.
- `onDestroy?` (`'pause' | 'kill' | 'leave'`): What to do with an owned sandbox when `destroy` is called:

  - `'kill'` (default) — call `sandbox.kill()`. The sandbox is deleted and
    its state is gone.
  - `'pause'` — call `sandbox.pause()` (falling back to the older
    `betaPause()` on `e2b` 2.0–2.26). The sandbox's filesystem and process
    state are preserved; reconnect later with the `sandboxId` option (E2B's
    `Sandbox.connect` auto-resumes a paused sandbox).
  - `'leave'` — drop the local handle without making any API call. The
    sandbox keeps running until E2B's own timeout fires.

  Has no effect on sandboxes the provider only connected to (the `sandboxId`
  option) — those are always left untouched on destroy since the caller owns
  their lifecycle.
- `pregame?` (`{ content: string; name: string }`): A setup script to upload and run once the sandbox is ready, before any
  prompting begins (see [`runE2BPregame`](https://zidane.si/docs/reference/contexts-e2b.md#runE2BPregame)). `name` is the basename used
  for the uploaded file; `content` is the script body. The script is run
  directly so its shebang selects the interpreter. A non-zero exit aborts
  `spawn` (and tears the sandbox down) — a broken setup must not silently
  hand back a half-provisioned sandbox.
- `readinessTimeoutSeconds?` (`number`): Total deadline, in seconds, for the post-create readiness probe (see
  [`waitForE2BReady`](https://zidane.si/docs/reference/contexts-e2b.md#waitForE2BReady)). A freshly-created sandbox can return its id
  before `envd` accepts commands; `spawn` blocks until a trivial probe
  succeeds so the first real command never absorbs the cold start. Defaults
  to `60`. Set to `0` to disable the gate.
- `sandboxId?` (`string`): Connect to a pre-existing sandbox by id instead of creating a fresh one
  (E2B's `Sandbox.connect`). When set, `template` is ignored — you're
  attaching to a sandbox that already exists. The provider treats such a
  sandbox as externally owned: it is NOT killed on `destroy`, nor torn down
  if the readiness probe / pregame fails, since the caller manages its
  lifecycle. Per-spawn `SpawnConfig.sandbox.sandboxId` overrides this.
- `template?` (`string`): Sandbox template id / name to launch. Falls back to the `E2B_TEMPLATE`
  env var, then to E2B's hosted `base` template. Note that `base` does not
  exist on self-hosted clusters — supply a template that does.
- `timeoutSeconds?` (`number`): Sandbox lifetime in seconds before E2B auto-kills it. Defaults to the
  SDK default (300s). Per-spawn `SpawnConfig.limits.timeout` overrides this.

Source: [src/contexts/e2b.ts:82](https://github.com/Tahul/zidane/blob/main/src/contexts/e2b.ts#L82)

### `E2BReadinessOptions`

Interface.

Options for [`waitForE2BReady`](https://zidane.si/docs/reference/contexts-e2b.md#waitForE2BReady).

```ts
interface E2BReadinessOptions
```

**Properties**

- `killOnTimeout?` (`boolean`): Whether to kill the sandbox if it never becomes ready. Defaults to `true`
  (we created it, so a dead sandbox is ours to clean up). Pass `false` when
  attaching to an externally-owned sandbox — failing the probe shouldn't tear
  down a sandbox the caller is managing.
- `now?` (`() => number`): Clock source — injectable so tests can drive the deadline deterministically.
- `sleep?` (`(ms: number) => Promise<void>`): Sleep between probes — injectable so tests can advance the clock without waiting.
- `timeoutSeconds` (`number`): Total deadline in seconds. `<= 0` disables the probe entirely.

Source: [src/contexts/e2b.ts:244](https://github.com/Tahul/zidane/blob/main/src/contexts/e2b.ts#L244)
