Control board MVP: one page showing running agent sessions and who is waiting on me #1503

Open
opened 2026-09-12 12:14:56 +00:00 by jason.woltje · 9 comments
Owner

Owner decision MOSAIC-STACK-D-001 (2026-09-12): the MVP is one web page that lists running agent sessions across projects, shows each one's current status, and flags which ones are waiting on Jason.

Plan page: docs/plans/2026-09-12_control-board-mvp.md (branch refactor).

Steps:

  1. Today: plan page, this ticket, and a status scanner (packages/control-board) with tests. Running it writes one status file per agent under <dataRoot>/board/.
  2. The web page that reads those files and refreshes itself, grouped by project, with a "waiting on you" section.
  3. Daily use by Jason; fix what he says is wrong.

Scope: Pi sessions only. No hooks, plugins, comms, memory, auth, launcher changes, or new root files. Board files are derived and rewritable, not run records.

The registry line (increment 3, headless identity-env leak) stays parked; #1500 is closed.

Owner decision MOSAIC-STACK-D-001 (2026-09-12): the MVP is one web page that lists running agent sessions across projects, shows each one's current status, and flags which ones are waiting on Jason. Plan page: `docs/plans/2026-09-12_control-board-mvp.md` (branch `refactor`). Steps: 1. Today: plan page, this ticket, and a status scanner (`packages/control-board`) with tests. Running it writes one status file per agent under `<dataRoot>/board/`. 2. The web page that reads those files and refreshes itself, grouped by project, with a "waiting on you" section. 3. Daily use by Jason; fix what he says is wrong. Scope: Pi sessions only. No hooks, plugins, comms, memory, auth, launcher changes, or new root files. Board files are derived and rewritable, not run records. The registry line (increment 3, headless identity-env leak) stays parked; #1500 is closed.
Author
Owner

Step 1 done and pushed on refactor: plan page docs/plans/2026-09-12_control-board-mvp.md, scanner packages/control-board (23/23 tests, independent review approved; receipt docs/plans/reviews/2026-09-12_control-board-step1-review.md). Live scan on the dev host: 42 sessions, 18 waiting on Jason. Next: step 2, the web page.

Step 1 done and pushed on `refactor`: plan page `docs/plans/2026-09-12_control-board-mvp.md`, scanner `packages/control-board` (23/23 tests, independent review approved; receipt `docs/plans/reviews/2026-09-12_control-board-step1-review.md`). Live scan on the dev host: 42 sessions, 18 waiting on Jason. Next: step 2, the web page.
Author
Owner

Step 2 done and pushed: ebedd128 on refactor.

What you can do now

cd /mnt/storage/src/mosaic-stack
node packages/control-board/src/cli.mjs serve

then open http://127.0.0.1:7331/ (your machine only; no login; Ctrl-C stops it).

The page shows "Waiting on you" first (errors on top), then one table per project. Rows expand for detail, offline agents are hidden per project by default, and it refreshes every 10 seconds (pause button in the header).

Evidence: control-board tests 33/33, registry 69/69; live check in Chrome with the full fleet (18 waiting rows, 24 offline hidden, expanded rows survive refresh). Receipt: docs/plans/reviews/2026-09-12_control-board-step2-review.md.

Next (step 3): use it daily and report what is wrong, missing or confusing here. Each report becomes a small fix with a test. Close this issue when the page answers "who is waiting on me" without opening a terminal.

Step 2 done and pushed: `ebedd128` on `refactor`. **What you can do now** ``` cd /mnt/storage/src/mosaic-stack node packages/control-board/src/cli.mjs serve ``` then open http://127.0.0.1:7331/ (your machine only; no login; Ctrl-C stops it). The page shows "Waiting on you" first (errors on top), then one table per project. Rows expand for detail, offline agents are hidden per project by default, and it refreshes every 10 seconds (pause button in the header). **Evidence**: control-board tests 33/33, registry 69/69; live check in Chrome with the full fleet (18 waiting rows, 24 offline hidden, expanded rows survive refresh). Receipt: `docs/plans/reviews/2026-09-12_control-board-step2-review.md`. **Next (step 3)**: use it daily and report what is wrong, missing or confusing here. Each report becomes a small fix with a test. Close this issue when the page answers "who is waiting on me" without opening a terminal.
Author
Owner

