Skip to content

feat(sandbox): a provider that creates VMs loads an agent's image unless it declares otherwise - #164

Merged
earakely-scale merged 3 commits into
mainfrom
sandbox-accepts-default
Oct 12, 2026
Merged

earakely-scale merged 3 commits into
mainfrom
sandbox-accepts-default

Conversation

@earakely-scale

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

Copy link
Copy Markdown
Collaborator

A new sandbox provider shaped like most of them (a VM per sandbox) no longer has to declare SANDBOX_ACCEPTS to work.

Why

SANDBOX_ACCEPTS says which images an agent's own sandbox (create_sandbox) runs, and only A2AAgent.accepts reads it. Its default, NAME, was right for one provider here (local) and wrong for the VM providers, which each carried SANDBOX_ACCEPTS = Accepts.LOADABLE to override it. A VM provider that missed the line, as #148's Vercel provider does, had agents refused for images it runs fine: the default local setup's tar.gz images, and images that are only a build context.

What

  • SANDBOX_ACCEPTS defaults to what create_sandbox gives, derived for each provider class in __init_subclass__:
    • those a VM loads when the provider implements create_vm, since its create_sandbox is a bare VM the deploy loads the image into;
    • otherwise its CONTAINER_ACCEPTS, since its create_sandbox is one of its containers.
  • A provider whose create_sandbox differs declares it, and its subclasses keep the declaration. The local provider, which creates VMs but runs an agent as a container by name, declares NAME.
  • Modal VM, E2B and Sail drop their LOADABLE line, and Modal the duplicate half of SANDBOX_ACCEPTS = CONTAINER_ACCEPTS = NAME_OR_CONTEXT.
  • A sandbox that can't run the agent's image is caught as it comes back. SandboxProvider.sandbox_problem(sandbox, image) says why: a VM that can't load it, or a container from a provider whose SANDBOX_ACCEPTS is LOADABLE (it said it gives a VM) and whose CONTAINER_ACCEPTS leaves the image out, naming the SANDBOX_ACCEPTS to declare. A provider that declares the images its agents' containers run is taken at its word, as preflight checked against them. A chain asks it of each provider's sandbox as it tries it, closes one that can't run the image and tries the next (this replaces its VM-only check); the agent's deploy asks a lone provider. So a provider taken to load the image that gives back a container is refused at once with the fix, instead of failing later on a pull or the card's timeout, and in a chain the next provider gets its turn. A container that runs the image, such as one whose provider built it first, goes on to serve it.

Every provider ends up with the value it had: local NAME; Modal NAME_OR_CONTEXT; Modal VM, E2B and Sail LOADABLE; the chain NAME. The Scale layer's providers declare LOADABLE and are unchanged.

For plugins

A plugin provider that implements create_vm but gives an agent a container from create_sandbox, and relied on the old NAME default, now has to declare SANDBOX_ACCEPTS = Accepts.NAME. The plugin API check doesn't see a changed default, so this is the note; if one is missed, the deploy names the fix. #148's Vercel provider needs no declaration after this, and #155's Tensorlake provider can drop its SANDBOX_ACCEPTS line, and its CREATES_VMS, which #159 removed.

Tests

Unit tier: 7,414 passed. New: the derivation for each provider shape (VM, container, a container that builds, declared, kept by a subclass, derived again by one), the local provider's declaration, and the deploy check: a lone undeclared provider's container refused with the declaration to add; containers that pull the image, built it first, or whose provider declared them let through; a chain that closes a container that can't run the image and serves the agent from Modal's, or names each refusal when no provider can; and a chain that falls back from a failing VM provider to Modal.

Live checks

