acpmux: new and ensure keep a predecessor's harness, or say why not

TE
8d ago
+774-27

Part of manaflow-ai/cmuxterm-hq#5266 (item 3). Items 1-2 (recovery and adopt keep the harness) are #27903.

Leo, 2026-10-30: "that visibility into [one fleet host's] guys was (and is?) pretty bad i feel like we can make more improvements to acpmux/cmux-next". Four deputies were relaunched with only area and role tags. Each came back on the bare default claude instead of its claude-sr2 profile, without the autocompact cap, and ran that way for hours. Nothing said so.

What changes

acpmux new and acpmux ensure (a session/new from the local socket or a peer's --host, never an app chat, an adopt or an ephemeral run) look for a predecessor. They take the first of these that matches:

  1. --harness-from P (new flag on new and ensure)
  2. a succeeds=P tag
  3. --report-to P, or the starting session, when P's name is in the same name family (dep-fleet-2 started by dep-fleet)
  4. a NAME-N name (N has one or two digits; review-27903 is an id, not a generation): the newest other session named NAME or NAME-K
  5. an area=A tag: the newest other session with that area that is live or changed in the last week. The starter and the --report-to target are never counted as predecessors here.

Then:

  • Refuse: there is no -m, -p or --harness-from, the start would land on the default harness, and P ran a named profile (one that its family name does not resolve to). The error names both sessions and both ways out: --harness-from P keeps P's profile, -m <default> starts on the default on purpose. Reason: successor.harness_dropped. This is tonight's exact case: the deputies' area and role tags matched a session with the same area and role. An area match alone only warns, because one coordinator's helpers share an area and are siblings, not successors.
  • Warn: the session is created, but its profile, launcher (argv[0] of what it last started with), model, effort or autocompact cap differs from P's recorded one. The reply carries _meta.acpmux.successor = {predecessor, via, differences, warning}, and the CLI prints it as one line on stderr: acpmux: warning: dep-fleet-2 runs unlike its predecessor dep-fleet (by its name): harness claude-sr2 -> claude, autocompact 400000 -> none; --harness-from dep-fleet keeps the original. A model or effort that one side doesn't know yet is not counted as a difference.
  • --harness-from P copies P's profile, preset, model and effort exactly. It conflicts with -m/-p/-e, is refused over a remote-origin connection (that connection's guard checked the harness the request names, not a copied one), and is refused with --host on new and ensure (P is a session on this machine). If a copy still differs, for example because P's profile changed since it started, the session warns about that too.

The predecessor is found in one pass over the session index. succeeds= and --report-to name a session by its exact id or name.

The autocompact cap is read from --autocompact N / --autocompact=N in the session's spawn argv, or from an env key that names autocompact in its profile, preset or family defaults.

Cost: one pass over the in-memory session index under its lock, plus the config read. No transcript, store or disk read is added to session/new.

Layout: the logic is in hub/lineage/successor.rs. The server glue is server/requests/successor.rs, and requests.rs is 10 lines shorter than on base because the peer-forward meta strip moved into peer_forward.rs. The CLI helpers are in cli/run/launch.rs, which also prints the existing admission warning, so standard.rs shrinks too. hub/lifecycle.rs and hub/adoption.rs are untouched, so this doesn't overlap #27903.

Tests

Red first: 8327cd8f15e adds tests/hub_integration/successor_harness.rs alone. The fix commit follows it.

  • a_bare_default_successor_of_a_named_profile_is_refused: covers area + role tags, an area-only name, NAME-2, and succeeds=. Then -m fake starts on purpose.
  • a_successor_on_another_harness_is_warned_with_the_flag_that_keeps_it: a one-line warning naming the harness, the autocompact change and --harness-from. No warning when the harness is the same.
  • harness_from_copies_the_predecessors_harness: the successor gets fake-sr2. A -m alongside it is refused, and so is an unknown session.
  • a_session_that_names_no_predecessor_starts_without_a_warning: covers other names, review-27903, another area, and an unnamed start.
  • report_to_names_a_predecessor_only_of_its_own_name_family: a helper that reports to a coordinator running a named profile is not that coordinator's successor; coord-2 --report-to coord is.
  • Unit tests in successor.rs: name families, reading the autocompact cap, an unknown model not counting as a difference, --harness-from copying Ultracode and Codex's reasoning_effort, and the launcher and cap taken from the argv the session last started with.

Command: cargo test -p acpmux --test hub_integration successor_harness (plus cargo test -p acpmux --lib successor).

Observed / Not run

Observed:

  • Red at 8327cd8 (tests only): cargo test -p acpmux --test hub_integration successor_harness gave 3 failed, 1 passed. Refused, warned and --harness-from all fail: the base creates the session where the test expects a refusal, carries no successor field, and starts dep-fleet-2 on fake rather than fake-sr2. The no-predecessor control passes on the base, as it should.
  • Green at c80e957: the same command gave 4 passed with no compiler warnings. cargo test -p acpmux --lib successor gave 3 passed.
  • At 63b882b, after the first review's fixes: successor_harness gave 5 passed, --lib successor gave 5 passed, and cargo clippy -p acpmux --all-targets was clean.
  • Checked locally: prepush-gate.py reports no new violations. The god-file check adds no growth: requests.rs and standard.rs are both shorter than on base.

Not run: the full acpmux suite, and clippy beyond the workspace lints as CI runs them. A real lane-place relaunch on a fleet host was not tried.

Changelog

  • Added: acpmux new and acpmux ensure warn when a session that takes over another runs on a different harness, refuse a bare default start where the predecessor ran a named profile, and take --harness-from SESSION to copy it.

No capture: CLI and daemon only, no UI.

Agent session: agent session

🤖 Generated with Claude Code