The E2B adapter: Managoat.Sandbox implemented against e2b.dev.
Identity
E2B assigns sandbox ids; Fountain's name-keyed identity is emulated by
stamping the minted name into sandbox metadata and resolving by a
server-side metadata filter on every operation. create/2 adopts an
existing sandbox with the same name, and the reaper converges the
duplicate a create/create race can leave behind.
Suspend
Advertises :suspend with real calls behind it: suspend/1 is an
explicit pause (filesystem + memory snapshot, retained indefinitely at
storage-only cost) and resume/1 is the /connect call that restores
it. Every running E2B sandbox carries a TTL, so live commands heartbeat
it (Managoat.Sandbox.E2B.CommandServer); sandboxes are created with
autoPause: true so a missed heartbeat degrades to a pause, never a
kill. Operations that find the sandbox paused resume it first — an
auto-paused sandbox under a ready row self-heals on the next use.
Streaming and reattach
Commands run through envd's Connect-RPC process.Process service
(Managoat.Sandbox.E2B.Envd). envd does not replay a reconnected
process's output — Connect tails from now, and detached output is
discarded — so detachable spawns run under a journaling shim: stdout and
stderr tee into /tmp/fountain/<tag>.{out,err} and the exit code lands
in <tag>.exit. attach/3 then satisfies the replay-from-start contract
exactly by spawning a tail -c +1 -f replayer over the journals (byte
zero onward, then live) that ends when the exit file appears; the real
exit code is read from the file. stdin still addresses the original
process by tag.
Template
Sandboxes are created from E2B_TEMPLATE, which must carry the agent
CLIs and the sprite user layout the provisioning pipeline assumes —
see images/e2b/ for the reference Dockerfile.