On a Linux amd64 host, with Modal, Modal VM, Sail and the local provider:

  • Built-in providers take and refuse what they did before: an agent that is only a build context deploys on Modal, Modal VM, Sail, local,modal and modal_vm,modal, and the local provider refuses it; a tar.gz agent deploys on Modal VM, Sail, local and modal_vm,local, and Modal refuses it.
  • A chain whose first provider, a VM one, is out of service falls back to Modal or Modal VM.
  • Providers shaped like plugins that declare nothing: Modal's containers plus create_vm deploys a context-only agent; one whose create_sandbox gives a stock container is refused with the declaration to add, alone or twice in a chain, and in a chain with Modal or Modal VM after it the next provider serves the agent.
  • Six concurrent deploys, six cancelled at random moments, and a six-minute randomized soak of 60 deploys across all of the above: 0 unexpected outcomes, and nothing left running on Modal or Sail afterwards.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; the declared-container rejection is fixed.

Summary

VM providers now default to accepting images they can load. Container providers default to CONTAINER_ACCEPTS, and explicit declarations stay inherited.

  • Sandbox providers get image rules from the kind of sandbox they create.
  • Deploys reject sandboxes that cannot run the agent's image.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Check SANDBOX_ACCEPTS] --> B[Create sandbox]
  B --> C{VM?}
  C -->|Yes| D[Check load_problems]
  C -->|No| E{SANDBOX_ACCEPTS is LOADABLE?}
  E -->|No| F[Trust declaration]
  E -->|Yes| G[Check CONTAINER_ACCEPTS]
  D --> H{Image accepted?}
  G --> H
  F --> I[Continue deploy]
  H -->|Yes| I
  H -->|No| J[Close sandbox]
  J --> K[Try next provider or fail]
Loading

Reviews (3) · Last reviewed commit: "fix(sandbox): only a provider that says ..." · Reviewed by Greptile

…ess it declares otherwise

SANDBOX_ACCEPTS, the images an agent's own sandbox runs, now defaults to what a
provider's create_sandbox gives: those a VM loads when it implements create_vm (its
create_sandbox is a bare VM the deploy loads the image into), and its CONTAINER_ACCEPTS
otherwise (its create_sandbox is one of its containers). A provider whose
create_sandbox differs declares it, and its subclasses keep the declaration. The local
provider, which creates VMs but runs an agent as a container by name, declares NAME;
Modal VM, E2B and Sail drop their LOADABLE line, and Modal its duplicate.

Every provider here and in the Scale layer ends up with the value it declared. A plugin
that implements create_vm and relied on the old NAME default for a container
create_sandbox now has to declare it; if it doesn't, the deploy says so as soon as the
container comes back for an image it can't run by name.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@earakely-scale
earakely-scale requested a review from a team as a code owner October 11, 2026 23:14
Comment thread src/agent_env/a2a_agent/a2a_agent.py Outdated
…, and a container is checked against what its provider's containers run

The deploy check looked at every provider in a chain rather than the one whose sandbox came back, so a
chain could pass a container that can't run the image (when another link's containers could) or refuse
a deploy its next provider would have served. It also asked whether the image runs by name, which
refuses a container whose provider built the image first, as Modal's do, and named the wrong fix.

SandboxProvider.sandbox_problem(sandbox, image) now says why a sandbox create_sandbox gave can't run
the image: a VM that can't load it, or a container its CONTAINER_ACCEPTS leaves it out of, naming the
SANDBOX_ACCEPTS to declare when that differs. A chain asks it of each provider's sandbox as it tries
it, closes one that can't and tries the next (replacing its VM-only check); a lone provider is asked
by the agent's deploy. A chain answers None, having checked its own.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment thread src/agent_env/providers/sandbox_providers/sandbox_provider.py
…es like a VM has their containers checked

A provider can declare that create_sandbox's containers run other images than create_container's (say
SANDBOX_ACCEPTS = NAME_OR_CONTEXT, CONTAINER_ACCEPTS = NAME); checking those containers against
CONTAINER_ACCEPTS refused sandboxes that work. sandbox_problem now checks a container only when the
provider's SANDBOX_ACCEPTS is LOADABLE, the case where it said it gives a VM; a provider that declares
the images its containers run is taken at its word, as preflight already checked against them. The two
test fakes changed for the wider check are back as they were.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@earakely-scale
earakely-scale merged commit 2e01f77 into main Oct 12, 2026
13 of 14 checks passed
@earakely-scale
earakely-scale deleted the sandbox-accepts-default branch October 12, 2026 01:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant