Elevator pitch
What are you proposing to change?For v2 of the protocol wire format, I am proposing a change in the lifecycle of the prompt request, allowing for more dynamic session updates from the agent, and unlocking new capabilities in the process. Once a session is created, the agent will be able to send session updates at any point in time, and prompt requests will last until the user message is inserted into the conversation, not until the end of the turn. As I’ll go into later, this not only removes some current awkwardness around the prompt request lifecycle, but also provides a more flexible foundation to add features like queued messages and multi-client replay. This can even allow the agent to initiate an interaction in a session rather than requiring it to wait for a user prompt, which is becoming increasingly important for background tasks and agents which may send updates before or after a “turn” is over since its runtime might be different than the main conversation. This sketch explores returning an agent-generated
messageId in the prompt response. For session/prompt, acceptance means insertion of the user message into the ACP conversation. The same ID identifies that message in live updates and, if retained, later replay, so clients that receive the response can correlate their submissions without introducing a second identifier. Insertion is distinct from processing completion and does not require persistent history.
Status quo
How do things work today and what problems does this cause? Why would we change things?Currently, the protocol kind of assumes that all turns will be initiated by a client and ended by an agent, with a series of session update notifications in-between. While in many cases this is enough, it is becoming clear that this model is not flexible enough. It is not clear how to model queued messages for instance: would these create a new turn request lifecycle? Or fit into the existing one? What if the agent wants to submit some text at the start of a session before the user prompts? Or a status update? Also, if an agent finishes its turn, wants to wait for the next user action, but has a background subagent or task running, can it only submit updates about that status after the user prompts again? When replaying a session, the prompt request can be turned into a user message notification, but what about the end of turn response? If you call load during a currently running session, how do you know that the turn is done? Some clients handle these out-of-turn updates more gracefully than others. But it is a constant point of confusion in discussions and issues. Decoupling the prompt response from session updates also leaves a correlation gap: when the agent reports an accepted user message, how does the submitting client know which pending prompt it belongs to? Matching by content or arrival order is unreliable when users submit identical prompts, multiple clients share a session, or insertion is delayed. The same problem appears after reconnecting if the agent accepted a prompt but the client never received its response. In the spirit of allowing as much flexibility in the protocol for new paradigms and designs to emerge in the prompt lifecycle, I think imposing fewer restrictions in the protocol, whether explicitly described or just implicitly inferred because of vague wording, on when participants can make session updates will allow for more dynamic sessions, as well as make it easier to extend to new use cases in the future.
What we propose to do about it
What are you proposing to improve the situation?
Change the session/prompt response
session/prompt is still a request, but its response lifecycle will change.
For this method, acceptance means insertion: the agent has added the user message to its ACP conversation, with a messageId and a position relative to other messages. Receiving the request, assigning an ID, or holding the input for later insertion is not acceptance.
The agent MUST respond successfully once the user message has been inserted, without waiting for foreground processing to finish. It MUST NOT send a successful response before insertion. The proposed successful response MUST include the inserted message’s messageId as a non-null string; omission and explicit null are both invalid. A request rejected before insertion uses a JSON-RPC error response instead.
Insertion is a logical conversation event, not a durable-storage guarantee or a claim that the model has consumed the input. Locally handled commands follow the same rule by inserting a live-only user message into the ACP conversation, without requiring a persisted transcript entry.
promptId is added to the request or updates.
The response confirms insertion and establishes the submitting client’s association with the message. The corresponding message updates report its content and placement; state_update notifications report foreground work. These are reports of insertion and processing, not separate acceptance and insertion phases.
Correlate prompts with accepted user messages
The JSON-RPC request ID links a response to the client’s submission. The returnedmessageId then links that submission to session updates and replay:
The working assumption is one accepted submission, one logical ACP user message. The agent MUST assign a distinct
messageId to each accepted submission, even when its content is identical to another submission. IDs are unique within the session and MUST NOT be reused for a different submission.
The agent MUST use the returned ID for all updates and chunks for that user message. The response and the user-message update describe the same insertion, but clients MUST tolerate those updates arriving before or after the response. On receiving the response, the submitting client can associate its local pending input with (sessionId, messageId), reconciling with an already-received message rather than creating a second canonical message.
Only the submitting client has the request/response association. Other clients identify the message by messageId without needing the original request. Neither content equality nor notification arrival order should be used to infer which client submitted a message.
An adapter can use an underlying agent’s user-message echo or insertion event as evidence of insertion and respond with the message’s ID while processing continues. It may use a private per-submission token to match that event to the outstanding request; this does not require a new field in ACP. An ID assigned in advance is not by itself evidence of insertion. The returned ID may be native to the underlying agent or adapter-owned, provided updates and replay preserve it consistently.
Additional Agent session/update notification types
Because session/updates can more freely flow from the agent, and we lost the ability to pass end_turn and other information from the prompt response, we need to provide the agent with the affordance for a few more notification types.
User message reported to clients
The prompt response acknowledges insertion. The agent MUST report each inserted user message throughuser_message or user_message_chunk updates, using the same messageId. These updates establish its content and placement in the client-visible conversation, not a guarantee that the agent stored it for later replay.
This RFD does not specify queueing, steering, or whether agents insert new prompts while busy.
The question then turns to what makes up this notification. Which brings us to:
Who owns the user message id?
The Message ID RFD defines that the agent owns message IDs. The client sends the prompt without a message ID, and the agent returns the ID when acknowledging insertion.
The corresponding user-message update reports the agent-owned content and placement under that same ID. The response does not add content to the conversation a second time; it establishes the submitting client’s association with the inserted message.
My current proposal is that this would look like the client sending the following message:
messageId to reconcile its optimistic message with the agent-reported message. If the notification arrives first, the response later establishes that association. Other clients simply render the message under the same ID. The response does not determine the message’s position in the feed.
This is a new message type as well. Not a user_message_chunk but just a user_message that allows for sending the entire message at once. The Message Updates and Chunks RFD defines the corresponding whole-message update and streamed-chunk patterns for user messages, agent messages, and agent thoughts.
Correlation and replay
Live correlation and history retention are separate:- The response and all updates for the accepted user message MUST use the same
messageId. - Agents are NOT REQUIRED to persist every accepted input or retain it for any minimum period. A message may be live-only, or later omitted under the agent’s history-retention policy.
- If an agent retains and replays that same message within the session, it MUST reuse the returned
messageId, including after reconnecting or restarting. This is an identity guarantee for retained messages, not a requirement to retain them.
(sessionId, messageId) association can recognize the message if it is replayed. It requests retained history with session/resume and replayFrom: { "type": "start" }, as described in the Session Resume Replay RFD. Omitting replayFrom resumes without replay.
A lost response remains an ambiguous outcome. The agent may have accepted the input, but the client does not know its ID. Replay can restore retained conversation history, but cannot reliably associate a message with that uncertain submission. Clients must not guess from content or order, or assume retrying cannot create a second submission. A client-generated correlation token could address this limitation for retained messages separately.
A successful response confirms that the message was inserted. Its absence from later replay does not contradict that acknowledgment: the message may have been live-only or may no longer be retained. Imported or non-ACP messages continue to use agent-generated message IDs without a corresponding prompt response.
Streaming user messages
The agent can stream a newly inserted user message usinguser_message_chunk updates with the returned messageId. No metadata-only user_message update is required just to establish correlation, and the chunk schema remains unchanged:
user_message update with content: []. This clears any content the client already holds for that messageId, preventing the replayed chunks from being appended to an earlier copy:
user_message update replaces the retained content directly. A metadata-only update does not clear content and is not sufficient for restarting the message’s content from the beginning.
These rules use the existing message-upsert semantics without an implicit client-side reset. When reconciling an optimistic message, clients should not append streamed content to their local copy of the submitted prompt; the agent’s content stream is authoritative.
state_update notification
This would be a notification from the agent to indicate that it’s current status has changed, such as the “turn” has ended, carrying information like stopReason and usage data for that turn.
Running, to indicate that a turn has begun. Important now that turns aren’t tied necessarily to prompts:
Shiny future
How will things play out once this feature exists?This isn’t a huge schema change, but it is a fundamental behavior change in the protocol that I believe:
- Provides agents with much more flexibility in how they want to update a client about a given session
- Solves some concrete pain points in the current model (i.e. how to integrate prompts into session replay and multi-client replays, message IDs, etc)
- Lets clients that received a prompt response reconcile optimistic submissions with live or replayed user messages without content matching or assuming a particular arrival order
Implementation details and plan
Tell me more about your implementation. What is your detailed implementation plan?Overall, this isn’t a huge lift on schema definition, but it is a large, breaking change in behavior which means we can only stabilize in protocol version 2. The v2 schema and guides define the insertion-receipt response. The implementation checklist is:
- Add a required non-null
messageIdtoPromptResponse, reusing the existingMessageIdtype. LeavePromptRequest,UserMessage,ContentChunk, and v1 unchanged; do not introducePromptId. - Update the prompt lifecycle and session replay guides with insertion as the acknowledgment point, distinct from processing completion; response/update ordering; optional retention; stable IDs when retained messages are replayed; and the content reset when replaying chunks from the beginning.
- Add serialization and schema coverage for valid response IDs and rejection of missing,
null, or non-string IDs. - Exercise an ID assigned before insertion without an early successful response, notifications before and after the response, identical prompts with distinct IDs, submissions from multiple clients, retained associations across replay, imported messages without prompt responses, and both full-content and streamed updates. Include a locally handled command reported live but omitted from later replay, and a retained command replayed with the same ID without re-executing it. Cover the lost-response limitation rather than promising recovery, and verify that streamed replay does not duplicate retained content.
- Update examples and generated artifacts, then run
npm run generateandnpm run check.
- Make this an opt-in “future-flag” capability on v1 so people can experiment, but it would be an unstable feature regardless.
- We establish a preview/beta flow for v2
_meta flag.
Frequently asked questions
What questions have arisen over the course of authoring this document or during subsequent discussions?I’ve hopefully addressed all of the questions and concerns for what motivated this above, but happy to engage with others on this.
Does messageId identify the client or user?
No. It identifies a logical message. The submitting client recognizes its own message through the request/response association, not by parsing the message ID. Sender attribution and authorization remain separate concerns.
Does the returned ID identify a turn or the agent’s response?
No. It identifies the accepted user message. A prompt may contribute to work already in progress, and agents may produce output without a prompt. Agent messages and tool calls retain their own IDs;state_update remains session-scoped.
Does returning messageId make retrying safe?
No. The ID is an acknowledgment, not a client-supplied idempotency key. A retry can be accepted as a distinct submission with a different ID, including after a lost response. This proposal does not promise automatic deduplication or exactly-once processing.
Do locally handled commands need persistent history?
No. Some local control commands complete without the runtime recording the submitted input. The adapter inserts a live-only user message for the command into the ACP conversation, assigns itsmessageId, and reports that insertion through the response and the user-message update. This is the command’s insertion point; it does not require a runtime insertion event that will never arrive or a permanent transcript entry.
The command may be absent from later history replay. If the agent does retain and replay it, it uses the same ID and reports the message without executing the command again. Returning a messageId promises live correlation, not archival storage.
What alternative approaches did you consider, and why did you settle on this one?
Client-generated correlation token
A client-generatedpromptId on the request, preserved with the accepted input and its replayed user message, could identify a submission even if the response is lost. It could also identify pending input before the agent assigns a history-message ID. This is separate from letting the client choose the canonical messageId.
That alternative adds a second identifier, a uniqueness requirement across clients, and a mapping between submissions and history messages. It would still need to define that mapping if several submissions could become one ACP history message, and would not by itself define queue lifecycle or safe retries.
The returned-ID approach is the simpler candidate when one submission remains one logical ACP message and its ID is returned on insertion. A client-generated correlation token remains an option if reliable lost-response recovery or a separate submission lifecycle becomes a requirement; it could be combined with a returned messageId.
Require acknowledgment before insertion
An earlier response would acknowledge receipt or reservation rather than insertion, and could require agents to reserve an ID before their runtime creates the message. This RFD instead keeps the prompt request pending until insertion or rejection. If a future queueing design needs an earlier receipt, that is a separate acknowledgment from the insertion response defined here.Reuse the JSON-RPC request ID
JSON-RPC IDs already associate the response with the submitting request. They are not suitable as persisted message identities: different clients or connections can reuse the same ID, and proxies may rewrite it. Returning the agent-ownedmessageId keeps these responsibilities separate.
Let the client choose the canonical message ID
The Message ID RFD rejected client-generated IDs as canonical message identities because they introduce multiple sources of IDs and require shared collision-avoidance rules. Returning an agent-generated ID preserves agent ownership while still giving the submitting client a correlation handle.Prompt as a notification
Early discussions revolved around having this be a bidirectional stream of notifications on the session. While this felt very symmetrical and appealing, it ran into several problems in practice:- Clients only really had one type of notification that made sense to emit on the session: user messages
- The Agent would still need to replay that message to show where it got accepted within the message history
- We would then need a notification-based way of emitting errors for invalid prompts that would need to be tied to fire-and-forget notifications.