Skip to content

feat(sandbox): add a Daytona sandbox provider - #320

Merged
earakely-scale merged 2 commits into
mainfrom
edgararakelyan/daytona-sandbox-provider
Oct 23, 2026
Merged

earakely-scale merged 2 commits into
mainfrom
edgararakelyan/daytona-sandbox-provider

Conversation

@earakely-scale

@earakely-scale earakely-scale commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

What

Adds Daytona as a built-in sandbox provider, daytona, behind a daytona extra:

pip install 'agentenv-framework[daytona]'
agent-env run hello --sandbox daytona

Each sandbox is a Daytona sandbox running its own Docker daemon, presented as a VM, with its ports on Daytona preview URLs. The docker-in-VM flows run on it unchanged: an env's docker-compose, an agent's docker run, image loading and artifact collection. Configuration, networking, cost attribution and limits are in the provider README.

The provider is adapted from Daytona's agentenv-daytona plugin (Apache-2.0), which is credited in NOTICE, the README and the commit.

Changes from the plugin

  • Clients: one SDK client per provider instance and event loop, as boxd does, in place of a module-level client shared by every provider built from the same config. agent-env closes short-lived providers mid-run (in teardown and when collecting artifacts), and with the shared client that closed it under other runs' live sandboxes. A sandbox handle reads itself again on its provider's current client when the one it came from was closed.
  • Teardown: get_sandbox raises SandboxNotFoundError for a sandbox that is gone, and terminate_sandbox deletes by id. Sandboxes are ephemeral with a hard time-to-live, so a teardown often finds its sandbox already gone; before, that teardown failed.
  • Read-only reconnect: get_sandbox no longer updates network settings or fetches a sample preview link.
  • Egress is never widened: a restricted sandbox used to have its allowlist widened, for its whole life, to the host of every signed URL it downloaded from. On a shared storage host that opens every bucket on it. Now a signed URL the policy admits is fetched in the sandbox, and anything else is uploaded through Daytona's file API, which needs no egress.
  • Static preview domains: EGRESS_HOSTS declares Daytona Cloud's preview domains (*.daytonaproxy01.net, *.daytonaproxy01.eu). They were learned at run time into a class attribute, which a fresh process didn't know. A self-hosted Daytona adds its own domain with egress_hosts.
  • Snapshot size: a request bigger than the configured snapshot's CPU, memory or disk is refused, as the provider guide asks, rather than booting a smaller sandbox.
  • Cancellation: cleanup after a failed or cancelled setup is shielded, so a second cancellation can't leave the sandbox running until its time-to-live.

Wiring

The same places boxd's PR (#263) touched:

  • the _BUILTIN_SANDBOX_PROVIDERS entry;
  • the daytona extra, also in dev, and uv.lock;
  • THIRD_PARTY_NOTICES.md and NOTICE;
  • the root AGENTS.md lists and the CLI --sandbox help;
  • a commented example in .agentenv/config.example.toml;
  • the capability gate and the shared provider tests: network-policy stamping, provider capabilities, the egress-host floor, host ports, config and optional extras;
  • a daytona case in the gateway and task-step integration tests;
  • a live smoke test.

uv.lock: daytona-api-client 0.224 requires pydantic>=2.13.5 and typing-extensions>=4.16.0, so the lock moves pydantic from 2.12.5 to 2.14.1, along with pydantic-core, typing-extensions, typing-inspection, aiohttp and yarl. Installs from PyPI resolve their own versions; this changes what CI and uv sync use.