First step-3 refinement pushed: 88d21def on refactor.

From Jason's feedback: killed pi sessions (darkwing, dewey) still showed "waiting", and most waiting rows were completion reports, not questions.

Fixed

  • Liveness now checks that a pane in the agent's tmux session is actually running pi. A session that only runs a shell now shows offline. Waiting on you went from 18 to 16 on the live board.
  • New Seen button on waiting/error rows. Click it after reading; the row leaves "Waiting on you" until the agent writes anything new, then it comes back on its own. Unsee reverses it. Marks live in ~/.mosaic-dev/board/seen.json, written only by clicks.

Evidence: control-board tests 63/63 (30 new), registry 69/69; Seen/Unsee round trip verified in Chrome; review APPROVED. Receipt: docs/plans/reviews/2026-09-12_control-board-step3-seen-marks.md.

Still open by design: the scanner cannot tell a question from a completion report. If Seen is not enough, next candidates are a wording hint on the badge or an agent-side "Waiting on:" line.

Restart the server to pick this up: Ctrl-C, then node packages/control-board/src/cli.mjs serve.

First step-3 refinement pushed: `88d21def` on `refactor`. **From Jason's feedback**: killed pi sessions (darkwing, dewey) still showed "waiting", and most waiting rows were completion reports, not questions. **Fixed** - Liveness now checks that a pane in the agent's tmux session is actually running `pi`. A session that only runs a shell now shows offline. Waiting on you went from 18 to 16 on the live board. - New **Seen** button on waiting/error rows. Click it after reading; the row leaves "Waiting on you" until the agent writes anything new, then it comes back on its own. **Unsee** reverses it. Marks live in `~/.mosaic-dev/board/seen.json`, written only by clicks. **Evidence**: control-board tests 63/63 (30 new), registry 69/69; Seen/Unsee round trip verified in Chrome; review APPROVED. Receipt: `docs/plans/reviews/2026-09-12_control-board-step3-seen-marks.md`. **Still open by design**: the scanner cannot tell a question from a completion report. If Seen is not enough, next candidates are a wording hint on the badge or an agent-side "Waiting on:" line. Restart the server to pick this up: Ctrl-C, then `node packages/control-board/src/cli.mjs serve`.
Author
Owner

Second step-3 refinement pushed: b7074921 on refactor.

From Jason's feedback: after marking two rows Seen there was no way to find them again.

Fixed: a collapsed Seen (N) section now sits between "Waiting on you" and "By project". Click the triangle to open it; every marked row is listed with an Unsee button. It stays open or closed across refreshes.

Evidence: page-only change, one static test added (control-board 64/64, registry 69/69), review APPROVED with no findings, checked in Chrome against the two real marks (resume, filbert).

Restart the server to pick this up: Ctrl-C, then node packages/control-board/src/cli.mjs serve.

Second step-3 refinement pushed: `b7074921` on `refactor`. **From Jason's feedback**: after marking two rows Seen there was no way to find them again. **Fixed**: a collapsed **Seen (N)** section now sits between "Waiting on you" and "By project". Click the triangle to open it; every marked row is listed with an Unsee button. It stays open or closed across refreshes. **Evidence**: page-only change, one static test added (control-board 64/64, registry 69/69), review APPROVED with no findings, checked in Chrome against the two real marks (resume, filbert). Restart the server to pick this up: Ctrl-C, then `node packages/control-board/src/cli.mjs serve`.
Author
Owner

Step 3, third refinement: per-project "Hide seen" checkbox — commit e90d15dd on refactor, pushed.

Jason asked for a "Hide seen" checkbox beside "Hide offline" on each project table. Done: both on by default, the note under the table reads "N offline hidden · N seen hidden", and the choice survives the 10-second refresh (state kept per project in the page, same pattern as Hide offline). Page-only change plus one static test (control-board suite 65/65, registry 69/69). Sonnet review: APPROVED, one FYI on test rigor (the default-on assertion would still pass without the persistence guard) fixed by tightening the regex. Verified live in Chrome against Jason's real marks: unticking "Hide seen" on fleet brings the seen resume row back with its Unsee button and the note drops to "24 offline hidden".

Records: README, plan page Step 3 log, SESSIONS.md, BUILD-LOG.md. Next: Jason keeps using the board; each report becomes a small fix.

