Skip to content

feat(sandbox): add SmolVM local sandbox provider - #157

Open
BinSquare wants to merge 6 commits into
scaleapi:mainfrom
BinSquare:feat/smol-sandbox-provider
Open

BinSquare wants to merge 6 commits into
scaleapi:mainfrom
BinSquare:feat/smol-sandbox-provider

Conversation

@BinSquare

@BinSquare BinSquare commented Oct 10, 2026 •

Copy link
Copy Markdown

Adds a local SmolVM sandbox provider for isolated AgentEnv agents and environments, including published ports, persistent Docker storage, local registry access, and trusted object transfers; this is the local phase of #156.

Validation: 6,817 AgentEnv unit tests passed locally and the plugin API check passed; live Linux KVM checks covered container networking, published HTTP, HTTPS grants, registry pulls, reconnect, and VM stop/start. On live Linux KVM, the explicitly run checks deployed an A2A agent in SmolVM (1 passed in 26 seconds) and read environment data from its container through a gateway in a second SmolVM machine after restoring the environment record (1 passed in 89 seconds); reproduce with Docker and the SDK reconnect fix using python -m pytest tst/manual/smol_vm_agent_check.py tst/manual/smol_vm_gateway_check.py -q --timeout=1200. A 64 MiB local object transferred into a live VM through a scoped HTTPS grant in 0.26 seconds with matching SHA-256.

Cloud follow-up: cloud VM endpoints require an Authorization header, so cloud gateway access needs a scoped private credential or authenticated proxy before AgentEnv can safely persist and return its gateway URL.

Dependency: The opt-in reconnect behavior from smol-machines/smol#277 is published in smolmachines 1.27.0, the optional minimum and locked version for this PR.

RetriggerView in GreptileConfidence Score: 2/5

Fix agent image startup, local-run registry forwarding, and loopback grant reachability before merging, and satisfy the named-limit requirement.

Fix All in CursorFindings

  1. P1 Saved agent images cannot start ▶
  2. P1 Local runs lose registry access ▶
  3. P1 Loopback grants reach wrong container ▶
  4. P2 Limits have no names ▶
Fix with agent prompt
### Issue 1
src/agent_env/providers/sandbox_providers/smol_vm/provider.py:56-60
`create_sandbox` declares `Accepts.LOADABLE`, but calls `create_container`, which always pulls `image_name` and returns container mode. A context-only agent image is accepted, then its local tag is pulled from Docker Hub instead of being built. Archive-backed images also cannot use their saved archive when the registry is unavailable.

Return a bare VM so agent deployment can call `load_docker_images`, or declare the narrower supported image contract.

### Issue 2
src/agent_env/providers/sandbox_providers/smol_vm/provider.py:129-131
During an `@local` run, `get_image_store()` returns `LocalRunImageStore`. This type check skips registry forwarding even when its `local` store is `LocalRegistryImageStore`. A SmolVM agent pulling a local image then contacts the guest's own `localhost:5000`, where no registry is listening, and deployment fails.

Unwrap the local store before checking its type and registry address.

### Issue 3
src/agent_env/store/object_store/local/store.py:200
`grants_reach("smol_vm")` now returns true when `grant_advertise_host` is `localhost` or `127.0.0.1`, but agent-facing grant URLs keep that host. Inside the agent container, those addresses point to the container itself, not the host's grant server. Object transfers select the grant path and fail instead of using staging.

Exclude these advertised hosts from the reachable set, or rewrite every agent-facing grant URL before sending it.

### Issue 4
src/agent_env/providers/sandbox_providers/smol_vm/sandbox.py:64
The new grant lifetime and Docker startup limits use inline values such as `3600` and `40`. The repository requires magic numbers to be stored as class or instance variables with descriptive names. The five-attempt socket retry in `grant_server.py` has the same pattern.

Extract these limits so their units and purpose are clear and they can be changed in one place. This repository requirement must be satisfied before merging.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Adds a local smol_vm provider with Docker storage, published ports, reconnect, registry forwarding, and trusted local object transfers.

  • Agents and environments can run in persistent local SmolVM machines.
  • SmolVM guests can reach host services through rewritten URLs and a registry proxy.
  • SmolVM guests can fetch local objects through trusted, scoped HTTPS grants.

