Slice 07: the agent says it waits — the board reads herdr and refreshes itself #447

Merged
mph merged 18 commits from dashboard-control-plane into main 2026-08-26 06:18:47 +00:00
Owner

Slice 07 — agents-report-themselves

Effort-local batch on dashboard-control-plane. Slices 01–06 shipped in earlier
PRs; this branch carries slice 07 only.

Goal (the use): Michal looks at the board and sees which agents are waiting on
him right now — including one sitting on a question — and goes and answers it,
without checking the terminal.

Fit test (close gate): several sessions open, one displaying an
AskUserQuestion prompt in its terminal. He reads the board, not the terminal: the
worktree holding the question names itself as waiting on him within seconds, the
question is really there, nothing he knows to be waiting is missing, and nothing
claims an agent where none runs.

Fit passed 2026-08-25 on his own read. Served from this branch's build against a
copy of the live state: herdr reported pane wX:p1 in
medeca-modul-poptavky-MEDECA-378 as blocked, the board named MEDECA-378
waiting on you on its act card, and the state cleared to working by itself once
he answered — without a reload. His words: "Yes, it works."

What this ships

The state comes from herdr, which reads the rendered screen. The card was
amended (approved, 2026-08-21) to replace a mechanism of our own. No Claude Code
hook reports that a question is still outstanding — PreToolUse most likely fires
when AskUserQuestion is called, but "still waiting" would have to be derived as
PreToolUse with no PostToolUse, which latches wrong the moment an event is
missed. herdr's live_blocked_form rule read a live AskUserQuestion as blocked
within one 2-second poll, held it for the 69 s it waited, and cleared it on the
answer — while the transcript for that turn was not on disk at all.

  • herdr agent list decoded into the adapter. agent_status is carried as
    herdr's own word, undecoded: its detection manifest is versioned and updated
    remotely, so a word we have never seen must reach the reader as itself rather than
    fail the list or be flattened into a guess. Only pane_id, agent_status and
    focused are required of an entry — the schema declares cwd,
    terminal_title_stripped and agent_session optional and nullable, and the
    contract suite pins that it still says so.
  • The worktree's state resolved from herdr, with the previous reading as the
    fallback.
    A pane is presence and present tense at once. One worktree reads once
    however many panes it holds, and the reading that wants him outranks the rest.
    Where no pane covers a worktree, the /proc walk and the transcript answer as
    before — and that fallback still reads a question as stopped, never as waiting on you, because the record proves the question was asked, never that it is still
    outstanding.
  • Gated on the backend the daemon drives, so under tmux the reading does not run.
    A herdr that is absent, misconfigured, down or slow answers no agents rather than
    an error, and agent list is capped at 2 s against a measured 0.07 s over 39
    agents, so the board can never wait on it.
  • "waiting on you" and "finished" on the board, in their own words and colour, on
    the act cards and in the lane, counted in the tally. A worktree waiting on him
    leads the lane however recently another acted — a question sitting wherever its age
    puts it among thirty sessions does not name itself.
  • The board re-reads itself every 5 seconds. Reload-only made every open tab a
    claim that went on being made after it stopped being true, against the card's own
    rule that a state is held only while something proves it now. It also answers
    refresh from:body — the event /static/dashboard.js has always fired when a tab
    becomes visible, which nothing listened for, so returning to a tab showed the
    moment it was left.

Review

Six single-skill reviewers (style, testing, security, scala3, composition,
architecture). Full report:
project-management/efforts/dashboard-control-plane/slices/07-agents-report-themselves/review-slice-2026-08-25.md

severity found fixed in-loop deferred
Critical 0 — —
Warnings 5 3 2
Suggestions 6 4 2 declined with reasons

Fixed in-loop, including one defect no reviewer found: the lane contradicted its
own card
— it printed "Nothing here is waiting on you" directly beneath a header
counting 1 waiting on you, because a worktree holding a question but claiming no
act lands in that lane. Also: one pane's drifted agent_session object cost every
agent's reading (the array fails on the first entry that will not decode);
workspaceFor now derives from backendFor instead of resolving beside it; the 2 s
cap is pinned as a unit invariant and measured live in the contract tier; loudest's
Finished rank is covered; three temporal "slice 06" references removed.

