perf(acpmux): stream session summaries into list snapshots
Full session lists retained every expanded JSON summary while constructing the serialized snapshot. Stream one complete summary at a time into serialized row storage, sort compact typed keys, and assemble the same reply. Remote rows are captured as immutable shared handles under their mutex and serialized after releasing it. Replacements and removals keep captured versions alive without deep-copying discarded rows under that mutex.
Closed sessions, tools, unknown fields, stable ordering, paging, peer normalization, authorization, and existing pacing limits retain their behavior. Typed sort keys avoid allocating temporary JSON key objects during collection; paging keys are constructed after sorting. At 1,000 retained sessions, the final same-worker comparison lowers mean post-list RSS by 63.59 MiB (16.6%) and daemon CPU by 21.2%.
Leo: “how much RAM, how many CPU cores are we taking with all these things? Are there ways to make things more efficient?”
Performance
| Metric | Before | After | Delta | Harness |
|---|---|---|---|---|
| N=0: RSS after lists | 62.920 MiB | 63.777 MiB | +0.857 MiB (+1.4%) | daemon_memory |
| N=0: Highest recorded peak RSS | 62.920 MiB | 63.777 MiB | +0.857 MiB (+1.4%) | daemon_memory |
| N=0: CPU, three requests | 10.000 ms | 20.000 ms | +10.000 ms (+100.0%) | daemon_memory |
| N=0: Paced wall, three requests | 0.006 s | 0.007 s | +0.001 s (+25.0%) | daemon_memory |
| N=288: RSS after lists | 180.148 MiB | 146.908 MiB | -33.240 MiB (-18.5%) | daemon_memory |
| N=288: Highest recorded peak RSS | 180.148 MiB | 148.479 MiB | -31.670 MiB (-17.6%) | daemon_memory |
| N=288: CPU, three requests | 240.000 ms | 190.000 ms | -50.000 ms (-20.8%) | daemon_memory |
| N=288: Paced wall, three requests | 1.965 s | 2.244 s | +0.280 s (+14.2%) | daemon_memory |
| N=1000: RSS after lists | 382.361 MiB | 318.771 MiB | -63.590 MiB (-16.6%) | daemon_memory |
| N=1000: Highest recorded peak RSS | 389.398 MiB | 324.008 MiB | -65.391 MiB (-16.8%) | daemon_memory |
| N=1000: CPU, three requests | 685.000 ms | 540.000 ms | -145.000 ms (-21.2%) | daemon_memory |
| N=1000: Paced wall, three requests | 7.399 s | 6.833 s | -0.566 s (-7.6%) | daemon_memory |
| Replay RSS after pages | 91.848 MiB | 93.598 MiB | +1.750 MiB (+1.9%) | daemon_page_memory |
| Replay peak RSS | 106.066 MiB | 107.918 MiB | +1.852 MiB (+1.7%) | daemon_page_memory |
| Replay CPU | 2430.000 ms | 2530.000 ms | +100.000 ms (+4.1%) | daemon_page_memory |
List metrics report arithmetic means of two before and two after passes in a single Linux job, alternating before/after/before/after on one worker. Identical final-head benchmark code runs against baseline 69540503d2a4905a673fa58c8ab3b2c22cdd9521 and final 926608c4640bb26e67685947ac694a7edf31fa0f; the production files and benchmark blob hashes are checked for every pass. Binaries use the repository's test profile. Each N has two-thirds closed sessions and 24 tools per session. Logs are prepared through per-session info before three full-list requests; tags invalidate nonempty snapshots between requests. Peak is the highest recorded VmHWM, including intermediate request samples, reset before each phase with Linux clear_refs=5. CPU has clock-tick resolution. N=0 includes startup activity and can reuse the empty snapshot. These are repeated measurements, not confidence intervals or a macOS/3,000-session claim.
Is the paced wall time user-visible?
Yes. The benchmark calls the real _acpmux/sessions RPC. server/list_reply.rs:52-60 awaits the same guard used by sessions, watch initialization/reconnection, and ACP list. Initial app session lists depend on that snapshot. Home uses mirrored state rather than requesting a full list on every render. A new CLI connection does not itself incur the third-request connection penalty, though it still shares global build/reply pacing.
The three-request total includes RPC waits, tag invalidations, client decoding, validation, and reserialization. The unchanged guard admits two replies per connection, then charges a full reply one window; its window remains build elapsed time × 32, capped at 15 seconds. It does not admit fewer rows per tick or charge extra for serialized rows. Per-request timings separate the first two replies from the paced third reply:
| N=1000 observed samples | Before, pass 1 / pass 2 | After, pass 1 / pass 2 |
|---|---|---|
| Reply 1 (server wait + transport) | 214.3 / 224.2 ms | 187.1 / 191.0 ms |
| Reply 2 (server wait + transport) | 217.9 / 233.6 ms | 179.0 / 171.0 ms |
| Reply 3 (server wait + transport) | 6329.3 / 6448.1 ms | 5635.3 / 6167.8 ms |
| Total paced wall | 7.309 / 7.489 s | 6.576 / 7.091 s |
| RSS after lists | 393.426 / 371.297 MiB | 321.516 / 316.027 MiB |
The earlier 5.97 → 11.38 s sample at the interim implementation was a real regression signal and was not dismissed as benchmark-only time. That run lacked per-request timing, so the exact cause of its ratio cannot be reconstructed. The typed-key follow-up reduces allocations in the measured build path; the final repeated, same-worker comparison above establishes the current result within this harness. Previous comparisons across different workers are superseded and are not used to claim timing improvements. Existing pacing can still delay sustained list traffic by seconds; this change does not relax its limits.
Validation
- Linux run 40785834614: SUCCESS, exact source
926608c4640bb26e67685947ac694a7edf31fa0f(snapshot6dfe5649bad280560cebf386f91c2096344be275). Four alternating list-benchmark passes, before/after page replay, 26 focused unit tests, all four daemon-memory guards, andcargo clippy --locked -p acpmux --all-targets -- -D warningspassed. - The unchanged 400-chat / 7,500-purged-leftover guard passed at 158 MiB, below its unchanged 166 MiB limit. Replay reads 93,816 records / 44 pages from a 40 MiB log; its single before/after sample is included without claiming a replay improvement.
- Independent Codex review: SHIP, no findings, at
926608c4640bb26e67685947ac694a7edf31fa0f. - Primary review checked the final diff against existing ordering, filtering, cursor, guard, and remote-row semantics. The crash-ratchet failure from two newly added
expectcalls was fixed by retaining the original non-panicking serialization handling.
Byte-equivalence coverage includes stable ties, duplicate IDs, escaped/Unicode strings, closed rows, tools, unknown fields, pages, and filters. The remote-row test removes every row inside the visitor, checks Arc identity/write counts, and verifies a complete captured snapshot with the mutex released.
Masked peer and ACP variants still materialize summary trees and remain outside this optimization. No timers were added; existing snapshot expiration and connection-pacing cleanup remain unchanged. The synthetic Linux harness does not establish memory savings on macOS.
Changelog: Reduce temporary daemon allocations while building session lists.
No screenshots: daemon-only change with byte-identical replies.
Agent session: agent session