Repository navigation
Add DigitalOcean Managed Agents as a sandbox backend - #2128
Merged
Merged
Conversation
Adds a tenth sandbox backend that runs each qm computer as a Managed Agents microVM. Sessions are created with agent: none, which asks for a bare sandbox with no managed agent loop, no event-translation runtime and no model credential of its own, so qm keeps its own agent loop and its own approval gates. The backend talks to two planes. Session lifecycle is bearer-authenticated REST under /v2/agents/sessions, with a DigitalOcean IAM token that carries the team identity owning the sessions. Exec and file transfer take a different route: a WebSocket port-forward to guest port 8443, bridged to a local TCP listener, carrying gRPC to the sandbox-agent that every microVM runs. That path has no request deadline, so a command is bounded by qm's SANDBOX_TIMEOUT_SEC rather than by a control-plane buffer. Transfer inside the guest is confined to /workspace, so a scope's home lives at /workspace/home. Sessions are created allowing bash outright, because the provider would otherwise hold every command at its own approval gate behind qm's. Select it with SANDBOX_BACKEND=do-managed-agents and DO_AGENTS_API_TOKEN. Set DO_AGENTS_SNAPSHOT_S3_BUCKET so a scope's home survives losing its session: the provider exposes session checkpoints, but qm does not capture them yet, so a portable tar is the only recovery path. The proto is vendored rather than consumed from a published SDK. That is a stopgap: the REST exec endpoint buffers in the control plane, so it clamps to four minutes and 1 MiB and carries no per-command environment, which rules it out for a general sandbox. When streaming exec reaches the public edge, the vendored proto, the tunnel and the two gRPC dependencies all go away.
The fake service answers the session REST surface and serves a real WebSocket port-forward bridged to a real gRPC sandbox-agent over a temporary directory, so the tests exercise the actual wire path rather than a stub of it: upgrade, bridge, gRPC exec, upload and download. Covers the cases that are easy to get wrong and expensive to debug in production. A port-forward the edge refuses reads as a retryable gone error carrying the rejection, not as a command that may have half run. A transient lifecycle failure while waiting for readiness is polled through, while one that never clears surfaces its own status rather than a readiness timeout. Pause and resume preserve the home, scratch sandboxes are reference counted and never persisted, and the egress allowlist is only claimed when a proxy is actually configured.
docs/managed-agents.md covers what the backend provides, the two planes it talks to, and each operation: create, exec, upload and download, pause and resume, and delete. Pause and resume are both automatic and there is no manual control for either, which is worth stating plainly because the sandbox tool offers no verb for them. The gotchas are the things that cost real debugging time. Deleting a conversation does not delete the microVM, because this backend implements no deep-idle reaping. PUBLIC_API_URL has to be reachable from inside the guest, since microVMs call back into core for connector credentials and file callbacks, and a deployment behind localhost runs shell commands fine while failing every cron and send. The bare base carries no jq, rg or unzip, all of which prompts reach for by name. The ADR records the two asks on the provider and why the vendored proto is a stopgap rather than a choice.
Resume completes in about a second, but the fixed 2s poll added most of a second interval to every warm turn. Poll quickly at first and fall back to 2s so slow resumes don't hammer the API.
deploy/do-managed-agents/Dockerfile is the E2B image plus ripgrep with HOME at the adapter's home, registered on the agentless sandbox base. With DO_AGENTS_TEMPLATE set, the profile advertises jq, rg, unzip and wget as installed. docs/managed-agents.md covers building it.
The portable home tar survives a destroyed session, but it is not the provider's machine-state checkpoint. Call CreateCheckpoint while the session is still running so the live microVM is captured before pause.
1 of 2 tasks
A failed delete used to drop the only reference to the previous checkpoint, and a failed home tar cleared the capture throttle because both shared lastSnapshotMs.
Restart calls the harness rollback API so the same session boots a new microVM from the stored capture.
Wrap the command so timeout -k force-kills stragglers and the exit code comes back directly; treat 124 and 137 as timed out.
16francej
force-pushed
the
managed-agents-sandbox-backend
branch
from
October 10, 2026 00:52
239fac5 to
f24b725
Compare
added 5 commits
October 10, 2026 00:58
Report swallowed kill, disconnect and probe failures; fail a command stream that ends without an exit status; decide missing files from gRPC NOT_FOUND or a test -e probe instead of matching error text; list gh as not installed; drop the backend from the legacy sandbox import; strip comments from the template and vendored proto.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Moved from #2067 (by @nkurraDO) onto a branch in this repo so the full CI suite, including "Certify tested tree", can run. Original authorship preserved; also folds in #2156 (checkpoints and rollback).
Adds a sandbox backend that runs each qm computer as a DigitalOcean Managed Agents microVM. Opt-in via
SANDBOX_BACKEND=do-managed-agents; nothing changes for other backends.Sessions are created with
agent: none, which asks for a bare sandbox: no managed agent loop, no event-translation runtime, and no model credential of its own. qm keeps its own agent loop and its own approval gates, and the provider supplies the machine.How it talks to the provider
/v2/agents/sessions, with a DigitalOcean IAM token carrying the team identity.sandbox-agentevery microVM runs.The second plane exists because the REST exec endpoint buffers in the control plane (four-minute and 1 MiB clamps, no per-command env). The port-forward has no request deadline, so commands are bounded by qm's
SANDBOX_TIMEOUT_SEC; a command that hits it is killed in the guest and returns a normal timed-out result.Configuration
deploy/do-managed-agents/register-template.shbuildsdeploy/do-managed-agents/Dockerfile(node 24 plus git, jq, rg, Python, etc.) and registers it as a template on your own DO team; then setDO_AGENTS_TEMPLATE. Templates are per team, so each deployment registers its own.egressEnforcement: "domain"; otherwise"none".Worth a reviewer's attention
The vendored proto is a stopgap. Every other backend uses a published SDK; this one vendors
managed-agents-sandbox-agent.protobecause the buffered REST exec can't carry a general sandbox. When streaming exec reaches the public edge, the proto, tunnel, and both gRPC deps go away.No deep-idle reaping from qm's side. A scope's machine outlives its conversation and is reclaimed by the provider's idle timeout (
DO_AGENTS_IDLE_TIMEOUT_SEC) or an explicit retire. Whether the provider's idle timeout pauses or deletes a session is still being confirmed with DO; it decides how much the snapshot bucket matters.Testing
The fake control plane answers the session REST surface and serves a real WebSocket port-forward bridged to a real gRPC
sandbox-agentover a temp directory, so tests exercise the actual wire path (upgrade, bridge, exec, upload, download), plus checkpoint, rollback, and pause/resume.scripts/managed-agents-*-smoke.tspass against the real provider, including pause/resume and a real qm turn. The team template was verified end to end on a dev qm instance.docs/do-managed-agents.md.register-template.shas a whole (only its individualdoctlcalls were run), restart through a real qm turn, and the provider's idle-timeout behavior.Wall of shame review report
Reviewed commit:
71635d4d15c64640e9816c6f11bd92f4ff5f2051Independent fresh-context reviewer per lens.
managed-agents-client.ts,managed-agents-sandbox.tsandmanaged-agents-tunnel.ts, reusing the shared exec, process-session, ro-layer, layer-tool and home-snapshot helpers. Teardown still carries two persistence paths (provider checkpoint plus optional S3 home tar) with separate interval bookkeeping, which is more than most backends need.timeout -k 5wrapper, gRPCtimeout_seconds, clienttimeoutMs + 30s) with timeouts inferred from exit codes 124/137. The new INTERNAL-errortest -eprobe inreadFileBytesis deterministic rather than text-matching, though it and the no-exit-status path have no direct tests yet.managed-agents-sandbox.tsmirrorse2b-sandbox.tsstructurally (adopt-by-name, scratch refcounts, withSession retry, pause on teardown), as expected for a parallel backend; the shared helpers are reused rather than copied.@grpc/proto-loaderand the WebSocket port-forward inmanaged-agents-tunnel.tsare justified; REST goes through the sharedfetchWithRetry.do_managed_agents_sandbox_bodiesmap and sessions are re-adoptable by name vialist(name); in-process maps are caches, matching the E2B backend.SANDBOX_BACKENDand nineDO_AGENTS_*env vars insrc/config.ts, on par with E2B. OptionalDO_AGENTS_TEMPLATEcreates two image modes, and the interval, idle-timeout and name-prefix knobs are not yet needed.managed-agents-sandbox.tsnow listsgh,jq,rg,unzipandwgetas not installed unless the template supplies them, so it no longer overclaims. The default bare base template is still unpinned, butqm-sandboxis a team-registered template outside the repo, so it cannot be a portable default; documenting and smoke-checking the base image tools is the right follow-up rather than a blocker.managed-agents-client.ts(about 750 lines) bundles REST, gRPC dialing and the exec watchdog, but it is one cohesive provider client mirroringe2b-client.ts, with the tunnel split out.isMissingPathis gone:NOT_FOUNDmeans missing andINTERNALfalls back to atest -eprobe, andcomputerStatuscompares the probe output exactly.swallowAsorreportError(failure()body read,disconnect(), stall probe, orphan kill after failed hydration), and download errors carrycause. A stream that ends without an exit frame now throwsManagedAgentsCommandLostErrorinstead of inventing exit code -1.legacySandboxesupgrade import insrc/wiring.ts.dialSandboxAgentre-parses the proto on every reconnect and each output stream buffers up to 16 MB, both minor.