diff --git a/docs/rfds/agent_message_clear.mdx b/docs/rfds/agent_message_clear.mdx new file mode 100644 index 00000000..0837765d --- /dev/null +++ b/docs/rfds/agent_message_clear.mdx @@ -0,0 +1,118 @@ +--- +title: "Agent Message Clear Mechanism for Non-Monotonic Streaming" +--- + +Author(s): [steve02081504](https://github.com/steve02081504) + +## Elevator pitch + +The current Agent Communication Protocol (ACP) treats `agent_message_chunk` as a strictly append-only operation. This proposal introduces a new session update type, `agent_message_clear`, which allows agents to clear the accumulated streamed content. This enables support for non-monotonic streaming scenarios—such as iterative refinement, post-processing, and speculative decoding—without causing UI clutter or redundant message history. + +## Status quo + +Currently, the `agent_message_chunk` session update is strictly cumulative. The client concatenates all received chunks into the agent's message with no mechanism to clear or replace what has already been streamed. This is problematic for agents where content generation is non-monotonic: + +1. **Post-processing / reformatting**: Backends applying progressive Markdown rendering or XML tag balancing may need to reformat intermediate text. +2. **Delegating agent frameworks**: Frameworks like [fount](https://github.com/steve02081504/fount) receive full accumulated states rather than deltas. When internal processing occurs (e.g., hiding tool-calls), the new state is no longer a simple prefix of the old one. +3. **Iterative Refinement & Diffusion-based Models**: These models refine entire text blocks over multiple steps rather than generating tokens linearly. +4. **Speculative decoding with rollback**: Inference engines may need to retract tokens if a drafted branch is rejected. +5. **Display vs. Context Divergence**: An agent might stream a simplified preview and wish to replace it with a final richly-formatted version. + +**Current Workarounds:** +* **Separator & Re-push**: Agents append a visual separator (like `---`) and re-send the entire text. This causes **UI clutter**, **flicker**, and **massive context waste** in transcripts. +* **Non Stream**: Agents stop streaming until the full text done. This makes the UI feel "frozen" or laggy. + +## What we propose to do about it + +We propose adding a new session update type: `agent_message_clear`. + +This update instructs the client to immediately clear the accumulated streamed content for the **current** agent message in the given session. Subsequent `agent_message_chunk` updates will then start appending from an empty state. (The ACP spec already supports multiple sessions on the same connection via repeated `session/new`; all session-scoped notifications and methods carry a `sessionId`, so "current" is unambiguous per session.) + +**Key characteristics:** +- **Simplicity**: It follows the existing "full-replacement" semantics found in `plan` updates and `tool_call_update` content. +- **Minimal Scope**: It introduces a single new `SessionUpdate` variant without changing existing logic. +- **Graceful Degradation**: Clients that do not recognize this update will ignore it, defaulting to the current (concatenated) behavior. + +## Shiny future + +In the shiny future, agent UIs will be fluid and reactive. +- **Clean Recovery**: If an agent makes a "mistake" in its stream or needs to reformat a table on the fly, it can do so invisibly to the user's permanent history. +- **Next-Gen Support**: ACP will natively support diffusion-style language models that "paint" or "de-noise" a response over time. +- **Framework Harmony**: Developers using high-level frameworks (like `fount`) can push full-state updates safely, knowing the protocol handles the "reset" efficiently. +- **Optimized Context**: Chat transcripts will remain concise and readable, free of "Separator & Re-push" artifacts. + +## Implementation details and plan + +### Protocol Change +Add `agent_message_clear` to the `SessionUpdate` variant list. When [message IDs](message-id.mdx) are in use (unstable today), the update may include an optional `messageId` to clear a specific message; otherwise it clears the current message in the session. + +**JSON-RPC Example (clear current message):** +```json +{ + "jsonrpc": "2.0", + "method": "session/update", + "params": { + "sessionId": "sess_abc123", + "update": { + "sessionUpdate": "agent_message_clear" + } + } +} +``` + +**With optional `messageId` (when message ID is supported):** +```json +{ + "jsonrpc": "2.0", + "method": "session/update", + "params": { + "sessionId": "sess_abc123", + "update": { + "sessionUpdate": "agent_message_clear", + "messageId": "msg_uuid_here" + } + } +} +``` + +### Usage Pattern +1. Agent sends `agent_message_chunk` with text "Drafting...". +2. Agent determines the draft needs replacement. +3. Agent sends `agent_message_clear`. +4. Agent sends `agent_message_chunk` with the final, polished content. + +### Implementation Plan +1. **Phase 1**: Update the ACP schema to include `agent_message_clear`. +2. **Phase 2**: Implement handling in reference clients to clear the local buffer for the active message. +3. **Phase 3 (Optional)**: Consider adding `agent_thought_clear` if agents require similar non-monotonic behavior for internal reasoning blocks. + +## Frequently asked questions + +### What alternative approaches did you consider, and why did you settle on this one? + +Several designs were evaluated: + +| Design | Reason for Rejection | +|---|---| +| **`agent_message_replace`** | While atomic, it requires a new content structure and results in very large payloads for long messages if sent frequently. | +| **`replace: true` flag** | Overloads the existing chunk type and makes partial-replace semantics ambiguous. | +| **Offset-based patching** | While bandwidth-efficient, it introduces significant complexity for both agent developers and client implementers (similar to Operational Transforms). | + +`agent_message_clear` was chosen because it is the most consistent with existing ACP patterns (like `plan` updates) and provides the best balance between implementation simplicity and functional power. + +### How does this affect backward compatibility? +It is fully backward-compatible. A legacy client will simply ignore the unknown `agent_message_clear` notification. The result is that the user sees the old text and new text concatenated, which is identical to the current "Separator" workaround. + +### Does this replace the entire message history? +No. It only clears the "accumulated streamed content" of the **current** active agent message in the session. It does not affect previously finalized messages in the chat history. + +### What about message IDs and "replace by id"? + +The spec already has **message IDs** (currently unstable): `agent_message_chunk` and related updates can carry an optional `messageId`; see the [Message ID](message-id.mdx) RFD and `schema.unstable.json`. Review feedback suggested building on this so we know *which* message to clear, and an alternative design: allow **`agent_message`** (full message) in addition to `agent_message_chunk`, with an `id`—when the client sees a full message with an existing id, it would replace all previous chunks/text for that id, similar to tool call updates. + +This RFD should align with that existing mechanism: `agent_message_clear` can take an optional `messageId`; when present, the client clears the accumulated content for that message id; when absent, it clears the "current" message (same as today's implicit semantics). That way we build on top of the new message ids as suggested. The "full `agent_message` with id for replace" design remains a viable alternative or future extension. + +## Revision history + +- **2026-02-07**: Initial draft by steve02081504. +- **2026-03-15**: Updated per review: multi-session clarification; build on existing (unstable) message IDs; optional `messageId` on `agent_message_clear`; replace-by-id alternative noted. diff --git a/schema/schema.json b/schema/schema.json index 9aca01f4..96c8886b 100644 --- a/schema/schema.json +++ b/schema/schema.json @@ -2714,6 +2714,17 @@ "required": ["sessionUpdate"], "type": "object" }, + { + "description": "Instructs the client to clear the accumulated streamed content for the current agent message. Subsequent agent_message_chunk updates append from empty. Enables non-monotonic streaming (e.g. reformatting, delegation, iterative refinement).", + "properties": { + "sessionUpdate": { + "const": "agent_message_clear", + "type": "string" + } + }, + "required": ["sessionUpdate"], + "type": "object" + }, { "allOf": [ { diff --git a/src/client.rs b/src/client.rs index c22c82fa..a9828e6a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -76,6 +76,11 @@ pub enum SessionUpdate { UserMessageChunk(ContentChunk), /// A chunk of the agent's response being streamed. AgentMessageChunk(ContentChunk), + /// Instructs the client to clear the accumulated streamed content for the current agent message. + /// Subsequent `agent_message_chunk` updates append from empty. Enables non-monotonic streaming + /// (e.g. reformatting, delegation, iterative refinement). + /// See protocol docs: [RFD: agent_message_clear](https://github.com/agentclientprotocol/agent-client-protocol/blob/main/docs/rfds/agent_message_clear.md) + AgentMessageClear, /// A chunk of the agent's internal reasoning being streamed. AgentThoughtChunk(ContentChunk), /// Notification that a new tool call has been initiated.