acpmux ls: compact by default (live named lanes, STUCK, UNKNOWN peers); never starts a daemon
Summary
Leo, 2026-10-30: "acpmux ls should probably not be overly... verbose? i guess" / "to keep everything efficient... tho idk", and "i feel like there's still a big mess of uncategorized stuff in the sidebar + various not as ergonomic/one-command-y as i'd liike set of commands for interacting with stuff" / "good intuitive stuff" / "and also docs!".
Part of HQ#5123 (the CLI ergonomics row) and HQ#4897 failure 4 / HQ#5075 (a listing that starts a daemon).
On a busy lane host, acpmux ls printed every session the daemon knew: about 370 rows there, 222 of them closed and about 200 reviewer boxes. A listing on a home with no daemon also started an empty one and printed "no sessions". Census probes read that as a host with nothing on it (HQ#4897 failure 4).
Now:
- Compact by default. Only the live lanes someone named, one line each: NAME, HOST (only while some row is on a peer), STATE, TURNS, AGE, STUCK, and WARN (only while some row breaks a usage rule, #27899's
usageWarnings). STUCK says in a few words why a lane needs someone:permission,capacity,harness down,failed turn,stalled 12m. Closed sessions, reviewer boxes, sub-sessions and sessions with only a generated name (codex-2) are hidden. The last line counts them:254 more hidden (222 closed, 32 reviewers): acpmux ls --all. On that host the list drops to 120 lines, and no row is ever cut off. --all(-a) lists the hidden rows, in the daemon's order (newest first), as every view does.--wide(-w) adds AGENT, STATUS and LAST (the last prompt). A filter (--status,--tag,--since,--limit,--live,--host) hides nothing.- A down peer is never dropped. Its rows read
unknown(STUCKpeer offline), and one line per down peer saysUNKNOWN <peer>: peer offline for 3m; its sessions are last known, not live. Peer states come from_acpmux/status, read alongside the list. If that read fails, the list saysUNKNOWN peers: their link states could not be readand does not assume no peer is down. lsnever starts a daemon (text and--json). With none running it fails at once withno_daemon. While a live daemon holds the lock but isn't serving yet (a restart handover), it waits for the socket as before (daemon/running.rs, which reads the lock's pid without taking the lock or creating the file).--jsonis unchanged. I grepped leo-workspacetools/,machines/and cmuxscripts/: every script already readsacpmux --json lsorls --json, so no script parses the text table and nothing moves.
Supersedes #27813, keeping its three commits and their authorship: --live, --host, the move of ls into cli/run/ls.rs, and a closed fixture that keeps a log. Two changes to it: no 40-row cap (the list is never cut; the last line counts what's hidden), and no --name REGEX. Name filtering is #28012's positional glob (acpmux ls 'review-27*'), the one way to filter by name, with --name as its alias there. Whichever of this and #28012 lands second carries the other's hunk into cli/run/ls.rs; a name pattern counts as a filter, so it hides nothing.
Column contract agreed with #27899 (WARN: one word, rules named in --wide) and #28015 (usage columns go in --wide after STUCK and before WARN; only --wide/--json ask the daemon for usage).
Testing
- Red on the base, green on the head:
cargo test -p acpmux --test ls_selection. On the base,ls_with_no_daemon_starts_nonefails at itsstarted a daemonassertion, andthe_default_text_list_is_compact_and_all_brings_the_rest_backfails on the missing hidden count. On the head, all 3 pass, including #27813's large-store test with the updated compact assertions (2700 live rows, never cut;300 more hidden (300 closed);--allkeeps newest-first order). cargo test -p acpmux --lib -- ls_table daemon::running cli::run::ls help_contract: 22 pass (the renderer, hide rules, STUCK reasons, UNKNOWN peers, WARN, the handover wait, the read-only lock probe, the help contract).- Review fixes (Codex Sol, passes 1-2 BLOCK): a filter that matches nothing still names down peers; each connect attempt is bounded by the handover budget (a socket that never answers its hello); on Windows the probe asks the lock itself with a non-blocking shared try, which can delay a start that collides with it (the starter retries on
lockHeld) but never fail one. The first two have unit tests; the Windows branch is not compiled here (no Windows target on this host). cargo clippy -p acpmux --all-targets: clean.scripts/cmux-next/prepush-gate.py: no new violations.
Observed / Not run
- Observed: the head build against a live lane host's daemon (370 sessions): 120 lines, with the hidden count above.
acpmux ls --helpshows the examples. - Not run: a peer actually going down on a live mesh. The UNKNOWN rows and lines are covered by unit tests on the daemon's
peerState/stalefields and itspeersstatus list.
Performance
scripts/ls_bench.py (in this PR): the base (d85f7bd4ac7) and head acpmux ls clients against one head daemon in a temporary home with synthetic sessions (a mix of named lanes, reviewer boxes and generated names, as a busy lane host has), 30 runs each, interleaved, on a 32-vCPU Linux CI box (run, at de82911; the later Windows-only commit doesn't touch this path). Default ls text output:
| metric | before | after | delta | harness or run link |
|---|---|---|---|---|
ls p50, 400 sessions | 28.9 ms | 19.8 ms | -31% | ls_bench.py run |
ls p95, 400 sessions | 69.0 ms | 27.5 ms | -60% | ls_bench.py run |
ls output, 400 sessions | 401 lines / 45,966 B | 202 lines / 5,515 B | -88% bytes | ls_bench.py run |
ls p50, 3000 sessions | 230.5 ms | 152.7 ms | -34% | ls_bench.py run |
ls p95, 3000 sessions | 276.0 ms | 194.6 ms | -29% | ls_bench.py run |
ls output, 3000 sessions | 3001 lines / 344,316 B | 1502 lines / 43,325 B | -87% bytes | ls_bench.py run |
ls --all --wide (every row and column, closest to the old output plus HOST/STUCK): p50 25.5 ms / 193.6 ms, p95 34.4 ms / 253.2 ms at 400 / 3000 sessions, so the extra _acpmux/status read for peer states, made alongside the list, costs no more than the old renderer. --json is unchanged and makes no extra request.
Changelog
Changed: acpmux ls lists only live, named lanes with a STUCK reason by default, and counts the rest (--all lists them, --wide adds every column); it never starts a daemon.
Proof
CLI only, no UI. Sample output on a synthetic set of 400 sessions:
NAME STATE TURNS AGE
lane-399 idle 3 1s
lane-398 idle 3 2s
lane-397 idle 3 3s
lane-392 idle 3 8s
lane-391 idle 3 9s
...
lane-0 idle 3 6m
200 more hidden (150 reviewers, 50 unnamed): acpmux ls --all
Agent session: agent session
🤖 Generated with Claude Code