acpmux: new and ensure keep a predecessor's harness, or say why not
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:
--harness-from P(new flag onnewandensure)- a
succeeds=Ptag --report-to P, or the starting session, when P's name is in the same name family (dep-fleet-2started bydep-fleet)- a
NAME-Nname (N has one or two digits;review-27903is an id, not a generation): the newest other session namedNAMEorNAME-K - an
area=Atag: the newest other session with that area that is live or changed in the last week. The starter and the--report-totarget are never counted as predecessors here.
Then:
- Refuse: there is no
-m,-por--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 Pkeeps P's profile,-m <default>starts on the default on purpose. Reason:successor.harness_dropped. This is tonight's exact case: the deputies'areaandroletags 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 Pcopies 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--hostonnewandensure(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, andsucceeds=. Then-m fakestarts 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 getsfake-sr2. A-malongside 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 coordis.- Unit tests in
successor.rs: name families, reading the autocompact cap, an unknown model not counting as a difference,--harness-fromcopying Ultracode and Codex'sreasoning_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_harnessgave 3 failed, 1 passed. Refused, warned and--harness-fromall fail: the base creates the session where the test expects a refusal, carries nosuccessorfield, and startsdep-fleet-2onfakerather thanfake-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 successorgave 3 passed. - At 63b882b, after the first review's fixes:
successor_harnessgave 5 passed,--lib successorgave 5 passed, andcargo clippy -p acpmux --all-targetswas clean. - Checked locally:
prepush-gate.pyreports no new violations. The god-file check adds no growth:requests.rsandstandard.rsare 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 newandacpmux ensurewarn 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 SESSIONto copy it.
No capture: CLI and daemon only, no UI.
Agent session: agent session
🤖 Generated with Claude Code