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>
ACP adapter for the Claude Agent SDK
Use Claude Agent SDK from ACP-compatible clients!
This tool implements an ACP agent by using the official Claude Agent SDK, supporting:
- Context @-mentions
- Images
- Tool calls (with permission requests)
- Compact file changes through the negotiated AIR diff patch extension
- Following
- Edit review
- TODO lists
- Nested subagent transcripts
- Interactive (and background) terminals
- Custom Slash commands
- Client MCP servers
/mcpin the chat: the MCP server status as a list. The adapter runs/mcp reconnect,/mcp enable, and/mcp disablethrough the SDK control API, because Claude Code refuses them in SDK mode. A reconnect of an ACP server that needs authentication starts MCP OAuth through URL elicitation- Session-scoped long-running goals for AIR through the goal extension under
_meta.jetbrains.air.goal - Structured errors, recovery, and warnings through the opt-in session failure extension
- Concrete model and effort defaults through the opt-in recommended config value extension
- Tool permission presentation, editable choices, and durable effects through the permission extension
- One fact per field in every tool call report, as the ACP tool call contract defines
- All AIR extensions, capabilities, and
_metakeys: AIR extensions
Learn more about the Agent Client Protocol.
To try changes that have landed on main but are not released yet, install from the
preview channel — every push to main publishes one. See
docs/RELEASES.md.
npm install @agentclientprotocol/claude-agent-acp@preview
Subagent sessions
Subagents are exposed only after bilateral capability negotiation. Until the released ACP SDKs
preserve the draft clientCapabilities.subagents field, a supporting client may advertise
nativeSubagentSessions in _meta.jetbrains.air.capabilities; the adapter mirrors the capability
in its initialize response. The canonical field remains supported and takes precedence once it is
available. Without either client signal, Agent/Task lifecycle keeps its legacy ordinary ACP
tool-call representation and child interactions stay on the root session. Clients that use the
historical _meta["subagent-transcript"] capability or forwardSubagentText session option retain
the flattened child transcript behavior. See AIR extensions.
Contribution Policy
This project does not require a Contributor License Agreement (CLA). Instead, contributions are accepted under the following terms:
By contributing to this project, you agree that your contributions will be licensed under the Apache License, Version 2.0. You affirm that you have the legal right to submit your work, that you are not including code you do not have rights to, and that you understand contributions are made without requiring a Contributor License Agreement (CLA).