Follow-ups (deferred, carried to the effort map)

  • Translate HerdrAgent at the edge rather than into the domain.
    AgentSession.of takes an adapter reply DTO and uses two of its six fields — the
    first herdr reply type to reach dashboard/domain undigested. Not a layering
    violation and non-blocking; a refactoring, not a close-time edit.
  • Throttle the herdr reading on the unauthenticated / route. / now
    self-polls and spawns a subprocess per render. Work-per-unauthenticated-request
    is not new — / already reconciled with disk and read every registered worktree's
    review-state — but this adds to it. Belongs with the effort's existing
    the-board-says-who-may-look card.
  • the-quiet-ones-fold-away is now partly done. Its refresh half shipped
    here (whole board, 5 s, not the lane at 30 s); the fold itself remains.

Gate

./iw ./test                                                        exit 0, 0 failures
./mill dashboard.itest.testForked                                  240/240 SUCCESS
IW_CONTRACT_HERDR=1 bats test/contract/herdr_adapter_contract.bats 22 ok, 0 not ok

Lessons triaged with Michal before this PR: 2 entries, both APPROVED → fixed-at-source, recorded locally.

## Slice 07 — `agents-report-themselves` Effort-local batch on `dashboard-control-plane`. Slices 01–06 shipped in earlier PRs; this branch carries **slice 07 only**. **Goal (the use):** Michal looks at the board and sees which agents are waiting on him right now — including one sitting on a question — and goes and answers it, without checking the terminal. **Fit test (close gate):** several sessions open, one displaying an `AskUserQuestion` prompt in its terminal. He reads the board, not the terminal: the worktree holding the question names itself as waiting on him within seconds, the question is really there, nothing he knows to be waiting is missing, and nothing claims an agent where none runs. **Fit passed 2026-08-25 on his own read.** Served from this branch's build against a copy of the live state: herdr reported pane `wX:p1` in `medeca-modul-poptavky-MEDECA-378` as `blocked`, the board named MEDECA-378 **waiting on you** on its act card, and the state cleared to `working` by itself once he answered — without a reload. His words: *"Yes, it works."* ## What this ships **The state comes from herdr, which reads the rendered screen.** The card was amended (approved, 2026-08-21) to replace a mechanism of our own. No Claude Code hook reports that a question is still *outstanding* — `PreToolUse` most likely fires when `AskUserQuestion` is called, but "still waiting" would have to be derived as `PreToolUse` with no `PostToolUse`, which latches wrong the moment an event is missed. herdr's `live_blocked_form` rule read a live `AskUserQuestion` as `blocked` within one 2-second poll, held it for the 69 s it waited, and cleared it on the answer — while the transcript for that turn was not on disk at all. - **`herdr agent list` decoded into the adapter.** `agent_status` is carried as herdr's own word, undecoded: its detection manifest is versioned and updated remotely, so a word we have never seen must reach the reader as itself rather than fail the list or be flattened into a guess. Only `pane_id`, `agent_status` and `focused` are required of an entry — the schema declares `cwd`, `terminal_title_stripped` and `agent_session` optional **and** nullable, and the contract suite pins that it still says so. - **The worktree's state resolved from herdr, with the previous reading as the fallback.** A pane is presence and present tense at once. One worktree reads once however many panes it holds, and the reading that wants him outranks the rest. Where no pane covers a worktree, the `/proc` walk and the transcript answer as before — and that fallback still reads a question as `stopped`, never as `waiting on you`, because the record proves the question was *asked*, never that it is still outstanding. - **Gated on the backend the daemon drives**, so under tmux the reading does not run. A herdr that is absent, misconfigured, down or slow answers no agents rather than an error, and `agent list` is capped at 2 s against a measured 0.07 s over 39 agents, so the board can never wait on it. - **"waiting on you" and "finished" on the board**, in their own words and colour, on the act cards and in the lane, counted in the tally. A worktree waiting on him leads the lane however recently another acted — a question sitting wherever its age puts it among thirty sessions does not name itself. - **The board re-reads itself every 5 seconds.** Reload-only made every open tab a claim that went on being made after it stopped being true, against the card's own rule that a state is held only while something proves it now. It also answers `refresh from:body` — the event `/static/dashboard.js` has always fired when a tab becomes visible, which nothing listened for, so returning to a tab showed the moment it was left. ## Review Six single-skill reviewers (style, testing, security, scala3, composition, architecture). Full report: `project-management/efforts/dashboard-control-plane/slices/07-agents-report-themselves/review-slice-2026-08-25.md` | severity | found | fixed in-loop | deferred | |---|---|---|---| | Critical | 0 | — | — | | Warnings | 5 | 3 | 2 | | Suggestions | 6 | 4 | 2 declined with reasons | Fixed in-loop, including one defect no reviewer found: **the lane contradicted its own card** — it printed *"Nothing here is waiting on you"* directly beneath a header counting `1 waiting on you`, because a worktree holding a question but claiming no act lands in that lane. Also: one pane's drifted `agent_session` object cost every agent's reading (the array fails on the first entry that will not decode); `workspaceFor` now derives from `backendFor` instead of resolving beside it; the 2 s cap is pinned as a unit invariant and measured live in the contract tier; `loudest`'s `Finished` rank is covered; three temporal "slice 06" references removed. ## Follow-ups (deferred, carried to the effort map) - [ ] **Translate `HerdrAgent` at the edge** rather than into the domain. `AgentSession.of` takes an adapter reply DTO and uses two of its six fields — the first herdr reply type to reach `dashboard/domain` undigested. Not a layering violation and non-blocking; a refactoring, not a close-time edit. - [ ] **Throttle the herdr reading on the unauthenticated `/` route.** `/` now self-polls *and* spawns a subprocess per render. Work-per-unauthenticated-request is not new — `/` already reconciled with disk and read every registered worktree's review-state — but this adds to it. Belongs with the effort's existing `the-board-says-who-may-look` card. - [ ] **`the-quiet-ones-fold-away` is now partly done.** Its refresh half shipped here (whole board, 5 s, not the lane at 30 s); the fold itself remains. ## Gate ``` ./iw ./test exit 0, 0 failures ./mill dashboard.itest.testForked 240/240 SUCCESS IW_CONTRACT_HERDR=1 bats test/contract/herdr_adapter_contract.bats 22 ok, 0 not ok ``` Lessons triaged with Michal before this PR: 2 entries, both `APPROVED → fixed-at-source`, recorded locally.
mph added 16 commits 2026-08-25 16:11:41 +00:00
The board's present-tense reading comes from herdr, which reads the rendered
screen: `blocked` says an agent is waiting on a person now, which no transcript
and no hook can say. This is the source, decoded into iw's own values.