**Step 3, third refinement: per-project "Hide seen" checkbox** — commit `e90d15dd` on `refactor`, pushed. Jason asked for a "Hide seen" checkbox beside "Hide offline" on each project table. Done: both on by default, the note under the table reads "N offline hidden · N seen hidden", and the choice survives the 10-second refresh (state kept per project in the page, same pattern as Hide offline). Page-only change plus one static test (control-board suite 65/65, registry 69/69). Sonnet review: APPROVED, one FYI on test rigor (the default-on assertion would still pass without the persistence guard) fixed by tightening the regex. Verified live in Chrome against Jason's real marks: unticking "Hide seen" on fleet brings the seen `resume` row back with its Unsee button and the note drops to "24 offline hidden". Records: README, plan page Step 3 log, SESSIONS.md, BUILD-LOG.md. Next: Jason keeps using the board; each report becomes a small fix.
Author
Owner

Step 3, fourth refinement: project header reads "N of N" — commit bf1391a4 on refactor, pushed.

Jason pointed out that "fleet (38)" sat above a table showing 13 rows. The header now reads "fleet (13 of 38)" while Hide offline or Hide seen hides at least one row, and falls back to "fleet (38)" when nothing is hidden. The note under the table stays as the breakdown (header says how many, note says why). Page-only change, numbers only so nothing new needs escaping, one static test (control-board 66/66, packages/mosaic 69/69). Sonnet review: APPROVED, no findings. Verified live in Chrome on a scratch server against Jason's real marks: "1 of 4", "3 of 4", plain "4", "13 of 38", console clean.

Records: README, plan page Step 3 log, SESSIONS.md, BUILD-LOG.md. Next: Jason keeps using the board.

**Step 3, fourth refinement: project header reads "N of N"** — commit `bf1391a4` on `refactor`, pushed. Jason pointed out that "fleet (38)" sat above a table showing 13 rows. The header now reads "fleet (13 of 38)" while Hide offline or Hide seen hides at least one row, and falls back to "fleet (38)" when nothing is hidden. The note under the table stays as the breakdown (header says how many, note says why). Page-only change, numbers only so nothing new needs escaping, one static test (control-board 66/66, packages/mosaic 69/69). Sonnet review: APPROVED, no findings. Verified live in Chrome on a scratch server against Jason's real marks: "1 of 4", "3 of 4", plain "4", "13 of 38", console clean. Records: README, plan page Step 3 log, SESSIONS.md, BUILD-LOG.md. Next: Jason keeps using the board.
Author
Owner

