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
Define the dashboard project: repo, layout, stack and delivery slices #228
Given the contract, the agentctl telemetry decisions, discovery and Docker access, and the UI prototype: define the separate dashboard project.
Repo name and layout, FastAPI + HTMX/SSE structure (SSE between dashboard and browser only).
Collection: polling via docker exec per container, interval, concurrency and timeouts, in-memory buffering.
Access to the dashboard itself in v1: it shows issues, token usage and log events even read-only. Bind to localhost, reverse proxy with auth, or other?
Deployment: compose service, how it gets Docker access (per the discovery decision).
Testing strategy against a fake agentctl / fake Docker exec layer returning recorded snapshots, including version skew (unknown fields ignored, missing = unavailable).
First delivery slices, and whether the data layer stays swappable for a later Next.js front end.
Requirements from the resolved ticket "Decide the agentctl telemetry command" (#225):
Poll telemetry only (control state is embedded); default interval 5 s, configurable, floor 2 s; at most one exec in flight per instance.
Classify each exec result:
exit 0 with a JSON snapshot → good snapshot;
stdout is {"ok": false, "error": {"code", "message"}} → use the code:
agent_unreachable → unreachable;
unsupported_operation → unsupported;
snapshot_failed → agent up, snapshot failed;
stdout is not a JSON object (argparse rejects the subcommand on an older image, exit 2) → "telemetry not supported by this agent version";
dashboard-side timeout → unreachable (timeout).
Keep the last good snapshot visible with its age on every failure. A busy recovery retry makes polls time out for its duration; this is expected.
control_observed_at (additive) tells when the control section was built; show the control part as stale when it is older than one poll interval while the rest of the snapshot is fresh.
Test fixtures should cover each of these outcomes (fake exec layer returning recorded stdout/exit pairs).
Decided in a grilling session; the operator accepted the recommended answer on every question. Facts checked on the target host rpi5 (aarch64, Debian 13, Python 3.13, systemd --user available, operator in the docker group, no uv/pipx installed) and in the new repository lbacik/agent-dashboard (public, empty before this ticket).
1. Repository and layout
Repo lbacik/agent-dashboard, MIT license. Tooling mirrors the agent: uv + hatchling, pytest, ruff, GitHub Actions CI on Python 3.11 and 3.13, requires-python >=3.11. Entry point agent-dashboard.
src/agent_dashboard/, one deep module per concern:
An asyncio task started in the FastAPI lifespan; Docker calls via asyncio.create_subprocess_exec (argv, no shell).
Each cycle: docker ps -a --filter label=io.github.lbacik.simple-coding-agent.role=agent --format '{{json .}}' (5 s timeout), drop com.docker.compose.oneoff=True, then telemetry for each running container.
Interval 5 s (configurable, floor 2 s); exec timeout 5 s (configurable); at most one exec in flight per instance; global cap of 4 concurrent execs (configurable) to spare the Raspberry Pi.
Cycles never overlap: the next starts one interval after the previous start, or when the previous finishes, whichever is later.
On timeout the docker client process is killed (agentctl has its own 3 s limit inside the container).
The app requires a single uvicorn worker (state is in memory).
3. In-memory state
Per managed instance (keyed by container name): the last good snapshot with its receive time, the last poll outcome (kind, reason, time), and container id and state.
Fleet-wide: the last discovery error (e.g. docker CLI missing, permission denied), shown as a banner.
No history in v1. A removed container disappears on the next cycle. A dashboard restart starts empty.
4. Access to the dashboard
Binds to 127.0.0.1 only and refuses to start on a non-loopback address in v1. Remote viewing goes through ssh -L 8080:127.0.0.1:8080 rpi5.
Validates the Host header against an allow-list (localhost, 127.0.0.1) to defeat DNS rebinding; no CORS.
LAN binding with auth or an authenticating reverse proxy is deferred to v2, where control commands need authentication and CSRF/Origin protection anyway.
One SSE stream GET /stream. After every cycle the server sends a fleet event carrying the whole re-rendered table body. The whole body is sent because attention ordering reorders rows and ages change every cycle.
The instance drawer is hx-get=/instances/{name} with hx-trigger="sse:fleet", so it refreshes with the table. The open drawer is kept in the URL (?instance=<name>) so it survives reloads.
htmx and its SSE extension are vendored into static files (no CDN). CSS is carried over from the prototype in Prototype the dashboard UI.
7. Swappable data layer
The seam is the view-model layer (dataclasses), independent of FastAPI and Jinja; templates consume only view models.
No public JSON API in v1. A later front end (e.g. Next.js) would add /api/... over the same fleet state.
Argv-pinning test for every argv the gateway can produce.
Collector tests call one cycle directly, without the timer.
Web tests with FastAPI TestClient, asserting HTML fragments. No browser end-to-end tests.
Integration test against real Docker in CI: a minimal image carrying the agent label, whose /usr/local/bin/agentctl is a script printing a fixture.
Snapshot fixtures come from the agent repo: the agent repo owns synthetic example snapshots for schema v1 (docs/telemetry/examples/*.json) and tests that its builder produces that shape. The dashboard copies them into tests/fixtures/contract/v1/, recording the source commit, and derives version-skew fixtures from them: unknown fields, missing fields, schema_version: 2, unknown basis, null sections.
The repo is public: fixtures are synthetic only, never raw captures from live instances.
9. Glossary
agent-dashboard has its own CONTEXT.md with Poll outcome, Reach, Status headline and Attention rank. It links to the agent's glossary for the contract terms rather than copying them. The agent-side terms are in docs: fleet dashboard glossary and ADR 0006.
Initial commit 1cfca31 in agent-dashboard: README (pointers to this map and ticket as the v1 specification), CONTEXT.md, ADR 0001, LICENSE. No code: building is out of this map's scope.
10. Dashboard delivery slices
S1 – Skeleton and discovery.
Repo scaffold and CI; gateway (list_instances, telemetry); collector cycle with outcome classification; SSE.
Fleet table with instance, reach and status headline columns; loopback and Host guards; real-Docker integration test with a stub agentctl.
Works against today's agents (they show as "telemetry not supported by this agent version"), so it proves the exec path on rpi5 early.
S2 – Telemetry table.
Snapshot reader with the version-skew rules; all table columns from the prototype; attention ordering; stale control marking via control_observed_at.
Built against the contract example snapshots.
S3 – Instance drawer.
Current attempt, control, recent events and last finished sections, with basis markers.
S4 – Release v0.1.0.
Tag; uv tool install and systemd --user docs; manual smoke test on rpi5.
S1 can start immediately. S2 and S3 need only the contract examples (agent-side item 1 below), not the working telemetry.
11. Consolidated agent-side changes (destination part (a))
Deduplicated from #224, #225, #226 and this ticket, in the order to split them into implementation issues:
Discovery label and contract examples.
LABEL io.github.lbacik.simple-coding-agent.role="agent" in the Dockerfile.
docs/telemetry/ with synthetic example snapshots for schema v1: idle, attempt in progress, soft threshold crossed with handoff, recovery hold, truncated, and the error replies.
Snapshot contents: identity; last_finished held in memory and seeded from disk; sequence; generated_at; 256 KiB cap with trimming and truncated.
{"op":"telemetry"} plus agentctl telemetry --json, with error codes agent_unreachable/unsupported_operation/snapshot_failed on stdout and a 3 s client timeout; the client maps unparsable or oversized replies to snapshot_failed.
Recent events: a redacted in-memory ring buffer of the last 50 events in JsonEventLogger, excluding limits_checked and tool_call, with text truncated to 300 characters. (Define the telemetry snapshot contract #224)
Activity
lbacik commentedon Oct 14, 2026
Requirements from the resolved ticket "Decide the agentctl telemetry command" (#225):
telemetryonly (control state is embedded); default interval 5 s, configurable, floor 2 s; at most one exec in flight per instance.{"ok": false, "error": {"code", "message"}}→ use thecode:agent_unreachable→ unreachable;unsupported_operation→ unsupported;snapshot_failed→ agent up, snapshot failed;recovery retrymakes polls time out for its duration; this is expected.control_observed_at(additive) tells when thecontrolsection was built; show the control part as stale when it is older than one poll interval while the rest of the snapshot is fresh.lbacik commentedon Oct 14, 2026
Resolution
Decided in a grilling session; the operator accepted the recommended answer on every question. Facts checked on the target host
rpi5(aarch64, Debian 13, Python 3.13,systemd --useravailable, operator in thedockergroup, nouv/pipxinstalled) and in the new repositorylbacik/agent-dashboard(public, empty before this ticket).1. Repository and layout
lbacik/agent-dashboard, MIT license. Tooling mirrors the agent:uv+ hatchling, pytest, ruff, GitHub Actions CI on Python 3.11 and 3.13,requires-python >=3.11. Entry pointagent-dashboard.src/agent_dashboard/, one deep module per concern:gateway.py: the only code that builds Docker argv; exposes exactlylist_instances()andtelemetry(container_id)(from Decide instance discovery and the dashboard's Docker access).collector.py: the poll cycle and classification of each exec into a poll outcome.fleet.py: in-memory fleet state.snapshot.py: tolerant reader of schema v1 into framework-free view models (version-skew rules from Define the telemetry snapshot contract).presentation.py: status headline, attention rank, formatting.web/: FastAPI app, Jinja2 templates, SSE stream, vendored static assets.2. Collection
lifespan; Docker calls viaasyncio.create_subprocess_exec(argv, no shell).docker ps -a --filter label=io.github.lbacik.simple-coding-agent.role=agent --format '{{json .}}'(5 s timeout), dropcom.docker.compose.oneoff=True, thentelemetryfor each running container.dockerclient process is killed (agentctlhas its own 3 s limit inside the container).3. In-memory state
dockerCLI missing, permission denied), shown as a banner.4. Access to the dashboard
127.0.0.1only and refuses to start on a non-loopback address in v1. Remote viewing goes throughssh -L 8080:127.0.0.1:8080 rpi5.Hostheader against an allow-list (localhost,127.0.0.1) to defeat DNS rebinding; no CORS.5. Installation and running
uvon the host, thenuv tool install git+https://github.com/lbacik/agent-dashboard@vX.Y.Z(versions by git tag; no PyPI in v1).systemd --userunit (example unit indocs/, plusloginctl enable-linger).AGENT_DASHBOARD_*environment variables with matching CLI flags; the Docker context is inherited from the environment.6. Browser updates
GET /stream. After every cycle the server sends afleetevent carrying the whole re-rendered table body. The whole body is sent because attention ordering reorders rows and ages change every cycle.hx-get=/instances/{name}withhx-trigger="sse:fleet", so it refreshes with the table. The open drawer is kept in the URL (?instance=<name>) so it survives reloads.7. Swappable data layer
/api/...over the same fleet state.8. Testing
TestClient, asserting HTML fragments. No browser end-to-end tests./usr/local/bin/agentctlis a script printing a fixture.docs/telemetry/examples/*.json) and tests that its builder produces that shape. The dashboard copies them intotests/fixtures/contract/v1/, recording the source commit, and derives version-skew fixtures from them: unknown fields, missing fields,schema_version: 2, unknownbasis, null sections.9. Glossary
agent-dashboardhas its ownCONTEXT.mdwith Poll outcome, Reach, Status headline and Attention rank. It links to the agent's glossary for the contract terms rather than copying them. The agent-side terms are in docs: fleet dashboard glossary and ADR 0006.1cfca31inagent-dashboard: README (pointers to this map and ticket as the v1 specification),CONTEXT.md, ADR 0001, LICENSE. No code: building is out of this map's scope.10. Dashboard delivery slices
list_instances,telemetry); collector cycle with outcome classification; SSE.Hostguards; real-Docker integration test with a stubagentctl.rpi5early.controlmarking viacontrol_observed_at.basismarkers.uv tool installandsystemd --userdocs; manual smoke test onrpi5.S1 can start immediately. S2 and S3 need only the contract examples (agent-side item 1 below), not the working telemetry.
11. Consolidated agent-side changes (destination part (a))
Deduplicated from #224, #225, #226 and this ticket, in the order to split them into implementation issues:
LABEL io.github.lbacik.simple-coding-agent.role="agent"in theDockerfile.docs/telemetry/with synthetic example snapshots for schema v1: idle, attempt in progress, soft threshold crossed with handoff, recovery hold, truncated, and the error replies.issue_titleand the automatic-continuation index at claim.attempt.json. (Define the telemetry snapshot contract #224)telemetrymodule, socket operation and command.begin_attempt,publish_attempt,finish_attempt. Reader side:snapshot.last_finishedheld in memory and seeded from disk;sequence;generated_at; 256 KiB cap with trimming andtruncated.{"op":"telemetry"}plusagentctl telemetry --json, with error codesagent_unreachable/unsupported_operation/snapshot_failedon stdout and a 3 s client timeout; the client maps unparsable or oversized replies tosnapshot_failed.basis; thresholds includingcrossed_at; turns; continuation; cache hit rate; compactions; subagent start/finish tracking.get_context_usage()at session start and after each compaction. (Define the telemetry snapshot contract #224, Decide the agentctl telemetry command: socket operation, snapshot publication, failure isolation #225)controlsection.control_statusvariant with a 1 s lock timeout, falling back to the last built section.control_observed_at. (Define the telemetry snapshot contract #224, Decide the agentctl telemetry command: socket operation, snapshot publication, failure isolation #225)JsonEventLogger, excludinglimits_checkedandtool_call, withtexttruncated to 300 characters. (Define the telemetry snapshot contract #224)Exceptionand answerssnapshot_failed; loop-side publishing never raises; a rate-limitedtelemetry_failedevent. (Decide the agentctl telemetry command: socket operation, snapshot publication, failure isolation #225)Not in the v1 split: the expected-target-repository guard on mutating commands (v2 control, still in the map's fog).
Records
agent-dashboard(above); no ADR in this repo.