`agent_status` is carried as herdr's own word rather than parsed into a type.
The manifest that produces it is versioned and updated remotely, so a word we
have never seen must reach the reader as itself.

Only pane_id, agent_status and focused are required of an entry. The schema
declares cwd, terminal_title_stripped and agent_session optional and nullable,
so an entry that names no directory keys to no worktree instead of costing the
reader the whole list. The contract suite pins that the schema still says so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018q6wZTiLDfNNND1Dnv7UML
herdr's pane list is presence and present tense at once: it reads the rendered
screen, so `blocked` says a question is outstanding now. Where no pane covers a
registered worktree, the /proc walk and the transcript answer as before. One
worktree still reads once however many panes it holds, and the reading that
wants him outranks the rest.

`AgentState` gains WaitingOnYou and Finished, the two of herdr's four words the
board could not say. The transcript fallback still reads a question as Stopped
on purpose: the record proves the question was asked, never that it is still
outstanding.

The reading is gated on the backend the daemon drives, so under tmux it does
not run. A herdr that is absent, misconfigured, down or slow answers no agents
rather than an error, and `agent list` is capped at two seconds against a
measured 0.07 s so the board can never wait on it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018q6wZTiLDfNNND1Dnv7UML
The state slice 06 could not see now has a name on the act cards and in the
lane, distinct from working, idle and finished, and the lane counts it. The
words and the colour live in AgentStateBadge, so a worktree cannot read one way
on an act card and another in the lane.

