Skip to content

Add DigitalOcean Managed Agents as a sandbox backend - #2067

Closed
nkurraDO wants to merge 7 commits into
yc-software:mainfrom
nkurraDO:managed-agents-sandbox-backend
Closed

nkurraDO wants to merge 7 commits into
yc-software:mainfrom
nkurraDO:managed-agents-sandbox-backend

Conversation

@nkurraDO

@nkurraDO nkurraDO commented Oct 5, 2026

Copy link
Copy Markdown

Adds a tenth sandbox backend that runs each qm computer as a DigitalOcean Managed Agents microVM.

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

Two planes, deliberately:

  • Session lifecycle is bearer-authenticated REST under /v2/agents/sessions, with a DigitalOcean IAM token carrying the team identity that owns the sessions.
  • 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: it clamps to four minutes and 1 MiB and carries no per-command environment, which rules it out for a general-purpose sandbox. The port-forward has no request deadline, so a command is bounded by qm's own SANDBOX_TIMEOUT_SEC.

Configuration

SANDBOX_BACKEND=do-managed-agents
DO_AGENTS_API_TOKEN=...
DO_AGENTS_SNAPSHOT_S3_BUCKET=...

The bucket matters more than it looks. The provider exposes session checkpoints, but qm does not capture them yet, so a portable tar is the only recovery path: without the bucket, a destroyed or reclaimed session takes the scope's home with it.

Two things worth a reviewer's attention

The vendored proto is a stopgap, not a preference. Every other backend consumes a published SDK. This one vendors managed-agents-sandbox-agent.proto, which means one wire with two owners in two repos. It is there only because the buffered REST endpoint cannot carry a general sandbox. When streaming exec reaches the public edge, the vendored proto, the tunnel, and both gRPC dependencies all go away. The ADR records this.

Deleting a conversation does not delete the microVM. This backend implements no deep-idle reaping, so a scope's machine outlives the conversation and is reclaimed by the provider's idle timeout or an explicit retire. DO_AGENTS_IDLE_TIMEOUT_SEC is the backstop. That is a deliberate choice to document rather than a gap to fix quietly, and docs/managed-agents.md says so.

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 the tests exercise the actual wire path — upgrade, bridge, exec, upload, download — rather than a stub of it.

The cases covered are the ones that are cheap to get wrong and expensive to debug in production:

  • A port-forward the edge refuses reads as a retryable gone error carrying the rejection text, not as a command that may have half-run. Getting this wrong turns a connect failure into a non-retryable "may have partially executed".
  • A transient lifecycle failure while waiting for readiness is polled through; 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.

Verification on this branch: typecheck, lint, and knip clean; the two backend suites pass 41/41; the live smoke passes end to end against the real provider, including pause and resume.

On the full suite, this branch reports 8348 tests with 285 failures. Clean main at the same commit reports 8305 tests with the same 285 failures and the same 9 cancelled, so the branch adds 43 tests, all passing, and introduces no new failures. The 285 are pre-existing on main and unrelated to sandboxes.

Each of the three commits typechecks standalone.

@v-aisac v-aisac left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@nkurraDO
nkurraDO force-pushed the managed-agents-sandbox-backend branch 2 times, most recently from 51d67b3 to b02433c Compare October 6, 2026 14:02
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.
@nkurraDO
nkurraDO force-pushed the managed-agents-sandbox-backend branch 2 times, most recently from 978180e to b489653 Compare October 6, 2026 18:56
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.
@nkurraDO
nkurraDO force-pushed the managed-agents-sandbox-backend branch from b489653 to 306f05a Compare October 6, 2026 19:00
Josh France added 3 commits October 7, 2026 18:22
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.
@16francej

Copy link
Copy Markdown
Contributor

Thanks @nkurraDO! Moving this to #2128, a branch in the main repo, so the full CI suite can run. Same commits, your authorship is kept. Closing in favor of #2128.

@16francej 16francej closed this Oct 8, 2026
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