feat(cli): cross-backend list active + --backend flag + dashboard picker (issue #77) #78
Reference in New Issue
Block a user
Delete Branch "cli-backend-aware-list-and-flag"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Addresses #77. The CLI and dashboard now share one cross-backend abstraction for listing + launching bottles — adding a backend lights up in both places without separate wiring.
Backend abstraction
ActiveBottledataclass (backend_name,slug,agent_name,started_at,services) replaces the docker-specificActiveAgent. Same field surface for existing dashboard consumers;ActiveAgentbecomes a typed alias for source-compat.BottleBackend.enumerate_active() -> Sequence[ActiveBottle]replaces the oldlist_active() -> None. Docker uses its existing compose query; smolmachines usessmolvm machine ls --jsoncross-referenced with each bundle container'sCLAUDE_BOTTLE_SIDECAR_DAEMONSenv (backend/smolmachines/enumerate.py).enumerate_active_bottles()+known_backend_names()helpers.get_bottle_backend(name=None)takes an explicit name (precedence: arg > env > "docker").CLI
./cli.py list activenow enumerates every backend → tab-separated<backend>\t<slug>\t<agent>\t<services>. Smolmachines bottles show up../cli.py start --backend=<docker|smolmachines>(choices pulled live fromknown_backend_names()).Dashboard
[docker]/[smolmachines].discover_active_agents()collapses toenumerate_active_bottles(); the parser/query helpers move intobackend/docker/cleanup.pywhere the enumerator that owns them now lives.Drift concern from the issue
enumerate_active_bottles().prepare_with_preflight()+attach_claude()(shared by CLI and dashboard; the dashboard now passes its modal-pickedbackend_name).--backendflag and the modal both feed into the sameget_bottle_backend(name)resolver, so changing precedence changes both behaviors at once.Tests
test_docker_enumerate_active.py(wastest_dashboard_active_agents.py).test_backend_selection.py: coversget_bottle_backend,known_backend_names,enumerate_active_bottles.test_cli_start_backend_flag.py: argparse shape + explicit-over-env precedence.Not in this PR
The dashboard's re-attach + capability-block flows still synthesize a
DockerBottledirectly from the slug. Re-attaching a smolmachines bottle from the dashboard would require a backend-side "reconstitute from slug" path that doesn't exist yet — out of scope; tracked separately if it becomes blocking.Closes #77.
@@ -178,0 +175,4 @@nothing's running."""# docker on PATH? Defensive — `list active` shouldn't die# just because the docker backend isn't usable on this host.if shutil.which("docker") is None:create a "has_backend('docker')" function instead which sits outside this so we can determine whether a backend exists on a system. Use that instead of this one off
@@ -175,3 +170,1 @@ps = subprocess.run(["docker", "compose", "-p", project, "ps", "--format","{{.Service}}\t{{.Name}}\t{{.Status}}"],def enumerate_active() -> list[ActiveBottle]:should probably go into an "enumerate" file to mirror the smolmachines backend structure
@@ -0,0 +70,4 @@Smolmachines bundles all run the PRD-0024 image with thesame daemon set declared via env, so one inspect per bundlegets us the picture without exec'ing into the container."""if shutil.which("docker") is None:same note RE one off/detecting backend availability
@@ -109,0 +103,4 @@# Field surface stays compatible (slug / agent_name / started_at# / services) plus a new `backend_name` so dashboard rows can# show which backend a bottle came from.ActiveAgent = ActiveBottleDon't bother with backwards compatibility/use the updated one.
But it should be "Agent"... the thing running (bottle+agent) is always referred to as an "agent", not a "bottle".
Review feedback addressed in
3b41858.@@ -166,5 +166,5 @@except OSError as e:warn(f"failed to remove {path}: {e}")def enumerate_active() -> list[ActiveBottle]:Done in
3b41858— movedenumerate_active+ parser + query helpers into a newbackend/docker/enumerate.py, mirroringbackend/smolmachines/enumerate.py.cleanup.pyis now purely prepare/cleanup.@@ -174,5 +174,5 @@consume this. Empty list when docker is unreachable ornothing's running."""# docker on PATH? Defensive — `list active` shouldn't die# just because the docker backend isn't usable on this host.if shutil.which("docker") is None:Done in
3b41858— added abstractBottleBackend.is_available()(docker →shutil.which('docker'), smolmachines →smolvm.is_available()) and a top-levelhas_backend(name)on the backend package. Cross-backendenumerate_active_agents()gates on it, so neither inlineshutil.whichcheck exists anymore.@@ -0,0 +70,4 @@def _query_bundle_services() -> dict[str, tuple[str, ...]]:"""`{slug: ('egress', 'pipelock', ...)}` from each runningbundle container's `CLAUDE_BOTTLE_SIDECAR_DAEMONS` env var.Done in
3b41858— samehas_backend('docker')gate replaces the inlineshutil.which. (Late-imported inside_query_bundle_services()to sidestep the cycle, sincebackend/__init__.pyimports this module transitively.)@@ -169,0 +103,4 @@backend is reachable. Backed by the shared`enumerate_active_agents` helper so the CLI's`./cli.py list active` and this dashboard show the same data."""return enumerate_active_agents()Done in
3b41858—ActiveBottlerenamed toActiveAgenteverywhere (backend package, both backends, CLI, dashboard, tests). TheActiveAgent = ActiveBottlealias is gone;enumerate_active_bottlesis nowenumerate_active_agents.@@ -184,1 +164,3 @@print()# `enumerate_active` moved to `backend/docker/enumerate.py` to# mirror the smolmachines layout. Cleanup keeps the orphan# enumeration; enumeration of live agents is its own concern.remove this comment
@@ -13,3 +3,1 @@The actual `docker ps` invocation is exercised by manual probingduring development and the (real-docker) integration tests; herewe lock down the shape contract so a regression surfaces in unit CI.The active-bottle enumeration tests moved tono need to leave this comment
@@ -0,0 +174,4 @@)# `noop when docker missing` lives at the cross-backend gate# now (`enumerate_active_agents` skips backends whosealso remove this
Stale pointer comments removed in
5d740a6.@@ -162,5 +162,5 @@# `enumerate_active` moved to `backend/docker/enumerate.py` to# mirror the smolmachines layout. Cleanup keeps the orphan# enumeration; enumeration of live agents is its own concern.Removed in
5d740a6.@@ -16,3 +1,3 @@""""""Unit: dashboard's row-formatting + selection helpers (PRD 0019)."""from __future__ import annotationsRemoved in
5d740a6.@@ -0,0 +174,4 @@)if __name__ == "__main__":Removed in
5d740a6.