Skip to content

Add DigitalOcean Managed Agents as a sandbox backend - #2128

Merged
16francej merged 19 commits into
mainfrom
managed-agents-sandbox-backend
Oct 11, 2026
Merged

16francej merged 19 commits into
mainfrom
managed-agents-sandbox-backend

Conversation

@16francej

@16francej 16francej commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

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

  • Session lifecycle is bearer-authenticated REST under /v2/agents/sessions, with a DigitalOcean IAM token carrying the team identity.
  • Exec and file transfer go over a WebSocket port-forward to guest port 8443, bridged to a local TCP listener, carrying gRPC to the sandbox-agent every 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

SANDBOX_BACKEND=do-managed-agents
DO_AGENTS_API_TOKEN=...
# optional
DO_AGENTS_TEMPLATE=qm-sandbox
DO_AGENTS_SNAPSHOT_S3_BUCKET=...
DO_AGENTS_EGRESS_PROXY_URL=...
  • Template (optional). Without it, sessions land on the provider's bare base. deploy/do-managed-agents/register-template.sh builds deploy/do-managed-agents/Dockerfile (node 24 plus git, jq, rg, Python, etc.) and registers it as a template on your own DO team; then set DO_AGENTS_TEMPLATE. Templates are per team, so each deployment registers its own.
  • Snapshot bucket (optional). Parking a scope takes a provider checkpoint, used to roll back a wedged computer in place. Checkpoints are bound to their session, so they don't survive a deleted session. The portable home tar covers that case, but it's only durable when a bucket is set; otherwise it lives in core's memory.
  • Egress proxy (optional). When set, the proxy host becomes the session's egress allowlist and the profile reports 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.proto because 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-agent over a temp directory, so tests exercise the actual wire path (upgrade, bridge, exec, upload, download), plus checkpoint, rollback, and pause/resume.

  • The two backend suites pass 58/58; typecheck, lint, and knip are clean.
  • Live: the smoke scripts in scripts/managed-agents-*-smoke.ts pass 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.
  • Setup lives in docs/do-managed-agents.md.
  • Untested: register-template.sh as a whole (only its individual doctl calls were run), restart through a real qm turn, and the provider's idle-timeout behavior.

Wall of shame review report

Reviewed commit: 71635d4d15c64640e9816c6f11bd92f4ff5f2051

Independent fresh-context reviewer per lens.

Lens Reviewer Verdict Score Assessment
Overengineering fresh-context reviewer Accept 65/100 About 1.5k production lines across managed-agents-client.ts, managed-agents-sandbox.ts and managed-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.
Band-aid fixes fresh-context reviewer Accept 65/100 Exec deadlines remain layered (timeout -k 5 wrapper, gRPC timeout_seconds, client timeoutMs + 30s) with timeouts inferred from exit codes 124/137. The new INTERNAL-error test -e probe in readFileBytes is deterministic rather than text-matching, though it and the no-exit-status path have no direct tests yet.
Duplication fresh-context reviewer Accept 60/100 managed-agents-sandbox.ts mirrors e2b-sandbox.ts structurally (adopt-by-name, scratch refcounts, withSession retry, pause on teardown), as expected for a parallel backend; the shared helpers are reused rather than copied.
Hand-rolling fresh-context reviewer Accept 75/100 DO publishes no SDK, so the vendored proto loaded through @grpc/proto-loader and the WebSocket port-forward in managed-agents-tunnel.ts are justified; REST goes through the shared fetchWithRetry.
Non-durability fresh-context reviewer Accept 80/100 Session and checkpoint state lives in the durable do_managed_agents_sandbox_bodies map and sessions are re-adoptable by name via list(name); in-process maps are caches, matching the E2B backend.
Config-matrix expansion fresh-context reviewer Accept 50/100 Adds a tenth SANDBOX_BACKEND and nine DO_AGENTS_* env vars in src/config.ts, on par with E2B. Optional DO_AGENTS_TEMPLATE creates two image modes, and the interval, idle-timeout and name-prefix knobs are not yet needed.
Provider drift fresh-context reviewer Accept 65/100 The profile in managed-agents-sandbox.ts now lists gh, jq, rg, unzip and wget as not installed unless the template supplies them, so it no longer overclaims. The default bare base template is still unpinned, but qm-sandbox is 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.
God files fresh-context reviewer Accept 75/100 managed-agents-client.ts (about 750 lines) bundles REST, gRPC dialing and the exec watchdog, but it is one cohesive provider client mirroring e2b-client.ts, with the tunnel split out.
Mismatched UI fresh-context reviewer Not relevant No UI changes.
Over-indexing on YC fresh-context reviewer Not relevant A generic provider backend; nothing is shaped around YC usage.
Regex fresh-context reviewer Accept 85/100 The error-text regex in isMissingPath is gone: NOT_FOUND means missing and INTERNAL falls back to a test -e probe, and computerStatus compares the probe output exactly.
YC info leaking into public qm fresh-context reviewer Accept 95/100 No YC identities, links or operational details in code, docs or tests.
Discarded error evidence fresh-context reviewer Accept 80/100 Swallowed failures now go through swallowAs or reportError (failure() body read, disconnect(), stall probe, orphan kill after failed hydration), and download errors carry cause. A stream that ends without an exit frame now throws ManagedAgentsCommandLostError instead of inventing exit code -1.
Legacy preservation fresh-context reviewer Accept 90/100 The new backend is no longer fed into the one-time legacySandboxes upgrade import in src/wiring.ts.
Over-testing fresh-context reviewer Accept 60/100 About 1,335 test lines (58 tests, all passing at this head) for about 1,530 production lines, using two fakes of the same service at different layers; consolidating onto the wire-level fake would trim it.
Performance fresh-context reviewer Accept 70/100 Ready polling and the 45s stall probe are bounded; dialSandboxAgent re-parses the proto on every reconnect and each output stream buffers up to 16 MB, both minor.
Token performance fresh-context reviewer Accept 85/100 Only the profile spec reaches the prompt, and it is comparable in length to the E2B profile.
Honorable mentions fresh-context reviewer Accept 85/100 Comments are removed from the Dockerfile and the vendored proto (a few stray blank lines remain inside proto messages); no screenshots.

nkurraDO and others added 7 commits October 6, 2026 11:33
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.
Josh France and others added 3 commits October 8, 2026 20:19
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.
gane5hvarma and others added 4 commits October 9, 2026 11:34
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
16francej force-pushed the managed-agents-sandbox-backend branch from 239fac5 to f24b725 Compare October 10, 2026 00:52
Josh France 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.
@16francej
16francej merged commit 51385cf into main Oct 11, 2026
25 of 26 checks passed
@16francej
16francej deleted the managed-agents-sandbox-backend branch October 11, 2026 02:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants