Files
stack/docs/plans/2026-09-12_control-board-mvp.md
T
jason.woltjeandClaude Fable 5.1 88d21defde Check pi liveness per tmux pane and add "Seen" marks to the control board (#1503)
First step-3 refinement from Jason's daily use. Liveness now lists the
panes of the agent's tmux session and counts it alive only if a pane runs
pi, so killed pi sessions whose tmux session still exists show offline
instead of waiting. A "Seen" button on waiting and error rows stores the
row's lastActivity in <dataRoot>/board/seen.json (clicks only, never
rewritten by a scan, fail closed if corrupt) and drops the row from
"Waiting on you" until the agent writes anything newer; "Unsee" reverses
it. New POST /api/seen route: JSON only, 4 KB limit, 400 on bad input.

Tests: control-board 63/63 (30 new), registry 69/69. Review APPROVED;
receipt docs/plans/reviews/2026-09-12_control-board-step3-seen-marks.md.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-12 08:24:49 -05:00

136 lines
5.2 KiB
Markdown

# Control board MVP — plan
Tracking: Gitea issue #1503 (https://git.mosaicstack.dev/mosaicstack/stack/issues/1503)
## Why
Jason is building blind. He has many agent sessions running across projects
and no single place to see what they are doing or who is waiting on him.
This plan replaces the registry line as the next thing to build. Registry
increment 3 stays parked; it does not resume by inertia. Decision record:
`MOSAIC-STACK-D-001` (Jason, 2026-09-12).
## What "done" looks like for the MVP
One web page lists my running agent sessions across projects, shows each
one's current status, and flags which ones are waiting on me.
Acceptance:
- Jason opens one page and sees every live pi agent, grouped by project.
- Each agent shows a state in plain words (working, waiting, error,
offline, idle, unknown).
- A "waiting on you" section sits at the top of the page.
- The page refreshes itself. Jason does not have to reload it by hand.
- Every state on the page comes from a status file written by the scanner,
not from a guess made in the page itself.
## Three steps
### Step 1 (today): plan, ticket, scanner
- Write this plan and open the Gitea ticket.
- Build the status scanner script with tests. Running it writes one status
file per agent under `<dataRoot>/board/`.
- This step does not touch any page or launcher. It only produces files.
### Step 2: the web page
- Build a page that reads the status files the scanner writes and shows
them grouped by project, with the waiting-on-you section on top.
- The page refreshes itself on a short timer. The small local server behind
it may re-run the scanner on each refresh so the files stay fresh. No
separate daemon is needed for this step.
### Step 3: daily use and fixes
- Jason uses the page every day for real work.
- He says what is wrong or missing.
- Fix that one thing, then repeat. No new scope beyond what he actually hits.
### Step 3 log
**2026-09-12 — liveness follows the tmux pane, not just the session.**
Jason found agents marked "waiting" that were actually dead: their tmux
session still existed but no longer ran `pi` (it had exited to a shell or
something else). The scanner now checks which program each tmux pane is
running (`tmux list-panes -s -t '=<session>' -F '#{pane_current_command}'`) and only counts an
agent alive if a pane is running `pi`. A tmux session with no `pi` pane is
now "offline" instead of "waiting".
**2026-09-12 — a "Seen" mark for rows that don't need a reply.** Jason
noticed most "waiting" rows were completion reports, not real asks, and
they kept cluttering "Waiting on you". The page now has a "Seen" button on
waiting/error rows; clicking it stores the row's `lastActivity` in
`<dataRoot>/board/seen.json` (via `POST /api/seen`) and drops the row out
of "Waiting on you" while keeping its state visible. The mark clears
itself the moment the agent writes anything new, and "Unsee" puts a row
back by hand. `seen.json` is only ever written by that click, never by a
scan.
## How the scanner decides state
The scanner reads the newest pi session log for each agent, plus whether
the agent's tmux session is still alive. It does not guess; it only reports
what the log and tmux say.
| State | Plain-words meaning |
|---------|----------------------|
| working | The agent is in the middle of a turn: thinking or running a tool. |
| waiting | The agent finished its turn. It is your move now. |
| error | The agent's last turn ended in an error, was aborted, or was cut off. Go look at it. |
| offline | There is no live tmux session for this agent right now. |
| idle | The agent is live but has not had a conversation yet. |
| unknown | The scanner could not ask tmux (missing or not answering). It does not assume the agent is alive. |
Two kinds of agents are scanned today:
- Repo agents: session logs live under `.pi/state/<agent>/sessions` in a
project checkout, on the default tmux socket.
- Fleet agents: session logs live under
`~/.mosaic/fleet/agents/<agent>/.pi/agent/sessions`, on the tmux socket
named `mosaic-fleet`.
## Where files go
- One file per agent: `<dataRoot>/board/sessions/<project>/<agent>.json`
- One summary file for the whole board: `<dataRoot>/board/index.json`
- `dataRoot` comes from `~/.config/mosaic-dev/config.json`. If that config
is missing or broken, the scanner refuses to run. It does not guess a
fallback location.
Board files are derived. They can be deleted and rebuilt at any time by
running the scanner again. They are NOT run records and they are not
evidence under the repository's write-once rules.
## Commands
Scan once and print the table:
```
node packages/control-board/src/cli.mjs scan --print
```
Start the page (step 2), then open http://127.0.0.1:7331/ in a browser:
```
node packages/control-board/src/cli.mjs serve
```
Tests:
```
node --test packages/control-board/tests/
```
## Boundaries
- No changes to any launcher script.
- No new files at the repository root.
- No secrets read, stored, or printed.
- No daemon yet. In step 1 the scanner is run by hand. In step 2 the page
may trigger it on refresh; nothing runs it on a schedule.
- No changes to `packages/mosaic`.
- This plan does not authorize push or merge beyond whatever the existing
refactor-branch plan already allows.