Repository navigation
acpmux: an adopted host's running subagents keep their tags - #30614
Conversation
… base) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
…ts replay After host_adopted the hub's SubagentTree started empty, so a running subagent's lines reached clients untagged (as the chat's own) until its next progress event. adopt_held now seeds the tree from the log (which subagents the chat started, and which still run) before the tap sees the replay. The head predicate is shared with open_work_reads; the tree's background tests move to their own file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-63b9964940c20354b639
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
Shipped in:
|
There was a problem hiding this comment.
5 issues found across 5 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="cmux-tui/crates/acpmux/src/subagents.rs">
<violation number="1" location="cmux-tui/crates/acpmux/src/subagents.rs:226">
P2: Adopting a root known by the durable log does not repair a live tree's pre-root phantom state. Apply each adopted root through `remember_root` so its stale `parents`, `background`, and `running` entries are removed before subsequent updates are classified.</violation>
</file>
<file name="cmux-tui/crates/acpmux/src/hub/work_below.rs">
<violation number="1" location="cmux-tui/crates/acpmux/src/hub/work_below.rs:126">
P2: This discards log-scan failures and lets adoption continue with an incomplete subagent tree, so the first post-restart child update can be tagged as the chat and the summary can undercount running subagents. Propagate the scan error or otherwise refuse/delay adoption until reconstruction succeeds.</violation>
<violation number="2" location="cmux-tui/crates/acpmux/src/hub/work_below.rs:130">
P2: The seed leaves foreground subagents running after a logged `turn_result` until another turn starts. Handle `turn_result` here as a foreground boundary, so canceled or otherwise unclosed foreground agents do not remain in `subagentsRunning` after restart.</violation>
</file>
<file name="cmux-tui/crates/acpmux/src/subagents_background_tests.rs">
<violation number="1" location="cmux-tui/crates/acpmux/src/subagents_background_tests.rs:47">
P3: This test never exercises a nested subagent: `annotate` always uses `sessionId: "s1"`, so every spawn is a root child. Add a child update under `a` and assert `running_count` and `running_now` include it; otherwise nested-count regressions can pass.</violation>
</file>
<file name="cmux-tui/crates/acpmux/src/hub/hosts.rs">
<violation number="1" location="cmux-tui/crates/acpmux/src/hub/hosts.rs:487">
P2: `seed_subagents_from_log` runs before adoption proves that the host is compatible. An incompatible or failed attach therefore leaves subagents from the old log permanently reported as running even though this daemon will not receive their updates; move seeding into the `Attached::Ready` path immediately before replay delivery.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
| self.running.push((id, label)); | ||
| } | ||
| } | ||
| self.root_threads.extend(log.root_threads); |
There was a problem hiding this comment.
P2: Adopting a root known by the durable log does not repair a live tree's pre-root phantom state. Apply each adopted root through remember_root so its stale parents, background, and running entries are removed before subsequent updates are classified.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/acpmux/src/subagents.rs, line 226:
<comment>Adopting a root known by the durable log does not repair a live tree's pre-root phantom state. Apply each adopted root through `remember_root` so its stale `parents`, `background`, and `running` entries are removed before subsequent updates are classified.</comment>
<file context>
@@ -203,6 +203,29 @@ impl SubagentTree {
+ self.running.push((id, label));
+ }
+ }
+ self.root_threads.extend(log.root_threads);
+ }
+
</file context>
| self.root_threads.extend(log.root_threads); | |
| for root in log.root_threads { | |
| self.remember_root(&root); | |
| } |
| tree.set_roots( | ||
| meta.agent_session_id.iter().chain(&meta.retired_agent_session_ids).map(String::as_str), | ||
| ); | ||
| let _ = self.store.scan_heads(&session.id, &mut |head| { |
There was a problem hiding this comment.
P2: This discards log-scan failures and lets adoption continue with an incomplete subagent tree, so the first post-restart child update can be tagged as the chat and the summary can undercount running subagents. Propagate the scan error or otherwise refuse/delay adoption until reconstruction succeeds.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/acpmux/src/hub/work_below.rs, line 126:
<comment>This discards log-scan failures and lets adoption continue with an incomplete subagent tree, so the first post-restart child update can be tagged as the chat and the summary can undercount running subagents. Propagate the scan error or otherwise refuse/delay adoption until reconstruction succeeds.</comment>
<file context>
@@ -109,6 +109,57 @@ impl Hub {
+ tree.set_roots(
+ meta.agent_session_id.iter().chain(&meta.retired_agent_session_ids).map(String::as_str),
+ );
+ let _ = self.store.scan_heads(&session.id, &mut |head| {
+ if head.dir == "mux" {
+ match head.kind {
</file context>
| let resume_after = self.last_host_seq(session, &record.incarnation); | ||
| // The subagents the host still runs, before its first replayed line reaches the tap: | ||
| // a child's line is tagged with its session from the first one (`work_below.rs`). | ||
| self.seed_subagents_from_log(session); |
There was a problem hiding this comment.
P2: seed_subagents_from_log runs before adoption proves that the host is compatible. An incompatible or failed attach therefore leaves subagents from the old log permanently reported as running even though this daemon will not receive their updates; move seeding into the Attached::Ready path immediately before replay delivery.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/acpmux/src/hub/hosts.rs, line 487:
<comment>`seed_subagents_from_log` runs before adoption proves that the host is compatible. An incompatible or failed attach therefore leaves subagents from the old log permanently reported as running even though this daemon will not receive their updates; move seeding into the `Attached::Ready` path immediately before replay delivery.</comment>
<file context>
@@ -482,6 +482,9 @@ impl Hub {
let resume_after = self.last_host_seq(session, &record.incarnation);
+ // The subagents the host still runs, before its first replayed line reaches the tap:
+ // a child's line is tagged with its session from the first one (`work_below.rs`).
+ self.seed_subagents_from_log(session);
let tap = self.session_tap(session);
let incarnation = record.incarnation.clone();
</file context>
| if head.dir == "mux" { | ||
| match head.kind { | ||
| "host_started" | "stopped" | "exited" => tree.end_background(), | ||
| "turn_started" => tree.end_foreground(), |
There was a problem hiding this comment.
P2: The seed leaves foreground subagents running after a logged turn_result until another turn starts. Handle turn_result here as a foreground boundary, so canceled or otherwise unclosed foreground agents do not remain in subagentsRunning after restart.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/acpmux/src/hub/work_below.rs, line 130:
<comment>The seed leaves foreground subagents running after a logged `turn_result` until another turn starts. Handle `turn_result` here as a foreground boundary, so canceled or otherwise unclosed foreground agents do not remain in `subagentsRunning` after restart.</comment>
<file context>
@@ -109,6 +109,57 @@ impl Hub {
+ if head.dir == "mux" {
+ match head.kind {
+ "host_started" | "stopped" | "exited" => tree.end_background(),
+ "turn_started" => tree.end_foreground(),
+ _ => {}
+ }
</file context>
| "turn_started" => tree.end_foreground(), | |
| "turn_started" | "turn_result" => tree.end_foreground(), |
|
|
||
| /// The hover card's live count (cmux NEXT sidebar, Leo 2026-11-01: "this hover preview being | ||
| /// able to see the # of associated subagents"): every running subagent, foreground or | ||
| /// background, nested ones too, with its name; a replay counts none, and without a running |
There was a problem hiding this comment.
P3: This test never exercises a nested subagent: annotate always uses sessionId: "s1", so every spawn is a root child. Add a child update under a and assert running_count and running_now include it; otherwise nested-count regressions can pass.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At cmux-tui/crates/acpmux/src/subagents_background_tests.rs, line 47:
<comment>This test never exercises a nested subagent: `annotate` always uses `sessionId: "s1"`, so every spawn is a root child. Add a child update under `a` and assert `running_count` and `running_now` include it; otherwise nested-count regressions can pass.</comment>
<file context>
@@ -0,0 +1,123 @@
+
+/// The hover card's live count (cmux NEXT sidebar, Leo 2026-11-01: "this hover preview being
+/// able to see the # of associated subagents"): every running subagent, foreground or
+/// background, nested ones too, with its name; a replay counts none, and without a running
+/// turn only the background ones still run.
+#[test]
</file context>
|
Review (codex, review-ux-adopt-subagents): SHIP at 5fdcf7b |
|
Test results at 5fdcf7b:
|
Summary
After a daemon restart, the new daemon adopts the running agent host (
host_adopted). The hub's subagent tree (SubagentTree) started empty, so it no longer knew which subagents the chat was running. Until each subagent's next progress event, that subagent's lines reached clients without a tag (_meta.acpmux.subagent), so they read as the chat's own. The web pane then showed a helper's grep as the chat's own call. Leo saw this after a finished turn: "why does it say you're still running grep". #30416 (cf818b1) made the pane attribute updates bysessionId. This PR fixes the cause on the hub side, so the hub keeps the list.adopt_held(hub/hosts.rs) now callsseed_subagents_from_log(hub/work_below.rs) before the host's replay reaches the tap. It rebuilds two things from the durable log, the same way the tap followed them live:subagentsRunningandsubagentNamesand the work-below state are rightprocesses.rsread_processesrebuilds the same way.SubagentTree::adoptmerges the rebuilt tree into the live one. A subagent the live tree already knows keeps its own state.open_work_reads(names_subagents).SubagentTree's background tests move tosubagents_background_tests.rs, so the god-file-cappedsubagents.rsandhosts.rsshrink instead of growing.Builds on #30439, which added the running-subagent counts that are now rebuilt too.
Test
tests/durable_agents.rsa_subagent_running_across_a_restart_keeps_its_tag_after_adoption. A turn starts subagentchild-1and its tool call. The daemon is killed and restarted, then adopts the host. The test checks that the summary still counts the subagent, then that the child's next line (tool_call_update cg-1) is taggedchild-1.Command:
cargo test -p acpmux --test durable_agents a_subagent_running_across_a_restart_keeps_its_tag_after_adoptionObserved / Not run
Observed: rustfmt, prepush-gate, and the post-edit god-file check (no growth). The test results are in the comments.
Not run: the full acpmux suite locally (it runs through cmux-ci).
Changelog
Fixed: after an acpmux restart, a running subagent's activity no longer shows as the chat's own.
Agent session: agent session
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.Summary by cubic
Fixes running subagents losing their tags after a daemon restart adopts their host. The adopting daemon's subagent tree started empty, so until each subagent's next progress event its lines reached clients untagged and read as the chat's own activity.
Bug Fixes
adopt_heldnow rebuilds the subagent tree from the durable log before the host's replay reaches the tap, restoring which subagents the chat started and which still run.SubagentTree::adoptto merge the rebuilt tree into the live one, keeping live state for subagents the tree already knows.names_subagentsis now shared between replay seeding andopen_work_reads.SubagentTree's background tests tosubagents_background_tests.rsso the file-size-cappedsubagents.rsandhosts.rsdon't grow.Written for commit 5fdcf7b. Summary will update on new commits.