B3OS serves E2B's sandbox API and envd, the daemon inside each sandbox, at /compat/e2b, with E2B's field names, status codes and error bodies. Code written for the e2b 2.62 SDKs (TypeScript and Python) creates B3OS microVMs and runs commands and file calls in them with three environment variables changed:

bash
export E2B_API_KEY="$B3OS_API_KEY"export E2B_API_URL="https://beta-api.b3os.org/compat/e2b"export E2B_SANDBOX_URL="https://beta-api.b3os.org/compat/e2b/envd"

The sandboxes underneath are the native ones: a sandbox created here is listed, billed and limited like any other, and the native API works on it too (fork, snapshot, ports, run_code). The key needs sandboxes:read, sandboxes:write and sandboxes:run.

Clients

bash
curl -s https://beta-api.b3os.org/compat/e2b/v2/sandboxes \ -H "X-API-KEY: $B3OS_API_KEY" \ -H 'content-type: application/json' \ -d '{"templateID": "base", "timeout": 300, "metadata": {"run": "e2b"}}'

The answer holds sandboxID (the B3OS ID in its DNS-safe form, sbx-…) and envdAccessToken, which envd calls send as X-Access-Token with E2b-Sandbox-Id: <sandboxID>.

Where it stands: the e2b 2.62.0 TypeScript and Python quickstarts in the repository's bench/sandboxes/e2b (create, commands with exit codes and streamed output, files, the process calls, list, get, timeout, pause, connect, kill, and the refusals below) passed against beta-api.b3os.org on 2026-11-02 with only the three variables set: all 23 TypeScript steps and all 11 Python steps (bench/sandboxes/results/20261102T2058Z-e2b-quickstart-prod).

Frameworks

These agent frameworks run on B3OS through their own E2B backend, with the three variables above and the agent's model on the node's OpenAI-compatible API. In a run against beta-api.b3os.org on 2026-11-14, from the node's own host, each framework's tool loop on b3os/qwen3.5-9b ran uname -a in a sandbox, wrote the output to /home/user/proof.txt, read it back and answered with its first word (bench/sandboxes/results/20261114T101650Z-e2b-frameworks-prod). AG2 and Google ADK met the pass rule in bench/sandboxes/e2b/frameworks/README.md on the second attempt: in the first, the model's first call was uname -a > /home/user/proof.txt, and the rule counts uname only when its own result shows Linux.

FrameworkVersionOn B3OSSettings
AG2ag2 1.3.1E2BEnvironment behind SandboxShellTool: the agent's shell commandsThe three variables. Model: OpenAICompletionsConfig(base_url=...)
Google ADKgoogle-adk 2.13.0E2BEnvironment behind EnvironmentToolset: Execute, WriteFile, ReadFileThe three variables. Model: LiteLlm(model="openai/b3os/qwen3.5-9b", api_base=...) on litellm 1.106.2; the run needed the litellm[mcp] extra. The run's adk.py adds a wire log, a metadata tag (a wrapper on AsyncSandbox.create) and LITELLM_LOCAL_MODEL_COST_MAP=True, so LiteLLM uses its bundled price list instead of fetching one from GitHub; none of the three changes where the sandbox points

