PAIPAI

@pai/sandbox-e2b

E2B-backed SandboxProvider implementation.

Package: @pai/sandbox-e2b

Implements the SandboxProvider contract on E2B Firecracker microVMs. Each sandbox is a real Linux VM with a POSIX shell, suitable for production agent workloads.

Quick start

Bind the provider where the runtime binds storage and files:

import { createAgentRuntime } from "@pai/core";
import { sandboxCapability } from "@pai/sandbox";
import { createE2BSandboxProvider } from "@pai/sandbox-e2b";

const runtime = createAgentRuntime({
  agent, // tools: { ...sandboxTools() }
  scopeKey: (identity) => identity.workspaceId,
  capabilities: {
    sandbox: sandboxCapability({ provider: createE2BSandboxProvider() }),
  },
});

The provider authenticates with the E2B_API_KEY environment variable when apiKey is not passed.

Options

type E2BSandboxProviderOptions = {
  /** E2B API key. Falls back to the `E2B_API_KEY` environment variable. */
  apiKey?: string;

  /** Default sandbox template. E2B's own default (`base`) applies when unset. */
  template?: string;

  /**
   * Default idle lifetime for created sandboxes. E2B's own default
   * (5 minutes) applies when unset.
   */
  defaultTtlMs?: number;

  /**
   * Network policy applied when `create` receives no `network`. Defaults to
   * `{ access: "none" }` — the inverse of E2B's internet-on default.
   */
  defaultNetwork?: SandboxNetworkConfig;

  /**
   * Pause idle sandboxes instead of killing them, and resume them
   * transparently on the next operation. Billing stops while paused.
   * Defaults to true.
   */
  autoPause?: boolean;
};

Behaviour notes

  • Network is off by default. Sandboxes are created with { access: "none" } unless the caller or defaultNetwork says otherwise. allowlist maps to E2B allowOut rules plus a deny-all rule; E2B can only domain-filter HTTP (port 80) and TLS SNI (port 443) traffic.
  • Auto-pause. With autoPause: true (the default), idle sandboxes are paused rather than killed. connect — or any auto-resumed operation — wakes them with memory intact. With autoPause: false, callers must reconnect through provider.connect after an explicit pause().
  • Timeouts and aborts are provider-enforced. A command exceeding timeoutMs is killed and rejects with SandboxCommandTimeoutError; aborting the runCommand signal kills the remote process before rejecting with the abort reason.
  • Dead sandboxes are normalized. E2B reports a reclaimed sandbox differently per API surface (404s, 502s, generic timeouts). The provider maps all of them: session operations throw SandboxNotFoundError, connect returns null (after one retry for E2B's known pause/resume flakiness), and backend or auth failures throw SandboxUnavailableError.
  • keepAlive({ ttlMs }) only extends. The idle deadline is pushed out to at least ttlMs from now and never pulled in, so a short keepalive cannot truncate a longer lifetime granted at create time.
  • probe() lists one sandbox. That is the cheapest authenticated call E2B offers, so a status check proves the API key and the control plane without provisioning anything. A rejected key arrives as SandboxUnavailableError with reason: "auth", which is not retryable.
  • resources is rejected. E2B sizes sandboxes through templates, not per-create configuration; pass template instead.
  • bindingKey is ignored. E2B has no atomic one-sandbox-per-key primitive, so replicas creating for one key at once can each get a sandbox; the session manager's metadata rediscovery still reconnects after a restart.

Conformance

createE2BSandboxProvider passes the full @pai/sandbox/test conformance suite against real E2B sandboxes. The package's conformance and live tests run only when E2B_API_KEY is set; without it they skip.

import { createSandboxProviderConformanceSuite } from "@pai/sandbox/test";
import { createE2BSandboxProvider } from "@pai/sandbox-e2b";

createSandboxProviderConformanceSuite({
  name: "createE2BSandboxProvider",
  createProvider: () => createE2BSandboxProvider(),
});

On this page