Step 3: mid-tool-call rule checked and pinned — commit 6ec253de on refactor, pushed (with Jason's ruling commit bf641e22 beneath it).

Rule: a seat mid-tool-call is working, never waiting. Finding: the scanner already honoured it, but only through pi's stopReason (toolUse and tool results were working, stop was waiting). deriveState now also looks at the content: an assistant message with a toolCall block is working whatever its stop reason; an error stop reason still wins; a thinking block does not keep a text turn from being waiting (stated assumption, since real waiting turns carry thinking+text).

Tests: three JSONL fixtures (tool call after a question-looking text, tool result last, finished text-only turn) plus three state-table cases. Control-board 69/69, packages/mosaic 69/69. Sonnet review: APPROVED, no findings.

Live check at 14:27Z on the real board: orch-01 (assistant + toolCall) working, rev-code-01 (tool result last) working, velma (text-only, stop) waiting. All 13 waiting seats had text-only or thinking+text last messages; none had a tool call, so no seat changed state. This closes a gap, not a bug.

Records: README state table, plan page Step 3 log, SESSIONS.md, BUILD-LOG.md.

**Step 3: mid-tool-call rule checked and pinned** — commit `6ec253de` on `refactor`, pushed (with Jason's ruling commit `bf641e22` beneath it). Rule: a seat mid-tool-call is `working`, never `waiting`. Finding: the scanner already honoured it, but only through pi's `stopReason` (`toolUse` and tool results were working, `stop` was waiting). `deriveState` now also looks at the content: an assistant message with a `toolCall` block is working whatever its stop reason; an error stop reason still wins; a `thinking` block does not keep a text turn from being waiting (stated assumption, since real waiting turns carry thinking+text). Tests: three JSONL fixtures (tool call after a question-looking text, tool result last, finished text-only turn) plus three state-table cases. Control-board 69/69, packages/mosaic 69/69. Sonnet review: APPROVED, no findings. Live check at 14:27Z on the real board: orch-01 (assistant + toolCall) working, rev-code-01 (tool result last) working, velma (text-only, stop) waiting. All 13 waiting seats had text-only or thinking+text last messages; none had a tool call, so no seat changed state. This closes a gap, not a bug. Records: README state table, plan page Step 3 log, SESSIONS.md, BUILD-LOG.md.
Author
Owner

Gate A fix, commit 4a7e16c3 on refactor: every row now shows Task, Active project, and (in the detail row) Workspace. Each value is derived from the session log and tmux, or reads "unknown". Nothing is guessed.

  • Task: the session's first user message. pi logs carry no task envelope, so for fleet seats this is the fleet-comms envelope the seat started with, and for hand-started seats whatever was typed ("resume", "orchestrate").
  • Active project: basename of the nearest git checkout above the workspace. All fleet seats read .mosaic because they run in ~/.mosaic, which is a git checkout, and the roster names no project.
  • Workspace: live pane_current_path of the pane running pi; for offline seats, the cwd from the log. The record says which (workspaceSource).

Missing launcher signals are written in the plan page Step 3 log: a task label in the first message or a per-seat status file, and a project field in roster.yaml.

Tests: fixture per field, tmux parse tests, page test. control-board 79/79, packages/mosaic 69/69. Sonnet review: no findings. Live check on a scratch server: 42 rows, all three fields present on every row.

Gate A fix, commit 4a7e16c3 on `refactor`: every row now shows Task, Active project, and (in the detail row) Workspace. Each value is derived from the session log and tmux, or reads "unknown". Nothing is guessed. - Task: the session's first user message. pi logs carry no task envelope, so for fleet seats this is the fleet-comms envelope the seat started with, and for hand-started seats whatever was typed ("resume", "orchestrate"). - Active project: basename of the nearest git checkout above the workspace. All fleet seats read `.mosaic` because they run in `~/.mosaic`, which is a git checkout, and the roster names no project. - Workspace: live `pane_current_path` of the pane running `pi`; for offline seats, the `cwd` from the log. The record says which (`workspaceSource`). Missing launcher signals are written in the plan page Step 3 log: a task label in the first message or a per-seat status file, and a `project` field in `roster.yaml`. Tests: fixture per field, tmux parse tests, page test. control-board 79/79, packages/mosaic 69/69. Sonnet review: no findings. Live check on a scratch server: 42 rows, all three fields present on every row.
Author
Owner

Landed in 0bed9ba3 on refactor: the board now shows which model each seat is running (Jason: "The running agent (sonnet, opus, gpt-6-astra) should surface in the interface as well").

  • Source: the pi log names the model twice, a model_change entry (provider, modelId) at launch and on every /model switch, and model/provider on each assistant turn. The scanner keeps whichever comes last, so a switch shows on the next scan even before the seat answers.
  • Record: model and provider on every row. Page: model in small type under the agent name, provider in the hover, and a Model detail row. Blank when the log names none; rocko has no pi log and stays off the board.
  • Live check on a scratch board against the real data root: all 42 rows carried a model. Board suite 91/91.
  • Sonnet review caught a double-escaped hover title; fixed before commit.

The running board on 7331 needs a restart to pick this up.

Landed in 0bed9ba3 on `refactor`: the board now shows which model each seat is running (Jason: "The running agent (sonnet, opus, gpt-6-astra) should surface in the interface as well"). - Source: the pi log names the model twice, a `model_change` entry (`provider`, `modelId`) at launch and on every `/model` switch, and `model`/`provider` on each assistant turn. The scanner keeps whichever comes last, so a switch shows on the next scan even before the seat answers. - Record: `model` and `provider` on every row. Page: model in small type under the agent name, provider in the hover, and a Model detail row. Blank when the log names none; rocko has no pi log and stays off the board. - Live check on a scratch board against the real data root: all 42 rows carried a model. Board suite 91/91. - Sonnet review caught a double-escaped hover title; fixed before commit. The running board on 7331 needs a restart to pick this up.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#1503