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.
dev.py fleet MVP: derive the session table live, CLI only, on nf-dev-sg.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:
audit_sessions.py had to be built to pair rows against claude agents --json and name the mismatches: ZOMBIE, GHOST, UNREGISTERED, STALE-SID, MISPLACED, DOUBLE-BOOKED, EMPTY-TMUX, NO-TMUX.board.py a compare-and-swap --set-row verb; #820 wires the remaining hand-rolled callers — now moot, see #834.poll.py tick.poll.py --check reports CHANGED every tick because the board escapes per-session scoping.--board is parsed but the report always names #398.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).
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 rendererEvery 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.
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.
Answered by aedanpope directly on this issue — not open questions anymore:
fleet."* Closed via #834, before fleet exists.tools/fleet/ — *"so we can add webserv and stuff alongside the py files needed."*#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.
The CAS comparison also surfaced its SQLite + BM25 context store. Deliberately rejected — notes/, 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.
None.
No comments.