Acknowledged scope and prerequisites:

  • BinSquare states that cloud gateway authentication is deferred to the cloud follow-up.
  • BinSquare states that the reconnect dependency needs a published SDK minimum before merging.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[AgentEnv host] --> B[SmolVM provider]
  B --> C[Local VM]
  C --> D[Guest Docker]
  D --> E[Agent or gateway containers]
  F[Host registry] --> G[Guest registry proxy]
  G --> D
  H[Host HTTPS grant server] --> C
  H --> E
  E --> I[Published host ports]
Loading

Reviews (1) · Last reviewed commit: "chore(smol): require published smolmachi..." · Reviewed by Greptile

@socket-security

socket-security Bot commented Oct 10, 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/​smolmachines@​1.27.098100100100100

View full report

@earakely-scale

Copy link
Copy Markdown
Collaborator

Hi @BinSquare , appreciate you working on integrating Smol Machines to AgentEnv. Would love to collab on co-marketing for once this is merged. Feel free to reach out to me over X https://x.com/earakely to discuss if you're interested, we can setup a shared slack channel.

@BinSquare

Copy link
Copy Markdown
Author

Hi @BinSquare , appreciate you working on integrating Smol Machines to AgentEnv. Would love to collab on co-marketing for once this is merged. Feel free to reach out to me over X https://x.com/earakely to discuss if you're interested, we can setup a shared slack channel.

That sounds great, would love to.

I'll msg you as I finish up testing

