Skip to main content
Flue is a TypeScript agent framework from the Astro team. An agent is a function marked with 'use agent' and configured with React-like hooks: useModel() picks the LLM, useSandbox() attaches a workspace. That second hook is what gives the agent its read, write, edit, bash, grep, and glob tools — without it the agent has no filesystem and no shell at all. Flue’s E2B adapter points those tools at an E2B sandbox. It ships as a blueprint: flue add sandbox e2b prints a Markdown implementation guide that your coding agent applies, leaving the adapter in your repo as src/sandboxes/e2b.ts. There is no @e2b/flue package — the only E2B dependency is the e2b SDK.
Flue adapters are deliberately thin. Your application creates the E2B sandbox and decides when it dies; Flue wraps the handle you hand it and never destroys provider infrastructure.

Why E2B as the sandbox

Flue ships two built-in environments — an in-memory just-bash sandbox and local(), which binds the agent to your host. Use E2B when agent-written code must not touch your host, when the work needs a real Linux toolchain, or when each conversation needs a workspace that outlives the process that started it.

Prerequisites

  • Node.js 22.19 or later
  • An E2B API key
  • A model provider key — this guide uses Anthropic

Create a project

Skip to the next section if you already have a Flue project.
flue init writes flue.config.ts, package.json, tsconfig.json, .env, src/agents/hello.ts, src/db.ts, AGENTS.md, and README.md. It does not install anything, hence the separate npm install. Put both keys in .envflue run loads it automatically, and the E2B SDK reads E2B_API_KEY from the environment:
.env

Add the adapter

flue add is not a package installer. It fetches the blueprint from Flue’s registry and prints it; the coding agent you pipe it into writes the file. Swap claude for codex, opencode, or whatever you use — or read the guide yourself with npx flue add sandbox e2b --print | less. Inside a coding agent session the CLI detects that and writes to stdout without --print. The result is src/sandboxes/e2b.ts, tagged // flue-blueprint: sandbox/e2b@1, exporting e2b(sandbox) — a SandboxFactory rooted at /home/user. It implements Flue’s SandboxDriver directly against the E2B SDK rather than shelling out: To pull in a later revision of the adapter, pipe npx flue update sandbox e2b --print into your coding agent the same way; the marker comment is how the guide recognizes an existing install.

Write the agent

The factory is where you own the sandbox: create it with the E2B SDK, then hand it to e2b().
src/agents/analyst.ts
Two details that matter:
  • createSandbox is called once per agent initialization, never on a re-render. Creating the sandbox inside it — not in the module body — is what keeps one sandbox per conversation instead of one per render.
  • timeoutMs is E2B’s, not Flue’s. The default sandbox timeout is 5 minutes; a conversational agent usually wants more. Nothing in Flue extends it for you.

Run it

Flue streams tool activity to stderr and the final reply to stdout:
The sandbox is now in your E2B dashboard. The metadata you set at creation is how you find it again from the SDK:

Keep the same sandbox across turns

Flue conversations persist across flue run invocations. Sandboxes do not, unless you make them: the default factory above creates a fresh one every time the agent initializes. createSandbox(options) receives options.id — the agent instance id — so store it in metadata and look it up first.
src/agents/durable.ts
Two runs of the same conversation id now share one workspace, across separate processes:
Including 'paused' in the state filter covers longer gaps: Sandbox.connect() resumes a paused sandbox with its filesystem intact, so a conversation can go quiet for days and come back to the same disk. Reconnecting can only ever lengthen a sandbox’s window. connect() keeps whatever time is left and raises it to the requested timeout — the 5-minute default, or the timeoutMs you pass — when the remainder is shorter than that. A sandbox with 20 minutes left keeps its 20 minutes either way.

Use a custom template

Sandbox.create() takes a template name or ID as its first argument. Bake the language runtimes, system packages, and repo checkout your agent needs into a template so no turn is spent on apt-get:

Lifecycle is your job

The adapter never creates or kills anything — it only wraps the sandbox you pass in. What that leaves you:
  • Timeout is the backstop. timeoutMs at creation decides how long an unattended sandbox lives; the sandbox dies at that point unless you pause it.
  • Nothing kills the sandbox when the agent finishes. flue run exits and the sandbox keeps running. Call sandbox.kill() from a harness tool, from your server’s teardown, or from a sweep keyed on metadata.

Troubleshooting

The blueprint’s exec() calls sandbox.commands.run() without a try/catch, and the E2B SDK throws CommandExitError on any non-zero exit instead of returning a result. Flue turns that throw into a bare tool error, so the model sees <error>exit status 1</error> and never sees stderr.Unwrap the error into the result the SandboxDriver contract expects:
src/sandboxes/e2b.ts
The same cat of a missing file then reaches the model as cat: /home/user/does-not-exist.txt: No such file or directory plus the exit code — which is what it needs to fix its own command.
The E2B SDK reads E2B_API_KEY from process.env. flue run loads the project’s .env for you (--env <file> picks a different one). Anywhere else — a dev server, a built server, CI — put the key in the process environment yourself, or pass it explicitly with Sandbox.create({ apiKey: process.env.MY_KEY }).
The blueprint’s driver class uses a TypeScript parameter property (constructor(private sandbox: E2BSandbox) {}), which Node.js’s built-in type stripping refuses to compile — including on Node 24:
flue run compiles the file properly, so agents are unaffected. A standalone script that imports the adapter directly needs node --experimental-transform-types.
E2B’s files.remove() has no recursive or force flags, so the adapter rejects both rather than silently ignoring them. When a tree has to go, let the agent run rm -rf through bash instead — that path goes through commands.run() and works.

How the integration works

Learn more

Templates

Build custom sandbox templates with pre-installed dependencies

Sandbox persistence

Pause, resume, and manage sandbox lifecycle

JavaScript SDK

The e2b package the adapter is built on