## Summary Session titles showed up only after the first turn ended. With background subagents, that could be minutes or hours later. Now the adapter asks for the title when the prompt is sent to the CLI. In a live run the title arrived **0.8s** after the prompt, compared with **17.9s** (at turn end) on `main`. ## Why - **The CLI already titles SDK sessions.** The bundled CLI (2.1.303) titles a new session from its first prompt within seconds and stores it as an `ai-title` transcript entry. `getSessionInfo` returns it as `customTitle`. The header comment in `session-titles.ts` said the CLI never titles SDK sessions; that is out of date. The CLI's off switch only applies to the `sdk-cli` entrypoint without `CLAUDE_AGENT_SDK_VERSION`, and the TS SDK sets `sdk-ts` and that variable. - **No message carries an AI title.** The CLI's internal `system/session_title_changed` covers only `/rename`-style names, and its schema says so. - **The adapter only checked at `idle`.** `SessionTitles.onTurnEnd` ran at `session_state_changed: idle`, which comes after the whole turn. Since CLI 2.1.270 it also waits for background subagents to finish. In the common case, `onTurnEnd` just picked up the title that had been on disk for the whole turn. ## Change - **New `SessionTitles.onPromptQueued`**, called right after the prompt is pushed to the SDK. Until the session has a title, it: 1. reads the session file, and publishes a stored title if there is one (a resumed session, or a `/rename`); 2. otherwise sends `generate_session_title` with the prompt text only, in the background, and publishes the result. - **No second model call.** The CLI runs this request alongside the turn instead of queuing it behind it. Its handler returns the stored custom title, then the AI title, and otherwise waits for the CLI's own title call already in flight. - **One try per prompt, like the CLI.** After a prompt-time request, the turn end does not ask again. If nothing came back, it publishes the stored summary as before, and the next prompt tries again. - **Unchanged:** - the turn end still titles a prompt too short to title from (under 10 characters, like "hi") from the prompt plus the reply; - the turn end still picks up a later `/rename`; - a title the client set still wins. ## Verification - `npm run check` and `npm run test:run` pass (3275 passed, 30 skipped). - New tests: - the title is published while the first turn is still running; - the raw prompt is not published at turn end while a title is still pending; - a short "hi" prompt is titled at turn end from the prompt and the reply. - The latch-release test now pins one request per prompt. Removing the new call site makes two of the tests fail. - Live check: one real text-only turn against this branch and against `main`, both on the bundled CLI 2.1.303, timing `session_info_update` against the prompt response: | build | title published | prompt response | | --- | --- | --- | | this branch | 0.8s | 17.1s | | `main` | 17.9s | 17.9s | Both runs got the same title, and both transcripts contain the same two `ai-title` entries. So the change adds no title generation; the CLI itself writes the entry twice. ## Risks - **Undocumented CLI behaviour.** This depends on the CLI answering `generate_session_title` mid-turn and returning the title it is already generating, which `sdk.d.ts` does not document. If a future CLI changes that, the worst case is one extra small-model call per session. The latch still limits it to one success. - **Titles now arrive mid-turn.** Clients that expected `session_info_update` only between turns should be checked. - **Steering is not covered.** Messages injected with `_session/steering` don't trigger a title request.
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
A client that declares clientCapabilities.subagents, other than JetBrains AIR, gets each Agent or
Task subagent as its own child session, following the subagents RFD:
subagent_updateon the parent announces the child before any of its traffic, with the parent'stitleanddescriptionfor it, and reports its work state:running,requires_actionwhile one of its requests is open (andrunningagain when the last one ends, unless a cancel withdrew it: the child is stopping then), andidlewhen its work ends, withend_turnorcancelledwhen the SDK gives the reason. A failure isidlewith theerrorstop reason and the SDK's error text as itserror. The child keeps its session for every delegation to it.- The prompt of the Agent or Task call that launches the child, and the message of each
SendMessage call that resumes it, is a
session_messagein the child's transcript. The tool call stays a tool call of the session that made it. session/cancelon the parent interrupts Claude Code, which stops the foreground children that the turn waits on. Background children keep running, as the RFD prefers; a client stops one with its owncancel. Each stopped child reportsidlewithcancelledfrom Claude Code's own events, and its open requests are withdrawn. The adapter ends no child at a cancel itself, so a child that keeps running keeps its open requests and its open tool calls. To spare background children, the adapter declares the SDK optionperTaskStopAffordance, which a client can turn off in_meta.claudeCode.options. That option spares background workflows too, which a client cannot stop, so the adapter stops them after the interrupt, as Claude Code did without it. Background commands keep running after a cancel either way. A tool call of a stopped child that Claude Code reports no result for, such as the command it was running, ends in the child's session before the cancelled prompt resolves. When Claude Code stops a background child after that, or while no prompt runs, the tool call ends right after the child reportsidle. A child that still runs when Claude Code exits isidlewith no stop reason.- A child whose parent is not yet known when it ends is not exposed, since the RFD forbids guessing its parent.
- A replay restores each child under its live session id, from its own transcript: its prompt and
each SendMessage message are
session_messages, and its end is anidlebefore the response. A launch without Claude Code's metadata file for the child stays an ordinary tool call of the parent, and a notice, for a client that takes notices, says that subagent history is missing. - After a load or resume, with or without replay, a SendMessage that resumes an agent from before it runs that agent in its session from before. Before the agent's traffic, each of its ancestors that the client was not told about in this connection is announced, parent before child and with no work state, as the RFD requires. An agent whose launch the session's history does not record stays unexposed.
- Each running child is announced with the
cancelcapability.session/cancelwith the child's session id stops that child's current work, through the SDK'sQuery.stopTask, and not its parent's: Claude Code also stops the foreground children it waits on, and the parent's turn goes on. Each stopped child reportsidlewithcancelledfrom Claude Code's own events, and its requests that the stop withdraws report norunningfirst. A replayed child, which no longer runs, gets no capabilities. A client cannot prompt or otherwise change a child.
An ACP v2 client gets the same without declaring anything, since subagents
are part of v2's baseline. Each child also reports its work state as its own state_update, right
after the parent's copy.
JetBrains AIR keeps the earlier draft of the RFD (subagent_spawned, subagent_state_update),
which it enables with nativeSubagentSessions in _meta.jetbrains.air.capabilities. See
AIR extensions.
Without either signal, Agent and Task stay ordinary tool calls and child interactions stay on the
root session. Clients that use the historical _meta["subagent-transcript"] capability or the
forwardSubagentText session option retain the flattened child transcript.
Experimental ACP v2
The adapter also serves the draft ACP v2:
a client that asks for protocol version 2 or later in initialize gets v2, and every other client
gets v1. The draft can still change, and this surface changes with it.
CLAUDE_AGENT_ACP_PROTOCOL limits the adapter to one version. With v1, a client that asks
for v2 is negotiated down to v1. With v2, the adapter rejects the initialize of a v1 client.
auto, the default, serves both.
On v2 the adapter serves the session baseline (session/new, session/list, session/resume
with replay, session/close, session/prompt, session/cancel), session/delete,
session/fork, additional directories, config options (the mode is one), plans, notices,
compaction, display terminals for commands, permission requests, elicitation, providers, auth, and
subagents. Steering and the AIR extensions are v1 only for now.
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).