A worktree waiting on him also leads the lane, however recently another one
acted. The board is read to find what wants him, and a question sitting
wherever its age puts it among thirty open sessions does not name itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018q6wZTiLDfNNND1Dnv7UML
A board that claims what waits on him right now is worth nothing the moment it
is stale, and reload-only made every open tab a claim that went on being made
after it stopped being true — against the card's own rule that a state is held
only while something proves it now.

The waiting board re-fetches itself every five seconds and swaps its container.
herdr settles a state in about two seconds and the whole board renders in about
0.2 s over 68 worktrees, so a question reaches him inside about seven.

It also answers `refresh from:body`, the event /static/dashboard.js has always
fired when a tab becomes visible again. Nothing listened for it, so returning to
a tab showed the moment it was left; a background tab's timer is throttled by
the browser, which is why that event matters as much as the interval. A test
pairs the two, because they live in different files and nothing else does.

The refresh is opt-in per page. The board carries no form and nothing the reader
is part-way through, which is what makes replacing its whole container safe.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018q6wZTiLDfNNND1Dnv7UML
Found while reviewing, not by a reviewer: the lane printed "Nothing here is
waiting on you" above a header counting "1 waiting on you". A worktree holding a
question but claiming no act lands in that lane, so the board said two things
about one worktree. The note now says only what stays true whatever the cards
say.

One pane's drifted `agent_session` object cost every agent's reading, because
the array fails on the first entry that will not decode. Nothing on the board
depends on the session id and the contract suite pins its shape against a live
server, so the runtime read degrades instead of blinding the board.

`workspaceFor` is now derived from `backendFor` rather than resolving beside it,
so the port and the name are one decision. The two policies inline in
`AgentSession.of` are named (`stateFor`, `wantingHimFirst`) like its other
rules. The two-second cap on `agent list` is pinned as a unit invariant and
measured against a live server in the contract tier; the wiring between them
needs a seam the adapter does not have, and the test says so. `loudest`'s
`Finished` rank is covered against all four neighbours.

Three references to "slice 06" removed: they named which iteration added a
behaviour, not the behaviour.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018q6wZTiLDfNNND1Dnv7UML
workflow: triage lessons for dashboard-control-plane
All checks were successful
CI / contract (pull_request) Has been skipped
CI / format (pull_request) Successful in 7s
CI / lint (pull_request) Successful in 8s
CI / compile (pull_request) Successful in 1m34s
CI / dashboard-build (pull_request) Successful in 1m7s
CI / test (pull_request) Successful in 10m12s
c0944d6a4b
Promoted:
- a recorded reply is not the contract; the schema is → fixed-at-source
  (herdr fixture README, HerdrReply's required set, the contract suite)
- a hardcoded note outlived the set of states it described → fixed-at-source
  (the lane's note states its membership rule, pinned by a test)

Both recorded locally at Michal's call; neither filed to kanon.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018q6wZTiLDfNNND1Dnv7UML
workflow(sl): close 07-agents-report-themselves (flush parked items)
All checks were successful
CI / contract (pull_request) Has been skipped
CI / format (pull_request) Successful in 8s
CI / lint (pull_request) Successful in 8s
CI / compile (pull_request) Successful in 1m28s
CI / dashboard-build (pull_request) Successful in 1m7s
CI / test (pull_request) Successful in 10m5s
65222533ba
Tracker closed, PR #447 recorded at awaiting_review. The map item is marked done
with its outcome; the parked items are flushed as two candidates
(the-session-says-what-it-is-about, the-board-says-why-it-claims-that) and eight
one-line asides.

Two neighbouring cards corrected rather than left to rot: the refresh half of
the-quiet-ones-fold-away shipped here, and the-board-says-who-may-look gains the
close review's finding that / now self-polls and spawns a subprocess per render.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018q6wZTiLDfNNND1Dnv7UML
workflow(sl): record CI green for slice 07 (PR #447)
All checks were successful
CI / contract (pull_request) Has been skipped
CI / format (pull_request) Successful in 9s
CI / lint (pull_request) Successful in 8s
CI / compile (pull_request) Successful in 1m28s
CI / dashboard-build (pull_request) Successful in 1m6s
CI / test (pull_request) Successful in 10m29s
a1bd608af5
mph merged commit 7430689a90 into main 2026-08-26 06:18:47 +00:00
Sign in to join this conversation.
No description provided.