Repository navigation
acpmux: a peer chat opens at once on its copy while its peer reconnects (read peer chats ahead) - #28626
Conversation
…le its peer is down Red on base: the daemon keeps a copy only of what its clients read, so the test finds no copy of a chat it never opened. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-299db44aaee03f80b442
… reconnects The daemon's copy of a peer chat (hub/peer_snapshot.rs) held only what a client here read, so a chat nobody opened on this Mac (a lane another agent started on a remote machine) had none: while its machine reconnected it opened on an error until the link came back (Leo, 2026-10-30: "AND IT DID IN FACT LOAD BUT TOOK 30+ FUCKING SECONDS"). A daemon whose clients open peer chats now reads each peer's 24 most recently changed open chats a little after their rows change (2 s settle, at most once per chat each 30 s, its last change always read), the pane's open page at most (400 records, 128 KB), only past what the copy covers once it covers some, and keeps it as a forwarded page. A newest page (`beforeSeq` past every seq, which the pane's read-ahead also asks for) is now kept like an attach's page. Tests: warm_tests.rs (plan, settle, newest page kept); tests/peer_warm_copy.rs (two hubs, relay cut: open-to-content 3 ms on the head, no copy on the base). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-299db44aaee03f80b442
|
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:
|
|
Found 3 test failures on Blacksmith runners: Failures
|
…ge, pass over a refused chat, stop with the daemon Review of #28626 at a2cc1e2 (Astra): - a page asked with both afterSeq and a newest beforeSeq is the newest records after the seq, which need not start right after it: it is not kept (it would claim the seqs it skipped); - new records past a copy that do not fit one page are read on from the newest one the page held, at most 4 pages a round, and the chat is read again after the interval if they do not get there; - a chat is marked read when it is tried, so a refusal waits its interval too; a refusal of the chat alone passes over to the next chat, and only a link that is down ends the round; - the daemon's stop (stop_idle_reaper) aborts every round, starts none, and no round keeps a page after it, before the last flush. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-299db44aaee03f80b442
…d chat, read one page a chat a round Review of #28626 at 599fcb1 (Astra): - the stopped check and the capture happen under the lock Warm::stop takes, and the daemon stops the reads (stop_idle_reaper) before its last flush, so no page is queued after that flush; - a chat the peer refuses (not the link) is read again after the interval; - one page a chat a round: new records past a copy that do not fit it are read after the interval (the plan continues from what the copy covers then), never in a burst. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-299db44aaee03f80b442
…e same session's read Review of #28626 at 112a7bf (Astra): - a copy past the row's lastSeq (a peer back on an older disk with the same session) is read again from the newest page, which replaces it (Store::behind), instead of passing as current; - the 30 s interval is kept per session generation, so a read of the session before a recreation never holds back the first read of the new one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-299db44aaee03f80b442
|
Review (gpt-6-astra, high, fast; independent of this PR's Claude author): SHIP at 5559a91 Rounds: BLOCK at a2cc1e2 → BLOCK at 599fcb1 → BLOCK at 112a7bf → SHIP at 5559a91. All blockers were fixed in the commits after each round (see the PR body's Review section). Remaining P3, non-blocking: no test drives the ahead-copy replacement through |
|
Non-green checks at 5559a91, classified:
Full acpmux suite at this head (run 41154152305): lib 2457 passed, 0 failed; peer_integration 51/51; peer_degraded 1/1; peer_warm_copy 1/1; clippy |
The read-ahead's params were set by Value IndexMut (clippy::indexing_slicing, a deny lint of rust_panic_ratchet.py) and its timing used raw Instant and u64 arithmetic (arithmetic_side_effects). Insert the cursor through as_object_mut, add durations with checked_add, and write the constants without arithmetic. warm.rs now has no ratchet hit; no hit falls on another changed line. Ran: cargo clippy -p acpmux --lib --tests -D warnings (clean); cargo test -p acpmux --lib peer_snapshot (62 passed); --test peer_warm_copy (1 passed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-299db44aaee03f80b442
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
Correction to the reds classification above: the Panic ratchet red at 5559a91 was this PR's own, not base. A per-file ratchet scan found two 099eca7 fixes it: the cursor goes in through |
|
Review (gpt-6-astra, high, fast; independent of this PR's Claude author): SHIP at 099eca7 Round 5 covers 5559a91..099eca7, the panic-ratchet fix: no behaviour change. The params are the same ( |
|
Awareness note from an agent lane that does not own this PR; it is not a review or a request to take the PR over. Reds at 099eca7, by name:
|
|
Review (codex gpt-6-astra high, next-review): SHIP at 5cc98ca SHIP at 5cc98ca Visual verdict: no images; Leo tries the build
Source review completed. Tests could not run because Cargo is unavailable in the box. Follow-ups:
|
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
Review (gpt-6-astra, high, fast; carried): SHIP at 966cda9 Carried from the round-5 SHIP at 099eca7. 966cda9 only merges feat-cmux-next, and the PR's own diff has the same patch-id ( |
A newer verdict blocks 966cda9 (no independent SHIP at the head).
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Summary
Leo, 2026-10-30, opening a chat on a remote machine while the link to it was reconnecting: "AND IT DID IN FACT LOAD BUT TOOK 30+ F***ING SECONDS". The pane showed
peer_unreachable: <machine> (reconnecting, last seen 10s ago: …)until the link came back.The Mac's acpmux already keeps a durable copy of each peer chat (
hub/peer_snapshot.rs, #25323..#25506), and the pane draws it at once under the " is offline. This is the copy this Mac kept… Last updated {time}" banner while the machine does not answer (peerCopy.ts, #25474). But the copy holds only what a client on this Mac read. A chat nobody opened here, such as a lane another agent started on that machine, has no copy, so it opens on the error until the link returns. A chat read hours ago has a copy that is hours old.This PR changes the daemon so it reads peer chats ahead:
afterSeq). It asks for the newest page when there is no copy, the copy is of another session, the copy is more than a page behind, or the copy is past the row (a peer back on an older disk, which the newest page then replaces). New records that don't fit one page are read on after the interval.capture_peer_page), with the same generation, link and ask-stripping rules. A newest page (beforeSeqpast every seq, and noafterSeq) now counts as the chat's own run, like an attach's page. A page asked with both cursors is not kept, since it could skip records. As a side effect, the pane's hover read-ahead (acpmux: a chat opened on screen reads ahead of agent work #28239) also fills the durable copy.Cost and staleness
Instantper planned chat (at most 24 per peer)Why the daemon and not the webview store: the webview's tail cache (
tailCache.ts) lives only in memory and only for chats one page read, so it is gone after an app restart and empty for chats never opened. The daemon copy is durable, already bounded, already drawn by the pane, and already merged with the live page without a flash when the link returns (agent-pane-peer-copy.test.ts: "the machine coming back continues the copy where its page joins it, each record once").What Leo's Mac showed
Coordinator's diagnostics from Leo's Mac, read-only:
~/.acpmux/peer-snapshotsis 63 MB, at the 64 MB bound.With this PR, the chat would have had a copy before the open, and it stays within about 30 s of live while the link is up.
Which part ships where
All changes are in the daemon (
cmux-tui/crates/acpmux). No app or webview code changes: the pane's copy path and banner already exist. Leo's Mac runs daemon build c230bb5, so this reaches it with the next NEXT nightly's daemon update.Not in this PR:
Performance
cargo test -p acpmux --test peer_warm_copy -- --nocapture(two hubs, relay cut), local Linux runTesting
cargo test -p acpmux --lib warm: 9 passed (adds the cut-short continuation, the stop, link-vs-chat refusals, the ahead copy and the per-generation throttle). Covers what a round plans (count, freshness, closed/stale/empty skipped,afterSeqvs newest, another generation), the 30 s interval, settle and one round per peer, a newest page being kept while an olderbeforeSeqpage is not, and no read-ahead on a daemon whose clients open no peer chat.cargo test -p acpmux --lib peer_snapshot: 62 passed.cargo clippy -p acpmux --lib --tests -- -D warnings: clean.cargo test -p acpmux --test peer_warm_copy -- --nocapture: passed, open-to-content 1–3 ms across runs. Setup: B runs two chats and A mirrors B through a relay. A's client opens one chat, the other is read ahead, then the relay is cut. Opening the other chat then refuses the attach aspeer_unreachable, and the copy holds its last reply.Review
Astra (gpt-6-astra, high, fast), five rounds: BLOCK at a2cc1e2 (mixed cursors, cut-short catch-up, failures bypassing the throttle, rounds ignoring shutdown), BLOCK at 599fcb1 (stop/capture race, first-page failure never retried, page bursts), BLOCK at 112a7bf (a copy past its row, throttle not per generation), then SHIP at 5559a91. CI's panic ratchet then found this PR's own
clippy::indexing_slicingin warm.rs (params["afterSeq"] = ...); 099eca7 inserts throughas_object_mutand drops warm.rs's raw arithmetic, and round 5 is SHIP at 099eca7 (no behaviour change). Its one P3 from round 4: no test drives the ahead-copy replacement throughStore::behindend to end (the planner side is tested).Observed / Not run
rust_panic_ratchet.py --flags) finds no hit in warm.rs and none on another changed line.cargo test -p acpmux --test peer_warm_copywith the PR's src reverted fails withno copy of a chat never opened here(the copy reply holds no events).PeerCopyBanner.gallery.ts, agent pane peer snapshots (7): the offline banner and the reply's reading #25474). Not measured on Leo's Mac: that needs this daemon build there.Changelog
Fixed: A chat on another machine opens at once on the Mac's copy while that machine reconnects, even if it was never opened on this Mac.
Agent session: agent session
🤖 Generated with Claude Code