acpmux: ls PATTERN, dotted --fields, session info --fields, send --tag

Merged
TE
7d ago
+571-65

Leo, 2026-10-30: "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!"

No HQ design issue covers acpmux CLI ergonomics. The closest is HQ#5197 (team tools), which covers pr-owner, not acpmux; the pr-owner who change from this audit goes to that tool in a separate PR.

What lanes actually ran

I went through every Bash call in the agent transcripts on one lane host: 16,464 calls in 159 transcripts, read only. I counted the command shapes that take several commands, a qualified id, or --json piped into python/jq/grep. Each count is calls / transcripts it shows up in.

#Pattern lanes run todayCalls / transcriptsOne commandWhere
1lane-gh call search_issues … | python3 -c 'json…items'134 / 47(outside acpmux)lane-gh owner
2lane-gh call get … | python3 or | head -c142 / 27(outside acpmux)lane-gh owner
3acpmux --json ls | python3 -c '… if r["name"]==X …' or acpmux ls | grep NAME / awk '$1==s'63 / 21acpmux ls 'review-27*' --fields name,status,turn.activitythis PR
4for n in …; do pr-owner who $n | head -3; done27 / 19 (59 / 34 for any pr-owner who)pr-owner who N N…leo-workspace PR
5for n in …; do glaeda-gh status pr …#$n | head/grep; done, and status --json | python342 / 19, 25 / 9(outside acpmux)glaeda-gh owner
6acpmux last X | tail -c 40037 / 10already one command (last -n); not changed—
7acpmux --json info X | python3 -c 'print(d["state"], d["turn"]["activity"])', often in a loop after acpmux wait12 / 5acpmux session info X --fields status,turn.activitythis PR
8for s in $(acpmux ls --tag area=X --json | python3 …); do acpmux send --no-wait $s "$msg"; done5 / 2acpmux send --tag area=X "$msg"this PR

Rows 3, 7 and 8 are acpmux's own. Row 8 is rare in calls, but it is how a coordinator tells a whole area something (base fixed, rebase now), and each instance is a loop with an inline python step.

What changes

  • acpmux ls PATTERN…: a name glob (*, ?) matched against the bare name on any host, a host/glob for one peer's sessions, or an exact session id. It combines with --status, --tag, --fields and --limit.
  • --fields takes dotted paths (turn.activity, lane.state, pullRequests.0.number). The longest key that matches wins, so a tag key with a dot in it (tags.lane.reportsTo) still resolves. Top-level fields behave exactly as before.
  • acpmux session info NAME --fields a,b: a single field prints its bare value (--fields sessionId gives just the id); several fields print field<TAB>value lines; with --json you get an object of only those fields.
  • acpmux ls --name PATTERN (repeatable) is the same as a positional PATTERN. It keeps the --name from #27813 working, because the ls rework drops that PR's regex filter in favour of this glob, so there is one way to filter by name. A pattern also matches a session id, bare or a peer's host/id.
  • acpmux send --tag K=V [--dry-run] MSG: sends one --no-wait message, with its own prompt id, to every non-closed session on any host that carries the tag. A local session is targeted by its id, a peer's by its peer/id (qualifiedSessionId, the key the outbox routes by). It skips the sender's own local session, but not a peer's session that happens to share the bare id. It prints one line per target with its delivery id. It exits 1 when any send failed and 4 (no such session) when no live session carries the tag. --attach is refused with --tag.
  • ls, session info and send gained Examples: in --help. The README's script section and the agent guide (skills/acpmux/SKILL.md) list the new forms.
  • The selection helpers (parse_fields, project_fields, text_field, the tag match) move out of cli/run/standard.rs into cli/select.rs. The send --tag and info --fields handlers live in cli/run/fanout.rs. standard.rs gets 39 lines shorter.

Coordination: the ls compact-default PR (which supersedes #27813) moves the Ls flags and handler into src/cli/run/ls.rs. Whichever of the two PRs lands second carries this PR's pattern clause into that retain, and adds || !patterns.is_empty() to its explicit-selection check, so acpmux ls 'review-*' still shows reviewer rows. The bare-name resolution PR does not touch run/standard.rs; both PRs add an after_help to Send, and the second to land merges the example lists.

Tests

Red/green: cargo test -p acpmux --test one_command runs against a real daemon with the stand-in agent. It covers name globs, a dotted --fields, session info --fields in all three output shapes, send --tag --dry-run, and a send --tag that reaches both tagged sessions (each replies echo: …) but not the session with another tag, which keeps 1 turn. It also checks exit 4 when no session carries the tag, --name, and a dotted field in text mode.

  • Red at 3c3991539e3 (the test commit, base source): error: unexpected argument 'review-27*' found.
  • Green at 560330fe324, and still green at the head after the review fixes. Each fix commit's red is recorded in its commit message: the peer-target and host/id unit tests panic with the old lines.

Unit tests: select:: (dotted paths including dotted keys, tag match, globs, ids) and fanout:: (targets skip closed sessions, other tags and the sender), 12 passed. ls_selection, cli_ergonomics and cli_help_reload still pass, and cargo clippy -p acpmux --lib --tests is clean.

Observed / Not run

  • Observed: everything listed above, on Linux in the acpmux crate (cargo test/clippy for the crate alone).
  • Not run: a send to a real peer over the mesh. The peer path goes through the same _acpmux/send_queue outbox as send --no-wait PEER/NAME, targeted by session id, which resolve_remote matches; I read that path but did not run it.

Changelog

  • Added: acpmux ls PATTERN, dotted --fields paths, acpmux session info --fields and acpmux send --tag for scripts and coordinators.

Agent session: agent session

🤖 Generated with Claude Code