Dashboard: where the agents are #419
No reviewers
Labels
No labels
bug
contract
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
iterative-works/iw-cli!419
Loading…
Reference in a new issue
No description provided.
Delete branch "dashboard-control-plane"
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?
Slice 06 — Where the agents are
Goal. Land on the board and tell, for every worktree, whether an agent is
working in it right now, stopped and waiting for me, or not there at
all — and decide what to pick up from that, without checking tmux.
Fit test. Michal returns to the board after a break and chooses what to work
on next from the board alone: nothing he knows to be running is missing, nothing
dead pretends to be alive, and "is anything already moving on this, or do I have
to start it?" is answered correctly.
Measured at definition against the live server: 22 worktrees held an open agent
session and 7 of them appeared nowhere on the board.
What ships
/procin-process (LiveAgentReader),keeping entries whose
commisclaudeand reading each one'scwd. Aprocess counts for a worktree when its cwd is that path or below it, on a
separator — so
-IW-1cannot swallow-IW-10. Forkingpscosts 73 msagainst 17 ms for the walk, so it does not fork.
(
TranscriptReader), taking the freshest.jsonlacross both~/.claudeand~/.claude-iwand reading it backwards in 256 KB chunks —doubling to a 4 MB bound, never whole. The open sessions hold 81 MB between
them; a board refresh cannot read that.
domain/AgentSession.scala). A live processproves presence and nothing more — one had been running 38 days. The file's
own mtime proves nothing either: the records that bump it carry no timestamp.
in its waiting lane, with the agent state and the age on it. The lane "Where
the agents are" keeps only the sessions no act card speaks for.
PendingActis untouched. Liveness is its own channel: a running agentneither creates nor removes an act. This is display, not claim.
Measured on the running board
Three corrections the fit reads forced
stop_reason, not content kinds, says whether a turn ended. One modelresponse is written as several records (thinking → text → tool call), each
carrying the response's
stop_reason. Reading content alone would havecalled 456 of 2532 assistant records "stopped" mid-turn.
conversation. MEDECA-407 read 56 minutes stale while its sub-agents reported
in 1.8 minutes ago.
been quiet.
AskUserQuestionblocks 22 min at the median and up to 7 days;Bash0.2 s at the median but 15 minutes when it polls CI. Duration cannotseparate them; identity can. 38 of 466 long silences were a question misread
as work.
Fit outcome — closed with the remainder carried forward
The fit test did not fully pass, and Michal closed the slice deliberately
rather than amend a frozen card mid-flight. Two sessions still read
workingwhile waiting on him.
The cause is architectural, and it is the next slice's spine: the transcript
is a reliable record of the past and not a contract about the present. Claude
Code does not flush the assistant turn carrying an
AskUserQuestionuntil thehuman answers it — measured on a session whose terminal was displaying the
prompt while its newest dated record was 118 minutes old. The same lesson is
already recorded in
procedures'agent-lifecycle-notify.sh(issue #414):"the transcript file on disk lags behind, so reading it would return the
penultimate message."
Michal's rule: never rely on when a tool writes its transcript — buffering
will be common, and no tool guarantees the file reflects current state.
Close review — 6 skills, 2 rounds
Round 1 (style, testing, security, scala3, composition, architecture) raised
3 criticals; all are fixed and re-verified in round 2.
/took the landing page down. It encoded to anempty transcript-directory name, and asking os-lib for a segment named by
nothing throws rather than misses. The same path matched every agent on the
machine, because a prefix rule rooted at
""contains everything — so itfired as soon as any agent ran. Registration only checks that a path starts
with
/. Verified against the running server:PUTa worktree at/, thenGET /→ 200, where it previously threw out of the handler./procwalk and the two-reader composition had no tests and no way toget one. Both are parameterised now, and the wiring is pinned — which
reader answers "where are the agents" and which answers "what is this one
doing" is the whole of what the service composes, and swapping them would
still have compiled.
just past it doubled once more, reached the file's start and read all of it —
a 5.6 MB transcript was read whole. Found because the round-2 reviewer
mutation-tested my own regression test and proved it vacuous. Both the bound
and the test are fixed; stripping the bound now turns the test red.
Also fixed: the state vocabulary lived in three places (
AgentStateBadgenowowns the words, the order and the shape, and the tally walks the states
themselves);
projectOfwas duplicated verbatim (MainProject.projectName);five methods were public that are steps of
of/tailFor(private[dashboard],matching
DashboardService);Try(...).toOptionwhere ujson has totalaccessors;
@tailrecon the backward read.Two findings deliberately NOT fixed — they need your decision
The dashboard has no auth and binds on a Tailscale IP. The board already
showed worktree keys and acts; this adds live working/stopped state and
last-acted age, refreshed per load — enough to tell when someone is actively
driving an agent versus stepped away, per issue. Cheapest options: a
shared-secret header in a Cask decorator, or render the lane only for
loopback requests. Recorded as an accepted risk unless you say otherwise
— auth was scoped out of this effort, and
dashboard-off-localhostisalready a candidate on the map.
WorktreeRegistration.createacceptsany string starting with
/, so a short prefix like/homestill matchesunrelated agents and would display their state under an attacker-chosen
issue id. Requiring the path to exist and hold a
.gitwould close it.Follow-up checklist (flushed to the effort map)
homes and already classify
stopped/started/resumed/finished;proposed shape is that the hook writes a state file the board reads on
refresh, not a fire-and-forget POST, so nothing is lost while the server is
down. Contradicts this card's "hooks parked, never in place of the
reading" fork — the next card must settle it the other way.
stoppedand 11 have been silent over a day. The fold must reach the actcards, not only the lane.
hx-trigger: every 30s.review-statewhen a phase islaunched — writer-side. MEDECA-407 sat in "Launch the next phase" while
an agent implemented it. The badge is what made it visible.
Carried debts, none touched here:
withLockinServerStateService;CaskServeras a god object; whetherdomain/andapplication/are thetarget layout.