Repository navigation
feat(sandbox): add SmolVM local sandbox provider - #157
Conversation
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
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 |
2bb090d to
abea4cd
Compare
|
Thanks for the provider, and for working through the Greptile rounds. I ran this branch merged onto current 1. The sandbox declares the wrong platform on Apple Silicon. Since #159 every sandbox carries the There are no binfmt entries, and Alpine 3.19 has no
Either way the README's Apple Silicon sentence should describe what was actually run; the validation in the description is Linux KVM only. 2. The TTL is dropped. 3. One new unit test is Linux-only. The unit tier runs on developers' Macs, so mark it Once you push, the fork CI needs a maintainer to approve the run; ping here and we'll do it. |
| def host_url_for(url: str, sandbox_type: Optional[str]) -> str: | ||
| """``url``, which this machine reaches, as a container on ``sandbox_type`` reaches it: only a local | ||
| sandbox's containers run on this machine, and they reach its loopback by another name.""" | ||
| if sandbox_type == "smol_vm": |
There was a problem hiding this comment.
This is the one place the PR teaches core about a particular provider by name, and the same pattern repeats in grants_reach("smol_vm"), the grant server's SAN list and LOCAL_NAMES. The rule in this repo (AGENTS.md; provider_capabilities_test.py enforces part of it) is that code outside a provider reads what the provider declares, never which provider it has. There is a route to the same behaviour with none of those four edits, and it is simpler than what I first wrote here.
Correction to the earlier version of this comment. I claimed the agent container could only resolve a name SmolVM's DNS serves. That was wrong: A2AAgent._run_container passes --add-host for every entry in the sandbox's extra_hosts, and the gateway compose takes sandbox.extra_hosts too, so with your extra_hosts = ("host.docker.internal:<gateway>",) the agent container and the gateway's services already resolve host.docker.internal to the right place. Confirmed live on a SmolVM: an agent-like container started with that --add-host fetched a grant URL as issued (https://host.docker.internal:…) with the local CA. The only thing in a SmolVM that cannot resolve host.docker.internal is the VM itself, which is why your VM-level downloads rewrite to host.smolvm.internal.
So the simplest fix is to give the VM the alias everything else already uses:
- In
_configure_host_access, where you already resolve the gateway, add<gateway> host.docker.internalto the guest's/etc/hosts. Then_download_object_to_vmandwrite_file_from_urlcan curl grant URLs as issued: no rewrite, no new certificate name. _host_urlbecomesLocalSandboxProvider.get_external_url(loopback →host.docker.internal), which already works in your containers viaextra_hostsand, after the line above, in the VM.LOCAL_NAMES, the grant server's SAN list and the alias list ingrants_reachrevert;host.smolvm.internalis then used only insidesmol_vm/, to find the gateway and as the registry proxy's target. That also avoids a side effect of the current change:_loadre-mints the CA whenever its stored name constraints differ from the code's, so touchingLOCAL_NAMESrotates every developer's local CA on upgrade.- What remains is "which providers' containers reach this machine as
host.docker.internal", and that is exactlyON_THIS_MACHINE. Here,host_url_forapplies the loopback rewrite when_sandbox_provider_class(sandbox_type).ON_THIS_MACHINE(returning the URL unchanged when the class can't be loaded, which is today's behaviour), andLocalFilesystemObjectStore.grants_reachreads the same flag plus your check that the advertised host is not a loopback literal. Both type-string cases go away, and one sentence onON_THIS_MACHINErecords the convention.
The dual bind in the grant server stays as it is; only its comment names SmolVM. If you'd rather not carry the host_url_for / grants_reach generalisation in this PR, say so and we'll land it first and you rebase onto it.
|
#178 is merged ( Drop, because core does it now
Add, all inside
smol_vm on main after #178--- a/src/agent_env/providers/sandbox_providers/smol_vm/provider.py
+++ b/src/agent_env/providers/sandbox_providers/smol_vm/provider.py
@@ -18,11 +18,9 @@
from agent_env.store.image_store.local_registry_image_store import LocalRegistryImageStore
from agent_env.store.routing import LocalRunImageStore
from agent_env.providers.sandbox_providers.sandbox import NetworkPolicy
-from agent_env.providers.sandbox_providers.local_sandbox import LOCAL_TRUST_ENV, local_grant_trust, start_trusting
-from agent_env.providers.sandbox_providers.sandbox_provider import (
- Accepts, SandboxProvider, refuse_unenforceable_policy,
-)
-from agent_env.providers.sandbox_providers.smol_vm.sandbox import SmolVmSandbox, _host_url
+from agent_env.providers.sandbox_providers.local_sandbox import LOCAL_TRUST_ENV, LocalSandboxProvider, local_grant_trust, start_trusting
+from agent_env.providers.sandbox_providers.sandbox_provider import SandboxProvider, refuse_unenforceable_policy
+from agent_env.providers.sandbox_providers.smol_vm.sandbox import SmolVmSandbox
_DUMMY_PORT = 60832
@@ -37,9 +35,7 @@
_DOCKER_READY_ATTEMPTS: ClassVar[int] = 40
_LOCAL_REGISTRY_DEFAULT_PORT: ClassVar[int] = 5000
ON_THIS_MACHINE: ClassVar[bool] = True
- CREATES_VMS: ClassVar[bool] = True
- SANDBOX_ACCEPTS: ClassVar[Accepts] = Accepts.LOADABLE
- CONTAINER_ACCEPTS: ClassVar[Accepts] = Accepts.NAME
+ REACHES_HOST_AT: ClassVar[str | None] = "127.0.0.1" # the guest's gateway forwards this machine's names to its loopback
@classmethod
def shares_network_with(cls, sandbox_type: str | None) -> bool:
@@ -47,10 +43,10 @@
@classmethod
def get_external_url(cls, url: str) -> str:
- return _host_url(url)
+ return LocalSandboxProvider.get_external_url(url)
def url_from_sandbox(self, url: str) -> str:
- return _host_url(url)
+ return LocalSandboxProvider.get_external_url(url)
async def create_sandbox(self, *, image_name: str, port: int, env: dict[str, str],
cpu: float = 1.0, memory: int = 8192, disk_size_gb: float = 10,
@@ -161,6 +157,10 @@
raise RuntimeError(f"Cannot resolve SmolVM host gateway: {stderr[-500:]}")
gateway = str(ipaddress.IPv4Address(stdout.split()[0]))
sandbox.extra_hosts = (f"host.docker.internal:{gateway}",)
+ # The VM itself resolves this machine by the name its containers (extra_hosts) and the framework use.
+ await sandbox.exec_script(
+ f"grep -q ' host.docker.internal$' /etc/hosts || echo '{gateway} host.docker.internal' >> /etc/hosts"
+ )
def _container_args(self, sandbox: SmolVmSandbox, *, image_name: str, port: int, env: dict[str, str]) -> str:
args = super()._container_args(sandbox, image_name=image_name, port=port, env=env)
--- a/src/agent_env/providers/sandbox_providers/smol_vm/sandbox.py
+++ b/src/agent_env/providers/sandbox_providers/smol_vm/sandbox.py
@@ -11,22 +11,17 @@
from smol import ConnectOptions, Machine
from agent_env.config import get_config
+from agent_env.providers.sandbox_providers.local_sandbox import LocalSandboxProvider
from agent_env.providers.sandbox_providers.sandbox import CURL_RETRY_FLAGS, VmSandbox
from agent_env.store.object_store.local.store import LocalFilesystemObjectStore
from agent_env.store.object_store.local.tls import local_ca
from agent_env.providers.sandbox_providers.sandbox_provider import SANDBOX_MODE_VM
_PORTS_FILE = "/storage/agentenv-ports.json"
-_HOST_ALIAS = "host.smolvm.internal"
def _host_url(url: str) -> str:
- parts = urlsplit(url)
- if parts.hostname not in ("localhost", "127.0.0.1", "::1", "0.0.0.0", "::", "host.docker.internal"):
- return url
- userinfo, at, _ = parts.netloc.rpartition("@")
- port = f":{parts.port}" if parts.port is not None else ""
- return urlunsplit(parts._replace(netloc=f"{userinfo}{at}{_HOST_ALIAS}{port}"))
+ return LocalSandboxProvider.get_external_url(url)
class SmolVmSandbox(VmSandbox): |
b67f728 to
5ef72d9
Compare
b26ea7f to
43e4ff8
Compare
|
Rebased this branch onto The only conflict was On the rebased branch: |
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.


Confidence Score: 5/5
The PR appears safe to merge; no new blocking issue was established.Summary
Adds an optional
smol_vmprovider that runs Docker inside local microVMs.No new actionable issues were established. The supplied previous threads have no comment numbers, so none require numbered classifications. BinSquare manually resolved “Limits have no names” and “Custom container providers stop working” without explanatory replies; the current code names the reported limits and restores the custom-provider path.
This review inspected code and tests but did not run them.
Diagram
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Agent deployment] --> B[Create bare SmolVM] C[Container task] --> D[create_container] D --> B B --> E[Prepare guest Docker and host access] E --> F[Load or pull images] F --> G[Start workload] G --> H[Return published URLs] I[Reconnect by saved ID] --> J[Restore ports and guest setup] J --> GReviews (7) · Last reviewed commit: "fix(smol): keep dev portable and reclaim..." · Reviewed by Greptile