Repository navigation
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): |
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; the custom-provider compatibility issue is fixed.Summary
Adds an optional local SmolVM provider for agents and environments.
Diagram
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Container deployment] --> B[create_container] B --> C{Provider implements create_vm?} C -->|No| D[Delegate to custom create_sandbox] C -->|Yes| E[Create VM] E --> F[Pull image and start container] G[SmolVM agent deployment] --> H[Create bare VM] H --> I[Load image and start agent]Reviews (4) · Last reviewed commit: "fix(sandbox): preserve custom container ..." · Reviewed by Greptile