Skip to content

ai-sandbox-daytona: resume() returns a handle to a stopped sandbox — thread reuse does not work after the 15-minute auto-stop #1083

Description

@mu-hashmi

Versions: @tanstack/ai-sandbox-daytona 0.2.1, @tanstack/ai-sandbox 0.3.0, @daytona/sdk v0.191.0.

Summary

Daytona stops an idle sandbox after 15 minutes by default. The adapter does not set autoStopInterval. A stopped sandbox keeps its filesystem, and one SDK call (sandbox.start()) starts it again. resume() does not make that call. Thus lifecycle: { reuse: 'thread' } operates only when runs occur less than 15 minutes apart.

Mechanism

  1. Run 1 creates a sandbox, completes bootstrap, and ends. destroyOnComplete: false keeps the sandbox.
  2. The sandbox is idle for 15 minutes. Daytona stops it. The filesystem stays.
  3. Run 2 starts in the same thread. ensure() calls provider.resume (packages/ai-sandbox/src/sandbox.ts).
  4. DaytonaProvider.resume (packages/ai-sandbox-daytona/src/provider.ts) only calls daytona.get(id) and wraps the result. daytona.get is a metadata request. It is successful for stopped and archived sandboxes. It does not start the sandbox (sdk-typescript/src/Daytona.ts in daytona/clients, tag v0.191.0).
  5. ensure() accepts each non-null handle as a started sandbox. The fallback to snapshot restore or to a new sandbox occurs only when resume returns null.
  6. The first executeCommand goes to the sandbox toolbox. The toolbox returns HTTP 400 with a message that ends with Is the Sandbox started?. The run ends with this error.

lifecycle.keepAlive: '30m' gives no protection. Per docs/sandbox/lifecycle.md, no code reads keepAlive. The adapter sends no idle limit to Daytona.

Expected

docs/sandbox/providers.md says that Daytona has resume-by-id. A resumed handle must accept commands.

Impact

Each reuse: 'thread' deployment on Daytona gets this error when two runs are more than 15 minutes apart. For human chat threads, this is the usual case, not a rare case. Durable-run takeover with resume-by-id has the same defect. The user sees an HTTP 400 error in the run, not a cold start.

Fix direction

  1. In resume(), read sandbox.state.
  2. If the state is stopped or archived, call await sandbox.start() before you wrap the handle.
  3. You can also add autoStopInterval to DaytonaSandboxConfig (minutes; 0 = off).

With the state check, Daytona becomes a durable resume-by-id provider. The filesystem already stays through a stop. This also gives #1029 a Daytona path with no new code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions