Fleet project: replace the session board with a live-derived view (tracking) nf-5da ← Beads

open priority 2 task unassigned

Twinned from https://github.com/cfsh/ninefold/issues/832 by tools/beads/import_github.py's reconcile pass.

Tracking issue for the fleet project. Filed off a comparison against [codingagentsystem/cas](https://github.com/codingagentsystem/cas), whose one genuinely-better idea is a live view of every running agent. Ours is a 42 KB hand-maintained markdown table.

Sub-issues
  1. #834 — delete the session board (#398) and its supporting machinery. Sequenced first per Aedan below.
  2. #835dev.py fleet MVP: derive the session table live, CLI only, on nf-dev-sg.
  3. Webserver / remote view, cross-box aggregation, remote-control-session visibility — real follow-ups named below, not yet filed. File when #835 is done and the shape of the CLI output makes the web version obvious.
The problem

The session board (#398) is a hand-edited mutable document that caches state git and GitHub already hold. Its own header says so:

> Regenerated by /triage from live GitHub state each round — labels, CLAIM markers, open PRs and claude agents are the source of truth, and this body is a cache of them. Between rounds a session edits only its own row.

A cache that humans and agents write by hand drifts, and we have paid for that drift repeatedly:

Every one of those is a consequence of the storage choice, not of any single bug. Eight audit categories, a CAS verb, two poll-churn bugs and a policy question are the cost of keeping fleet state in prose.

And it costs every dev directly: editing your row is a step in the loop, a gh round-trip, a re-read-before-write rule, and a thing to remember at wrap-up. That is A1 (programmer time and cognitive load are the most expensive resource) and E6 (avoid hand-hacking — write programs to write programs).

The change

Keep git and GitHub as the store of record. Stop hand-maintaining the view.

Assigned issues, tracked PRs, branches and commits stay exactly where they are. What goes away is the hand-written cache of them. Aedan, in the conversation that prompted this:

> "I like the idea of dev.py fleet, and I think we would save devs time if they weren't updating the github pinned issue necessarily. We still use github /git for the storage of assigned issues, tracked PRs commits etc - just visualise differently. We could easily roll a small webserver to serve dev.py fleet to me too."

./dev.py fleet — a read-only renderer

Every column of the Sessions table is already derivable:

| Column | Source | |---|---| | Session, sid, Clone | claude agents --json (via nflib.sessions — see #658) | | liveness | tmux ls | | Branch, last-commit age | git worktree list + git log in each clone | | Chunk, Issues | active + session:<x> labels, and CLAIM/RELEASE markers | | Tracked PRs | each worktree's <state>/tracked-prs.txt | | PR state | gh pr list — review decision, mergeability, rev:* verdict at head |

The residue that is *not* derivable — the chunk's name and Type, each role's one-line description — is ephemeral /triage output, not stored anywhere. See *Decided* below.

Why this is worth doing rather than tolerating

B7 — sidestep failure modes: redesign systems so whole categories of errors become impossible. A view computed at read time cannot have a ZOMBIE, GHOST, UNREGISTERED or STALE-SID row, because there is no row to go stale. Those four audit_sessions.py categories stop being findings and become *unrepresentable*. The genuinely-real anomalies — DOUBLE-BOOKED, MISPLACED, EMPTY-TMUX, NO-TMUX — survive as badges in the view itself, which is where a reader wants them anyway. Verify that split before relying on it; it was this issue's claim, not a measured result, when written.

Downstream, B12 (relish deleting code): #451 and #623 close as designed-out, #650 becomes moot, and a chunk of board.py + audit_sessions.py collapses — see #834 for the full deletion list.

Decided

Answered by aedanpope directly on this issue — not open questions anymore:

  1. #398 does not survive. *"retire #398 since it's gonna go stale anyway, people/agents who want to know can call fleet."* Closed via #834, before fleet exists.
  2. Non-derivable residue (chunk name/Type, role descriptions) is ephemeral. *"can just exist ephemerally as /triage output, don't need to be stored at the moment."* No new store.
  3. No cross-box aggregation. *"Fleet lives on nf-dev-sg so no need to aggregate, we can serve the webview from nf-dev-sg too so it only shows tmux & claude-agent info for sessions on that machine."* Remote-control-session visibility (via GitHub or the Claude API) is a maybe-later, explicitly "not critical" — not in #835.
  4. Server lives in tools/fleet/ — *"so we can add webserv and stuff alongside the py files needed."*
  5. Sequencing: delete the board before building fleet, not after. *"Right now we're barely using session board."* — reversed the original "run them side by side to measure drift" plan; #834 lands first.
Related

#833 — the push-signal half of the same gap: nothing tells Aedan a session is *blocked*, only that one is done. "Blocked" becomes a computed badge in this view for free once #835 lands; #833 is the case where nobody is looking at any view. Independent, but they must share one predicate for blocked rather than growing two.

Not in scope

The CAS comparison also surfaced its SQLite + BM25 context store. Deliberately rejectednotes/, issue comments and MEMORY.md are git-native, reviewable and diffable, and an opaque local DB trades all three for search we do not need at this scale. This issue is the view, not a new store.

Dependencies

None.

Comments

No comments.

Add a comment