The AG2 lines (the run's bench/sandboxes/e2b/frameworks/ag2.py adds a wire log, a metadata tag and, after the answer, a verify read: cat /home/user/proof.txt through the same E2BEnvironment):

bash
pip install "ag2[openai]==1.3.1" "openai==3.45.0" "e2b==2.62.0"
python
import asyncioimport osfrom ag2 import Agentfrom ag2.config import OpenAICompletionsConfigfrom ag2.extensions.e2b import E2BEnvironmentfrom ag2.tools import SandboxShellToolasync def main(): env = E2BEnvironment(sandbox_timeout=300) agent = Agent( "agent", "You work in a remote Linux sandbox through your tools. Use a tool for every step; never guess an output.", config=OpenAICompletionsConfig( model="b3os/qwen3.5-9b", api_key=os.environ["B3OS_API_KEY"], base_url="https://beta-api.b3os.org/v1/infer", ), tools=[SandboxShellTool(env)], ) try: reply = await agent.ask( "Run uname -a in the sandbox, write the output to /home/user/proof.txt, " "read it back, and answer with its first word." ) print(reply.body) finally: await env.aclose()asyncio.run(main())

The model's tool calls, what each returned, and the answer, from ag2.json in that run:

text
run_shell_command uname -a Linux sbx-01m7cryqkcvhw3sb5pbp4gynph 6.1.188 #1 SMP PREEMPT_DYNAMIC Fri Oct 23 16:17:47 UTC 2026 x86_64 x86_64 x86_64 GNU/Linuxrun_shell_command uname -a > /home/user/proof.txtrun_shell_command cat /home/user/proof.txt Linux sbx-01m7cryqkcvhw3sb5pbp4gynph 6.1.188 #1 SMP PREEMPT_DYNAMIC Fri Oct 23 16:17:47 UTC 2026 x86_64 x86_64 x86_64 GNU/LinuxThe first word of the output is **Linux**.

Pydantic AI Harness and TanStack AI finished the same task, but on B3OS they lose exit codes: see What is refused.

What works

SDK callOn B3OS
Sandbox.createTemplates base (the default), code-interpreter and agent (templates), with envs, metadata, timeoutMs and lifecycle.onTimeout: "pause". A retry with the SDK's Idempotency-Key returns the same sandbox
Sandbox.list, Sandbox.getInfoRunning and paused sandboxes, with E2B's metadata and state filters and nextToken pages
setTimeout, pause, Sandbox.connect, killAs in E2B: connect resumes a paused sandbox and returns a new envd access token
commands.run, commands.start, background: true, onStdout, onStderrA process belongs to the sandbox, so closing the stream leaves it running; commands.connect streams it again from its start
commands.list, commands.killProcesses started through envd
files.read, files.write, files.writeFiles, files.list (with depth up to 8), files.exists, files.getInfo, files.makeDir, files.rename, files.removeRelative paths start at the user's home (/home/user; /root with user: "root")
network.allowOut with denyOut: ["0.0.0.0/0"], or allowInternetAccess: falseB3OS egress: only the named hosts (example.com, *.example.com, host:port), or nothing

What is refused

A create, pause or connect option B3OS cannot honor answers 400 naming the field (unsupported_option). A body member B3OS does not read is accepted and ignored, so a field a later SDK release adds does not fail the call.

OptionInstead
mcpRun your MCP server as a process in the sandbox (commands.run with background: true)
lifecycle.autoResumeResume a paused sandbox with Sandbox.connect
volumeMountsKeep files in the sandbox and save it with a snapshot
iam, and network fields other than allowOut and denyOutNot in the beta
onTimeout.mode: "filesystem", pause without memory, connect with onResume: "reboot"A paused sandbox always keeps its memory

envd refuses two options with Connect's invalid_argument, naming the option, and one with 400:

OptionInstead
commands.run with stdin: truePass input in the command (a here-document or a file)
sandbox.ptyOpen a terminal with the native GET /v1/sandboxes/{sandbox_id}/pty
files.write with gzip (400)Send the file uncompressed

Two envd behaviors differ from E2B's (envd-probe.json in the frameworks run):

  • A command from the Python SDK runs in /workspace whatever cwd it names: the SDK runs each command through a login shell (the probe shows the login profile's cd /workspace taking effect). E2B runs it in cwd, else the user's home. Use absolute paths, or cd in the command.
  • Each command leads its own session, so setsid forks and exits 0 at once, and output written after that is lost. Use setsid -w, or no setsid.

Two frameworks trip on these and on the stdin refusal, so they run in part:

FrameworkWhat worksWhat is refused
Pydantic AI Harness, pydantic-ai-harness[e2b] 0.64.0Shell's run_command ran the command, wrote the file with > and read it with catThe backend's run reports exit code 0 for exit 5 (envd-probe.json, harness_run_exit_5). FileSystem's write_file answers "/home/user/proof.txt is outside the project root /workspace", also with E2BSandbox(working_dir="/home/user"), and the harness's file read raises "FIFO reads are not supported" for a plain file
TanStack AI, @tanstack/ai-sandbox-e2b 1.0.0The task ran through three tools the bench wrote, exec, write_file and read_file, thin wrappers over the handle's process.exec, fs.write and fs.read, because TanStack ships no exec tool for a text adapter. fs.write, fs.read, and process.exec of a command that prints at once workprocess.exec runs exec setsid bash -c, so exit 5 reports exit code 0 and sleep 1; echo late returns no output. process.spawn opens stdin and gets [invalid_argument] stdin: true is not supported by B3OS envd; pass input in the command (a here-document or a file)

Template builds (Template.build, POST /v3/templates) answer 501 not_implemented with the alternative: start from a built-in template, install what you need, and create from a snapshot. files.watchDir and getMetrics are not served and answer 404 in E2B's shape.

The envd access token

envd calls carry no API key, only the sandbox's envd access token, so B3OS keeps it under these rules:

  • It is b3os_et_ followed by 32 random bytes. The node keeps only its SHA-256 in the sandbox's record; nothing else stores it.
  • Only the create and connect answers return it, never a list or get answer or the native API.
  • It works while the sandbox is running or paused, for at most an hour from issue, and while the key that minted it is not revoked. Kill, delete and the timeout end it at once.
  • Sandbox.connect mints a new one, bound to the connecting key; the previous one keeps working for a minute, then never again.
  • Forks and sandboxes restored from a snapshot start without a token: Sandbox.connect gives them their own.
  • It grants what sandboxes:run grants on that one sandbox (commands and files), as the key that minted it.

Every refused envd call gets the same 401 with envd_token_invalid in the message, whether the token is missing, wrong, expired or rotated out, or the sandbox is missing, ended or another organization's.

E2B's signed file URLs (downloadUrl and uploadUrl in TypeScript, download_url and upload_url in Python) are not supported: the node keeps no token it could check a signature with, so a request carrying signature or signature_expiration gets that 401. Read and write files through files.read and files.write.

Plan limits apply as to native sandboxes: see sandbox limits.

Ask a question... ⌘I