Tests

  • Unit: 169 new tests in daytona_sandbox_provider_test.py, daytona_sandbox_test.py and daytona_network_test.py, offline against an in-memory SDK. To check they bite, 13 deliberate breakages of the provider in a scratch copy each failed them; examples are a handle that never re-reads, an unknown policy allowing downloads, unshielded cleanup and a shared client. The provider, config, capabilities, optional-extras and task suites pass: 2,308 passed, 6 skipped. ruff, uv lock --check and the plugin API check (no break) are clean.
  • Live, against a real Daytona organization:
    • agent-env run hello --sandbox daytona passes in about 30 s; with the local object store its artifact goes through the file-API upload.
    • daytona_sandbox_smoke_test.py passes, 2 of 2: nested Docker, a container port reached through its preview URL, a 9 MiB multi-part upload to a root-owned path checked by sha256, a reconnect from a fresh provider keeping ports and policy, then terminating twice and get_sandbox raising SandboxNotFoundError.
    • A multi-container MCP environment deployed in about 75 s: a gateway and several MCP servers, 15 images from a private registry delivered over signed S3 URLs, and docker compose up.
    • In another task, an A2A agent deployed in its own sandbox, took its files and skills, and was prompted through its preview URL.
    • Every sandbox from these runs, including those of runs that failed, was deleted, as confirmed with Daytona.
  • Not yet run end to end: a full multi-agent task. The test organization is below Tier 3, so Daytona allows sandboxes only its list of essential services, and the agent's model endpoint wasn't on it. Its quotas (10 GiB of memory in total, 10 GB of disk per sandbox) are also below what a five-sandbox task needs. The README covers the tier behaviour.

Notes

  • The first sandbox an organization creates builds the host image inside the create. A fallback chain gives each provider 180 s, so the README says to run hello once before putting daytona in a chain.
  • For the website docs: a Daytona section in Sandboxes, covering the extra, api_key, snapshot, the Tier 3 requirement for egress allowlists and the first host-image build.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge, with all three previous findings addressed and no new actionable issues found.

Summary

Adds daytona as an optional built-in provider for Docker-capable sandboxes.

  • AgentEnv can run Docker work inside a Daytona sandbox.
  • Daytona sandboxes keep network rules when they move files.
  • The CLI and config can select Daytona as a sandbox.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Create Daytona sandbox] --> B{Create outcome}
    B -->|Success| C[Restore port URLs and start Docker]
    C -->|Ready| D[Return sandbox]
    C -->|Setup fails| E[Delete sandbox]
    B -->|Caller cancels| F[Wait for create and delete its sandbox]
    F -->|Cleanup finishes| G[Raise cancellation]
    F -->|120 seconds pass| H[Continue cleanup in background]
    H --> G
    B -->|Uncertain failure| I[Find sandbox by unique name and delete]
Loading

Reviews (2) · Last reviewed commit: "fix(sandbox): delete a cancelled Daytona..." · Reviewed by Greptile

Daytona sandboxes as a built-in VM provider, `daytona`, behind the `daytona` extra. Each sandbox runs its own
Docker daemon and serves its ports on Daytona preview URLs, so the docker-in-VM flows run on it unchanged.

Adapted from Daytona's agentenv-daytona plugin (Apache-2.0), with these changes:
- one SDK client per provider instance and event loop, so closing one provider can't close another's client;
- get_sandbox raises SandboxNotFoundError for a sandbox that is gone and changes nothing, and terminate_sandbox
  deletes by id, so a sandbox its time-to-live already removed tears down cleanly;
- a restricted egress policy is never widened: a download the policy doesn't admit goes through the file API;
- Daytona's preview domains are declared in EGRESS_HOSTS instead of learned at run time;
- a request bigger than the configured snapshot is refused, and cleanup after a cancelled create is shielded.

Co-Authored-By: Daytona Platforms Inc. <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@earakely-scale
earakely-scale requested a review from a team as a code owner October 23, 2026 17:01
@socket-security

socket-security Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedpypi/​daytona@​0.224.196100100100100
Updatedpypi/​pydantic@​2.12.5 ⏵ 2.14.1100 +1100100100100

View full report

Comment thread src/agent_env/providers/sandbox_providers/daytona/lifecycle.py
Comment thread src/agent_env/providers/sandbox_providers/daytona/provider.py
Comment thread src/agent_env/providers/sandbox_providers/daytona/lifecycle.py Outdated
…ing, and log a snapshot's real size

A create cancelled in flight, as an interrupted run's is, used to re-raise at once and leave the delete to a
callback on a loop that was about to close, so its sandbox could run until its time-to-live. It now waits for
the create to answer and deletes what it made before the cancellation is raised, for at most _CANCEL_WAIT, after
which the delete carries on by itself.

A sandbox booted from a snapshot is logged at the snapshot's size, not the request's, and the rate-limit retry cap
is named.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
@earakely-scale
earakely-scale merged commit 4543e55 into main Oct 23, 2026
14 checks passed
@earakely-scale
earakely-scale deleted the edgararakelyan/daytona-sandbox-provider branch October 23, 2026 17:54
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.

1 participant