feat(backend): remove smolmachines; firecracker is the Linux default
lint / lint (push) Successful in 1m54s
test / unit (pull_request) Successful in 58s
test / integration (pull_request) Successful in 18s
test / coverage (pull_request) Failing after 1m3s

Delete the smolmachines backend (the whole bot_bottle/backend/smolmachines
package and its tests). It had fatal Linux issues (TSI networking under
sustained use, exec-channel contention, no SIGWINCH) and is superseded by
the Firecracker backend (issue #342).

Backend selection now:
- default is macos-container on macOS, firecracker on KVM-capable Linux
  hosts, and docker as the last resort (was smolmachines).
- firecracker is selected on a KVM host even when the `firecracker`
  binary isn't installed, so start routes through its preflight and
  prints an install pointer (same UX as require_container), instead of
  silently falling back. Split is_host_capable() (Linux + KVM) out of
  is_available() (adds the binary check) to drive this.

Retarget the cross-backend tests (parity, print-parity, prepare,
workspace, freezer, selection) from smolmachines to firecracker rather
than dropping the coverage. Remove docker.util.image_id/save, which only
smolmachines used. Update README/AGENTS/example bottles and stale
comments; historical docs/prds are left as a point-in-time record.

BREAKING: BOT_BOTTLE_BACKEND=smolmachines now errors as unknown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WBMWTEtQdJ4W5UrWuLHCck
This commit is contained in:
2026-07-11 13:24:47 -04:00
parent c276f7b0b1
commit c07ebca867
73 changed files with 305 additions and 6218 deletions
+34 -25
View File
@@ -23,14 +23,14 @@ from bot_bottle.backend import (
class TestGetBottleBackend(unittest.TestCase):
def test_explicit_name_wins_over_env(self):
with patch.dict(os.environ, {"BOT_BOTTLE_BACKEND": "smolmachines"}):
with patch.dict(os.environ, {"BOT_BOTTLE_BACKEND": "firecracker"}):
b = get_bottle_backend("docker")
self.assertEqual("docker", b.name)
def test_env_var_fallback(self):
with patch.dict(os.environ, {"BOT_BOTTLE_BACKEND": "smolmachines"}):
with patch.dict(os.environ, {"BOT_BOTTLE_BACKEND": "firecracker"}):
b = get_bottle_backend()
self.assertEqual("smolmachines", b.name)
self.assertEqual("firecracker", b.name)
def test_default_macos_container_when_available(self):
class _FakeBackend:
@@ -42,12 +42,12 @@ class TestGetBottleBackend(unittest.TestCase):
with patch.dict(os.environ, {}, clear=True), \
patch.object(backend_mod, "_BACKENDS", {
"macos-container": _FakeBackend(),
"smolmachines": _FakeBackend(),
"docker": _FakeBackend(),
}):
b = get_bottle_backend()
self.assertEqual("macos-container", b.name)
def test_default_smolmachines_when_macos_container_unavailable(self):
def test_default_docker_when_no_macos_and_host_not_kvm(self):
class _FakeBackend:
def __init__(self, name: str, available: bool) -> None:
self.name = name
@@ -56,15 +56,19 @@ class TestGetBottleBackend(unittest.TestCase):
def is_available(self) -> bool:
return self._available
# No macOS container and the host can't run firecracker (no
# KVM / not Linux) → docker is the last resort.
with patch.dict(os.environ, {}, clear=True), \
patch.object(backend_mod.FirecrackerBottleBackend,
"is_host_capable", classmethod(lambda cls: False)), \
patch.object(backend_mod, "_BACKENDS", {
"macos-container": _FakeBackend("macos-container", False),
"smolmachines": _FakeBackend("smolmachines", False),
"docker": _FakeBackend("docker", True),
}):
b = get_bottle_backend()
self.assertEqual("smolmachines", b.name)
self.assertEqual("docker", b.name)
def test_default_firecracker_when_macos_unavailable_but_fc_available(self):
def test_default_firecracker_on_kvm_host_even_when_binary_missing(self):
class _FakeBackend:
def __init__(self, name: str, available: bool) -> None:
self.name = name
@@ -73,11 +77,16 @@ class TestGetBottleBackend(unittest.TestCase):
def is_available(self) -> bool:
return self._available
# A KVM-capable Linux host defaults to firecracker even when the
# binary isn't installed (is_available False) — start then prints
# the install pointer instead of falling back to docker.
with patch.dict(os.environ, {}, clear=True), \
patch.object(backend_mod.FirecrackerBottleBackend,
"is_host_capable", classmethod(lambda cls: True)), \
patch.object(backend_mod, "_BACKENDS", {
"macos-container": _FakeBackend("macos-container", False),
"firecracker": _FakeBackend("firecracker", True),
"smolmachines": _FakeBackend("smolmachines", True),
"firecracker": _FakeBackend("firecracker", False),
"docker": _FakeBackend("docker", True),
}):
b = get_bottle_backend()
self.assertEqual("firecracker", b.name)
@@ -91,7 +100,7 @@ class TestGetBottleBackend(unittest.TestCase):
class TestKnownBackendNames(unittest.TestCase):
def test_returns_backends_sorted(self):
self.assertEqual(
("docker", "firecracker", "macos-container", "smolmachines"),
("docker", "firecracker", "macos-container"),
known_backend_names(),
)
@@ -99,8 +108,8 @@ class TestKnownBackendNames(unittest.TestCase):
class TestEnumerateActiveAgents(unittest.TestCase):
"""Combines each backend's `enumerate_active`. Each backend's
implementation has its own tests (`test_docker_enumerate_active`,
`test_smolmachines_*`); this just asserts the aggregator stitches
them together."""
`test_firecracker_backend`); this just asserts the aggregator
stitches them together."""
def test_concatenates_per_backend(self):
a = ActiveAgent(
@@ -108,7 +117,7 @@ class TestEnumerateActiveAgents(unittest.TestCase):
started_at="", services=("egress",),
)
b = ActiveAgent(
backend_name="smolmachines", slug="b-2", agent_name="research",
backend_name="firecracker", slug="b-2", agent_name="research",
started_at="", services=(),
)
@@ -125,7 +134,7 @@ class TestEnumerateActiveAgents(unittest.TestCase):
with patch.object(
backend_mod, "_BACKENDS",
{"docker": _FakeBackend([a]), "smolmachines": _FakeBackend([b])},
{"docker": _FakeBackend([a]), "firecracker": _FakeBackend([b])},
):
self.assertEqual([a, b], enumerate_active_agents())
@@ -139,11 +148,11 @@ class TestEnumerateActiveAgents(unittest.TestCase):
started_at="2026-06-02T11:00:00Z", services=(),
)
missing_metadata = ActiveAgent(
backend_name="smolmachines", slug="missing-metadata",
backend_name="firecracker", slug="missing-metadata",
agent_name="?", started_at="", services=(),
)
tie_a = ActiveAgent(
backend_name="smolmachines", slug="a-slug", agent_name="research",
backend_name="firecracker", slug="a-slug", agent_name="research",
started_at="2026-06-02T11:00:00Z", services=(),
)
@@ -161,7 +170,7 @@ class TestEnumerateActiveAgents(unittest.TestCase):
backend_mod, "_BACKENDS",
{
"docker": _FakeBackend([newer, tie_b]),
"smolmachines": _FakeBackend([missing_metadata, tie_a]),
"firecracker": _FakeBackend([missing_metadata, tie_a]),
},
):
self.assertEqual(
@@ -179,21 +188,21 @@ class TestEnumerateActiveAgents(unittest.TestCase):
with patch.object(
backend_mod, "_BACKENDS",
{"docker": _FakeBackend(), "smolmachines": _FakeBackend()},
{"docker": _FakeBackend(), "firecracker": _FakeBackend()},
):
self.assertEqual([], enumerate_active_agents())
def test_skips_unavailable_backends(self):
# If a backend's runtime isn't installed (smolvm missing on
# a docker-only host, or docker missing on a smolmachines-
# only host), the cross-backend enumerator skips it rather
# than dying — `has_backend` gates the iteration.
# If a backend's runtime isn't installed (docker missing on a
# firecracker host, or KVM missing on a docker-only host), the
# cross-backend enumerator skips it rather than dying —
# `has_backend` gates the iteration.
present = ActiveAgent(
backend_name="docker", slug="a-1", agent_name="impl",
started_at="", services=(),
)
hidden = ActiveAgent(
backend_name="smolmachines", slug="x", agent_name="x",
backend_name="firecracker", slug="x", agent_name="x",
started_at="", services=(),
)
@@ -212,7 +221,7 @@ class TestEnumerateActiveAgents(unittest.TestCase):
backend_mod, "_BACKENDS",
{
"docker": _FakeBackend([present], available=True),
"smolmachines": _FakeBackend([hidden], available=False),
"firecracker": _FakeBackend([hidden], available=False),
},
):
self.assertEqual([present], enumerate_active_agents())