mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-10-28 13:01:20 +00:00
Two findings of the 2026-10-10 night perf run on a 5,000-session home (findings 8 and 9). ## Rename push (finding 9) The run measured "no push within 10 s" after `_session/rename`. The cause was the harness, not the adapter. The bench renames the newest session to `"Night renamed"`, and an earlier bench run on the same home had already given it that title. The transcript shows that pair of title records twice. A rename to the current title appends nothing, so there was nothing to push. On a fresh home, the same bench gets the push. The hypotheses checked: - The metadata cache is not updated by a rename. The appended records change the transcript's size, and the watcher re-reads it. - The sidecar lies outside the non-recursive watch, but so does every rename. Each one also appends to the transcript, which is watched. Still, the push depended on `fs.watch` plus its 150 ms quiet period. Now a rename, archive, unarchive or delete through this connection publishes at once. `ListSubscriptions.sessionWritten` stats the session's transcripts and reads its row, the way a transcript event would, without waiting. The event that follows finds the row unchanged and sends nothing. Renames by other processes still arrive through the watcher. The 1 s per-session interval of a subscription still applies. ## Listing every page (finding 8) Profiling `SessionIndex.list` showed where a page's time goes. The full enumeration and stat of 5,000 transcripts takes about 20 ms per page. Most of the remaining ~380 ms was the SDK `getSessionInfo`, called once per row read: its lookup runs a `readdir` of the whole project directory (5,000 entries) on every call. - A metadata read no longer calls the SDK. Titles come from the head and tail the index already reads, by the same rule (`sdkTitles`). The tail is searched as the SDK searches it, cut first line included. - The pages after a cursor reuse the list's enumeration. It is keyed by the cursor and kept for at most 30 s, at most 8 lists. It is dropped as soon as a directory of the scope adds or removes an entry, through the `DirListings` identity check. Such a page stats only the transcripts it reads (each with its session's other copies) right before reading them. A transcript that changed since either keeps its order key (no new prompt) or moves up before the cursor, so the page is as fresh as one from a new enumeration. The first page always enumerates. ## Measurements Real adapter (`dist/index.js`), driven as AIR drives it by `acp-perf-harness/air-ui/night/acp-adapter-bench.mjs`. Fixture: 5,000 sessions from `gen-night-data.mjs`. | | before | after | |---|---|---| | Listing every page (25 × 200) | 10.1–16.3 s | 0.92–1.16 s | | First page (51) | 156–472 ms | 46–62 ms | | Show more (50) | 117–283 ms | 13–23 ms | | Push after archive | ~170 ms | 3–4 ms | | Push after a rename | ~170 ms | 3–5 ms | ## Tests - A rename, archive, unarchive and delete each push within 500 ms while the watcher's quiet period is 60 s (fails without the fix). A rename by another process is pushed through the watcher. - Paging: - one enumeration for all pages; - between pages, a transcript emptied, one with a new prompt, and one with only a new answer give the same pages as a new enumeration; - an older copy of a session that got a prompt hides it on later pages; - adding or removing a transcript enumerates again. - A custom title whose record starts before the tail window matches the SDK. - Read-count assertions use the new `SessionIndex.transcriptsRead` counter instead of SDK call counts. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>