acpmux ls: compact by default (live named lanes, STUCK, UNKNOWN peers); never starts a daemon

TE
7d ago
+1294-257

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 (STUCK peer offline), and one line per down peer says UNKNOWN <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 says UNKNOWN peers: their link states could not be read and does not assume no peer is down.
  • ls never starts a daemon (text and --json). With none running it fails at once with no_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).
  • --json is unchanged. I grepped leo-workspace tools/, machines/ and cmux scripts/: every script already reads acpmux --json ls or ls --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_none fails at its started a daemon assertion, and the_default_text_list_is_compact_and_all_brings_the_rest_back fails 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); --all keeps 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 --help shows 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/stale fields and its peers status 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:

metricbeforeafterdeltaharness or run link
ls p50, 400 sessions28.9 ms19.8 ms-31%ls_bench.py run
ls p95, 400 sessions69.0 ms27.5 ms-60%ls_bench.py run
ls output, 400 sessions401 lines / 45,966 B202 lines / 5,515 B-88% bytesls_bench.py run
ls p50, 3000 sessions230.5 ms152.7 ms-34%ls_bench.py run
ls p95, 3000 sessions276.0 ms194.6 ms-29%ls_bench.py run
ls output, 3000 sessions3001 lines / 344,316 B1502 lines / 43,325 B-87% bytesls_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