modern-fireman-99755
10/01/2026, 4:43 PMquick-window-91705
10/01/2026, 7:52 PMquick-window-91705
10/01/2026, 7:59 PMmodern-fireman-99755
10/01/2026, 8:26 PMquick-window-91705
10/01/2026, 8:29 PMquick-window-91705
10/01/2026, 8:30 PMroot: Path
StoredRuntime.home: Path | None
Trash.discard(path: Path)
path.rename(...)
safe_rmtree(...)
shutil.disk_usage(...)modern-fireman-99755
10/01/2026, 8:34 PMquick-window-91705
10/01/2026, 8:35 PMquick-window-91705
10/01/2026, 8:38 PMmodern-fireman-99755
10/01/2026, 8:40 PMmodern-fireman-99755
10/01/2026, 8:40 PMquick-window-91705
10/01/2026, 8:44 PMquick-window-91705
10/01/2026, 8:46 PMquick-window-91705
10/01/2026, 8:50 PMclient / orchestrator
-> remote agent-server
-> local Docker/container/workspace/storage
Current abstraction works.
Case B: central agent-server manages remote execution service
central agent-server
-> remote sandbox API
-> storage inaccessible as local Path
I think we mostly operate on case A, do you think it would be easy to defer case B support to future PRs?modern-fireman-99755
10/01/2026, 8:55 PMquick-window-91705
10/01/2026, 8:58 PMmodern-fireman-99755
10/01/2026, 8:58 PMclass RuntimeBackend(Protocol):
def runtimes(self) -> list[RuntimeRecord]: ... # id, last_active, state (running/paused/stopped)
def idle(self, id) -> AbstractAsyncContextManager[bool]: ...
def supports(self, step: Step) -> bool: ... # local worktrees: no RETIRE; remote: maybe no DEPENDENCIES
async def apply(self, id, step: Step) -> bool: ... # do the step; True if something was freed
def usage(self) -> float | None: ... # local: disk fraction at root; remote: sandbox count / quota
class FilesystemBackend(RuntimeBackend): # shared base: Trash + selectors
... # Docker, local worktrees, Podman, Apptainer subclass this
class CloudSandboxBackend(RuntimeBackend): # apply() calls pause() / DELETE
...
But the most important question is: do we want that? or?quick-window-91705
10/01/2026, 9:01 PMmodern-fireman-99755
10/01/2026, 9:02 PMmodern-fireman-99755
10/02/2026, 7:32 AMmodern-fireman-99755
10/02/2026, 7:33 AMmodern-fireman-99755
10/02/2026, 2:16 PMmodern-fireman-99755
10/02/2026, 2:16 PMcalm-artist-7206
10/02/2026, 2:27 PMcalm-artist-7206
10/02/2026, 2:30 PMcalm-artist-7206
10/02/2026, 2:34 PMcalm-artist-7206
10/02/2026, 3:01 PMcalm-artist-7206
10/02/2026, 3:22 PMcalm-artist-7206
10/02/2026, 3:24 PMPerhaps is overkilling and we should just look at the local caseYes, I think we don’t have a case of a sandbox without agent-server. In fact, and please correct me if wrong, I don’t think the new “inner” agent-servers can be in something else than Docker?
calm-artist-7206
10/02/2026, 3:27 PMcalm-artist-7206
10/02/2026, 3:28 PMcalm-artist-7206
10/02/2026, 3:28 PMquick-window-91705
10/02/2026, 3:32 PMcalm-artist-7206
10/02/2026, 3:36 PMmodern-fireman-99755
10/05/2026, 1:14 PMRuntimeBackend hierarchy in the SDK would serve none of them better.)
• Native first. When the provider has idle and expiry settings, set them at creation and let the provider enforce them. Only build a loop for what it can't do.
• Timers need an always-on owner.
I would also like to have the opinion of @fierce-account-55183 as you are an expert in runtime-apis 😉
Please let me know what do you think. I hope i didn't forget nothing importantmodern-fireman-99755
10/05/2026, 1:15 PMmodern-fireman-99755
10/05/2026, 4:44 PMmodern-fireman-99755
10/05/2026, 5:36 PMcalm-artist-7206
10/05/2026, 7:41 PMmodern-fireman-99755
10/05/2026, 10:11 PMcalm-artist-7206
10/05/2026, 10:16 PMmodern-fireman-99755
10/06/2026, 5:31 AMmodern-fireman-99755
10/06/2026, 10:21 AMmodern-fireman-99755
10/06/2026, 10:21 AMmodern-fireman-99755
10/06/2026, 10:48 AMmodern-fireman-99755
10/06/2026, 10:48 AMcurved-angle-80449
10/06/2026, 4:05 PMcalm-artist-7206
10/06/2026, 7:48 PMsmolpaws: I like this revision. The ownership split is right: the agent-server cleans files it understands; the service that created the sandbox manages its lifetime. That keeps provider-specific code out of the SDK.
I'd tighten four things before calling it ready:
• Export must succeed before deletion. "Downloaded" should mean the archive reached durable storage and was checked—not just that the server started sending it. History export also does not necessarily preserve uncommitted work. The confirmation should say exactly what survives.
• Old does not mean safe to delete. State-file timestamps can help choose candidates, but the reclaimer must check again under the idle lock before removing anything. Shared caches and dependencies need particular care: another conversation may still use them.
• Dry run is a preview, not a promise. Work can restart between preview and apply. The apply pass must recheck and skip anything that became active.
• "Archived" currently promises more than export delivers. The document says history stays readable, but also says importing exported history is a follow-up. A downloaded zip and history readable in Canvas are different user experiences. I'd make that gap explicit.
The warning about polling is especially good. Keeping the agent-server's idle clock unchanged is insufficient if opening its tunnel wakes the provider. Native timers avoid that trap.
My take: keep this architecture, and spend the next effort on safe deletion and recovery—not a broader provider abstraction. The proposed storage-status and reclaim endpoints seem useful even with one provider.
FWIW my main thought seems solved, I do think the client that created the box (or a maintenance script of sorts) should have responsibilitymodern-fireman-99755
10/06/2026, 7:49 PMmodern-fireman-99755
10/06/2026, 7:49 PMcurved-angle-80449
10/07/2026, 3:14 AMlease_expires_at field to the Runtime model in runtime-api and a POST /extend_lease endpoint that lets an owner tick the lease, preventing idle-reap regardless of idle time (still capped by the 12h session max).
I would then add a companion POST /sandboxes/{sandbox_id}/extend-lease in OpenHands/enterprise that passes through to runtime-api. This lets warm-pool controllers hold pre-initialized sandboxes and late bind to conversations (which some customers need due to extensive setup some of which can't be baked into a custom image).
I admit the term lease is ambiguous here and should be changed in my proposal since we have three kinds of leases in cloud: 1. session-idle (my proposal), 2. cleanup of pods after snapshot (seems like this is internal only and shouldn't be exposed in user api), 3. max sandbox life time (this could be managed via an api too).
I see your rev 3 design prefers setting per-sandbox idle_timeout_seconds once at creation and has no extension mechanism. Should I realign on that or could we support both?