@BinSquare
BinSquare force-pushed the feat/smol-sandbox-provider branch from 2bb090d to abea4cd Compare October 12, 2026 02:06
@BinSquare
BinSquare marked this pull request as ready for review October 12, 2026 03:32
@BinSquare
BinSquare requested a review from a team as a code owner October 12, 2026 03:32
Comment on lines +56 to +60
return await self.create_container(
image_name=image_name, port=port, env=env, cpu=cpu, memory=memory,
disk_size_gb=disk_size_gb, timeout=timeout, attribution=attribution,
network_policy=network_policy,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Saved agent images cannot start

create_sandbox declares Accepts.LOADABLE, but calls create_container, which always pulls image_name and returns container mode. A context-only agent image is accepted, then its local tag is pulled from Docker Hub instead of being built. Archive-backed images also cannot use their saved archive when the registry is unavailable.

Return a bare VM so agent deployment can call load_docker_images, or declare the narrower supported image contract.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agent_env/providers/sandbox_providers/smol_vm/provider.py
Line: 56-60

Comment:
**Saved agent images cannot start**

`create_sandbox` declares `Accepts.LOADABLE`, but calls `create_container`, which always pulls `image_name` and returns container mode. A context-only agent image is accepted, then its local tag is pulled from Docker Hub instead of being built. Archive-backed images also cannot use their saved archive when the registry is unavailable.

Return a bare VM so agent deployment can call `load_docker_images`, or declare the narrower supported image contract.

**Knowledge Base Used:**
- [Provider integrations and sandbox deployment](https://app.greptile.com/scale-ai/-/custom-context/knowledge-base/scaleapi/agentenv-framework/-/docs/provider-integrations.md)
- [Container images and composition](https://app.greptile.com/scale-ai/-/custom-context/knowledge-base/scaleapi/agentenv-framework/-/docs/container-images-and-composition.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Claude Code Fix in Codex

Comment on lines +129 to +131
store = get_config().get_image_store()
if not isinstance(store, LocalRegistryImageStore):
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Local runs lose registry access

During an @local run, get_image_store() returns LocalRunImageStore. This type check skips registry forwarding even when its local store is LocalRegistryImageStore. A SmolVM agent pulling a local image then contacts the guest's own localhost:5000, where no registry is listening, and deployment fails.

Unwrap the local store before checking its type and registry address.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agent_env/providers/sandbox_providers/smol_vm/provider.py
Line: 129-131

Comment:
**Local runs lose registry access**

During an `@local` run, `get_image_store()` returns `LocalRunImageStore`. This type check skips registry forwarding even when its `local` store is `LocalRegistryImageStore`. A SmolVM agent pulling a local image then contacts the guest's own `localhost:5000`, where no registry is listening, and deployment fails.

Unwrap the local store before checking its type and registry address.

**Knowledge Base Used:**
- [Container images and composition](https://app.greptile.com/scale-ai/-/custom-context/knowledge-base/scaleapi/agentenv-framework/-/docs/container-images-and-composition.md)
- [Store backends and querying](https://app.greptile.com/scale-ai/-/custom-context/knowledge-base/scaleapi/agentenv-framework/-/docs/store-backends-and-querying.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Claude Code Fix in Codex

return sandbox_type == _LOCAL_SANDBOX_TYPE or (
sandbox_type == "smol_vm"
and self._grant_bind_host in (None, "127.0.0.1")
and self._grant_advertise_host in (None, "localhost", "127.0.0.1", "host.docker.internal", "host.smolvm.internal")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Loopback grants reach wrong container

grants_reach("smol_vm") now returns true when grant_advertise_host is localhost or 127.0.0.1, but agent-facing grant URLs keep that host. Inside the agent container, those addresses point to the container itself, not the host's grant server. Object transfers select the grant path and fail instead of using staging.

Exclude these advertised hosts from the reachable set, or rewrite every agent-facing grant URL before sending it.

Knowledge Base Used: Store backends and querying

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agent_env/store/object_store/local/store.py
Line: 200

Comment:
**Loopback grants reach wrong container**

`grants_reach("smol_vm")` now returns true when `grant_advertise_host` is `localhost` or `127.0.0.1`, but agent-facing grant URLs keep that host. Inside the agent container, those addresses point to the container itself, not the host's grant server. Object transfers select the grant path and fail instead of using staging.

Exclude these advertised hosts from the reachable set, or rewrite every agent-facing grant URL before sending it.

**Knowledge Base Used:** [Store backends and querying](https://app.greptile.com/scale-ai/-/custom-context/knowledge-base/scaleapi/agentenv-framework/-/docs/store-backends-and-querying.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Claude Code Fix in Codex

signed = await asyncio.to_thread(store.signed_get_url, object_url)
local_grant = isinstance(store, LocalFilesystemObjectStore) and store.supports_transfer_grants and store.grants_reach(self.type)
if signed is None and local_grant:
signed = (await asyncio.to_thread(store.issue_read_grant, object_url, expires_in=3600)).url

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Limits have no names

The new grant lifetime and Docker startup limits use inline values such as 3600 and 40. The repository requires magic numbers to be stored as class or instance variables with descriptive names. The five-attempt socket retry in grant_server.py has the same pattern.

Extract these limits so their units and purpose are clear and they can be changed in one place. This repository requirement must be satisfied before merging.

Rule Used: Store magic numbers as class or instance variables with descriptive names rather than using them inline in the code. (source)

Learned From
scaleapi/scaleapi#126388

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agent_env/providers/sandbox_providers/smol_vm/sandbox.py
Line: 64

Comment:
**Limits have no names**

The new grant lifetime and Docker startup limits use inline values such as `3600` and `40`. The repository requires magic numbers to be stored as class or instance variables with descriptive names. The five-attempt socket retry in `grant_server.py` has the same pattern.

Extract these limits so their units and purpose are clear and they can be changed in one place. This repository requirement must be satisfied before merging.

**Rule Used:** Store magic numbers as class or instance variables with descriptive names rather than using them inline in the code. ([source](https://app.greptile.com/scale-ai/-/custom-context?memory=002e0051-41ad-46c1-9098-47433c580150))

**Learned From**
[scaleapi/scaleapi#126388](https://github.com/scaleapi/scaleapi/pull/126388)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Cursor Fix in Claude Code Fix in Codex

This branch has not been deployed

No deployments
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.

2 participants