You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Decide instance discovery and the dashboard's Docker access #226
How does the dashboard find agent containers, and how does it get the Docker access it needs to run agentctl safely? Telemetry is read via docker exec ... agentctl telemetry --json, so even the read-only v1 needs exec, not just container listing.
Discovery: container labels and naming, filtering to agent containers only, handling containers that start, stop or are replaced while the dashboard runs.
Exec invocation: docker exec as user agent with the absolute agentctl path, timeouts, and the TARGET_REPO check the operator doc requires before mutating commands (v2), or also before reads.
Blast radius: Docker socket access with exec is effectively root on the host. Options: raw socket, docker-socket-proxy (which can allow exec but not restrict which command runs), a host-side helper limited to agentctl status|telemetry, or running the dashboard as a host process. Which one for v1, and does v2 control change the answer?
v1 vs v2: how the dashboard guarantees it only issues read-only commands in v1, and what changes when control (stop, stop --after, resume, handoff, next issue) arrives.
changed the title [-]Decide instance discovery and the control path from the dashboard[/-][+]Decide instance discovery and the dashboard's Docker access[/+]on Oct 14, 2026
Decided in a grilling session; the operator asked to take the recommended answer on every question.
Discovery
Agent-side change: the image carries LABEL io.github.lbacik.simple-coding-agent.role="agent" (in the Dockerfile, not in compose), so every container built from it is discoverable without operator configuration.
The dashboard lists docker ps -a --filter label=io.github.lbacik.simple-coding-agent.role=agent and drops containers labelled com.docker.compose.oneoff=True (docker compose run containers have no live agent and must never be addressed).
Rediscovery happens on every poll cycle; no docker events stream in v1. Running containers are polled; exited ones are shown as "not running" without any exec; removed containers disappear.
Managed instance identity = container name (stable across docker compose up recreation). The container ID is tracked only to detect replacement; the target repository shown in the UI comes from the snapshot.
Exec invocation
The dashboard shells out to the docker CLI via an argv list (no shell), not the Docker Engine SDK: it honours Docker contexts/DOCKER_HOST (the remote-context fog) and is exactly what an operator would type.
Argv is built from constants: docker exec --user agent <container-id> /usr/local/bin/agentctl telemetry --json. No -t/-i. It targets the container ID resolved in the same cycle, never the name, so a recreated container is never hit by a stale decision.
Dashboard-side timeout 5 s (configurable); at most one in-flight exec per instance (the control server is serial). On failure the instance is shown as unreachable with the reason, and the last good snapshot stays visible with its age.
TARGET_REPO check
Reads need no separate check: the snapshot identifies its own target repository (requirement passed to "Define the telemetry snapshot contract").
v2 mutations must be bound to the container ID and target repository the operator saw; the recommended mechanism is an agent-side guard (agentctl rejects a command whose expected target repository does not match). Mechanism is left in the v2 control fog.
Blast radius
v1 runs as a host process under the operator's own account, using the operator's existing Docker CLI access, with the web UI bound to 127.0.0.1 only. It grants no privilege the operator does not already have.
Rejected for v1: mounting the raw socket into a dashboard container (root-equivalent for a web-facing process); docker-socket-proxy (cannot restrict which command exec runs, and allowing POST /containers/{id}/exec also opens container creation); a separate host-side helper (an extra component that reduces nothing while the dashboard already runs as the operator). Revisit when the dashboard must be reachable remotely.
Polling is timer-driven: HTTP handlers only read the in-memory cache, so in v1 no request path reaches an argv.
Read-only guarantee (v1) and v2
One gateway module is the only code that builds Docker argv. In v1 it exposes exactly list_instances() and telemetry(container_id); there is no generic exec. A test pins every argv the gateway can produce.
v2 keeps the same deployment and adds one named gateway method per control command (stop, stop --after, resume, handoff, next issue), with the dashboard generating --request-id for safe retries; recovery and errors reset are never added. Request-triggered exec in v2 additionally needs CSRF/Origin protection (part of the v2 control fog).
Activity
[-]Decide instance discovery and the control path from the dashboard[/-][+]Decide instance discovery and the dashboard's Docker access[/+]lbacik commentedon Oct 14, 2026
Resolution
Decided in a grilling session; the operator asked to take the recommended answer on every question.
Discovery
LABEL io.github.lbacik.simple-coding-agent.role="agent"(in theDockerfile, not in compose), so every container built from it is discoverable without operator configuration.docker ps -a --filter label=io.github.lbacik.simple-coding-agent.role=agentand drops containers labelledcom.docker.compose.oneoff=True(docker compose runcontainers have no live agent and must never be addressed).docker eventsstream in v1. Running containers are polled; exited ones are shown as "not running" without anyexec; removed containers disappear.docker compose uprecreation). The container ID is tracked only to detect replacement; the target repository shown in the UI comes from the snapshot.Exec invocation
dockerCLI via an argv list (no shell), not the Docker Engine SDK: it honours Docker contexts/DOCKER_HOST(the remote-context fog) and is exactly what an operator would type.docker exec --user agent <container-id> /usr/local/bin/agentctl telemetry --json. No-t/-i. It targets the container ID resolved in the same cycle, never the name, so a recreated container is never hit by a stale decision.TARGET_REPO check
agentctlrejects a command whose expected target repository does not match). Mechanism is left in the v2 control fog.Blast radius
127.0.0.1only. It grants no privilege the operator does not already have.execruns, and allowingPOST /containers/{id}/execalso opens container creation); a separate host-side helper (an extra component that reduces nothing while the dashboard already runs as the operator). Revisit when the dashboard must be reachable remotely.Read-only guarantee (v1) and v2
list_instances()andtelemetry(container_id); there is no generic exec. A test pins every argv the gateway can produce.stop,stop --after,resume,handoff,next issue), with the dashboard generating--request-idfor safe retries;recoveryanderrors resetare never added. Request-triggered exec in v2 additionally needs CSRF/Origin protection (part of the v2 control fog).Agent-side changes surfaced
io.github.lbacik.simple-coding-agent.role="agent".agentctl telemetryfinishes well inside 5 s without a TTY and reports failures machine-readably (-> agentctl telemetry command).Assets
CONTEXT.mdon branchdocs/fleet-dashboard-glossary(commit 457b102).