OpenHands agent-server · design proposal · 2 Oct 2026
One reclaimer for local and remote runtimes
Every conversation leaves a runtime behind: files on our disk for Docker and local worktrees, or a sandbox at a provider for remote runtimes. This proposal manages both with the same policies, and keeps what the current PR stack (#5263 → #5433) already built.
The architecture
The five building blocks
| Pattern | Solves | Exists today as |
|---|---|---|
| Reconciler loop | Providers change state behind our back (Daytona stops sandboxes after 15 min by default). Re-reading every pass makes those races harmless. | Reclaimer.run_pass() |
| Bridge | Policies and backends vary independently: a new provider gets every policy, and a new policy works on every backend. | Reclaimer + ReclaimableStorage |
| Role interfaces | Backends differ in what they can do. Each implements only Listable, Suspendable, Deletable, FileReclaimable or NativeLifecycle, and the type checker catches a missing method. | one protocol; local retire() returns [] |
| Native first, loop as fallback | Most providers already have idle and expiry settings. Configure those; run our loop only for the rest (E2B never auto-deletes). | not yet |
| Commands: plan, then apply | Every step destroys something. A plan doubles as a dry-run report and a test fixture. | select outside the lock, move inside it |
What each backend can do
From the SDK code and the providers' official docs, checked 2 Oct 2026. ✓ yes · ~ partly · ✗ no · — not applicable.
| Backend | List + last activity | Suspend, files kept | Delete | File tiers | Native lifecycle |
|---|---|---|---|---|---|
| Docker (ours) | ✓ from our state file | ✓ stop container | ✓ retire | ✓ | — |
| Local worktrees | ✓ from our state file | — | ✓ on delete | ✓ | — |
| Daytona | ✓ lastActivityAt | ✓ | ✓ | ✗ | ✓ stop, pause, archive, delete |
| E2B | ~ list, no activity | ✓ incl. memory | ✓ | ✗ | ~ auto-pause, never deletes |
| Modal | ~ list, no state | ✗ no pause | ✓ | ✗ | ~ timeouts terminate |
| Cloudflare Sandbox | ✗ no list | ✗ sleep wipes files | ✓ | ✗ | ~ sleep after idle |
| OpenHands Cloud | ~ list endpoint, no activity field | ✗ no pause | ✓ | ✗ | unverified |
Seven of the fifteen providers surveyed expose no last-activity time, so the activity tracker has to be ours.
Where it runs
- Local runtimes: inside the agent-server, which owns the files. This is what the PR stack does today.
- Remote runtimes: in a long-lived process that created the sandboxes and sees every conversation's activity, such as Agent Canvas, the automation service, or the sandbox-server for Cloud. The agent-server can't manage its own remote sandbox from the inside.
Trade-offs
Pros
- One set of policies for every runtime, written and tested once.
- Gaps are explicit. A backend that can't pause simply doesn't implement
Suspendable, and the type checker enforces it. - Works with providers' own settings instead of racing them.
- Safe by construction: it re-reads state every pass, acts only while a runtime is idle, and can print a dry run before deleting anything.
- Builds on the current stack.
Reclaimer,idle(),Trashand the selectors move in unchanged.
Cons
- More moving parts than today's file-only reclaimer, for backends nobody uses yet.
- Native settings are written at creation. Changing a policy later means re-syncing every existing sandbox, or the two drift apart.
- For most providers we must track activity ourselves, so only a host that sees every conversation can run it.
- Common-denominator risk: archive tiers, memory snapshots and snapshot billing need per-backend escape hatches.
- Eventual, not instant. Actions land on the next pass, minutes later.
- Remote backends need provider accounts or mocks to test, and break when provider APIs change.