|
| 1 | +--- |
| 2 | +title: "Message ID" |
| 3 | +--- |
| 4 | + |
| 5 | +Author(s): [@michelTho](https://github.com/michelTho), [@nemtecl](https://github.com/nemtecl) |
| 6 | + |
| 7 | +## Elevator pitch |
| 8 | + |
| 9 | +Add a `messageId` field to `agent_message_chunk` and `user_message_chunk` session updates, and to `session/prompt` responses, to uniquely identify individual messages within a conversation. This enables clients to distinguish between different messages beyond changes in update type and lays the groundwork for future capabilities like message editing. |
| 10 | + |
| 11 | +## Status quo |
| 12 | + |
| 13 | +Currently, when an Agent sends message chunks via `session/update` notifications, there is no explicit identifier for the message being streamed: |
| 14 | + |
| 15 | +```json |
| 16 | +{ |
| 17 | + "jsonrpc": "2.0", |
| 18 | + "method": "session/update", |
| 19 | + "params": { |
| 20 | + "sessionId": "sess_abc123def456", |
| 21 | + "update": { |
| 22 | + "sessionUpdate": "agent_message_chunk", |
| 23 | + "content": { |
| 24 | + "type": "text", |
| 25 | + "text": "Let me analyze your code..." |
| 26 | + } |
| 27 | + } |
| 28 | + } |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +This creates several limitations: |
| 33 | + |
| 34 | +1. **Ambiguous message boundaries** - When the Agent sends multiple messages in sequence (e.g., alternating between agent and user messages, or multiple agent messages), Clients can only infer message boundaries by detecting a change in the `sessionUpdate` type. If an Agent sends consecutive messages of the same type, Clients cannot distinguish where one message ends and another begins. |
| 35 | + |
| 36 | +2. **Non-standard workarounds** - Currently, implementations rely on the `_meta` field to work around this limitation. While functional, this approach is not standardized and each implementation may use different conventions. |
| 37 | + |
| 38 | +3. **Limited future capabilities** - Without stable message identifiers, it's difficult to build features like: |
| 39 | + - Message editing or updates |
| 40 | + - Message-specific metadata or annotations |
| 41 | + - Message threading or references |
| 42 | + - Undo/redo functionality |
| 43 | + |
| 44 | +As an example, consider this sequence where a Client cannot reliably determine message boundaries: |
| 45 | + |
| 46 | +```json |
| 47 | +// First agent message chunk |
| 48 | +{ "sessionUpdate": "agent_message_chunk", "content": { "type": "text", "text": "Analyzing..." } } |
| 49 | + |
| 50 | +// More chunks... but is this still the same message or a new one? |
| 51 | +{ "sessionUpdate": "agent_message_chunk", "content": { "type": "text", "text": "Found issues." } } |
| 52 | + |
| 53 | +// Tool call happens |
| 54 | +{ "sessionUpdate": "tool_call", ... } |
| 55 | + |
| 56 | +// Another agent message - definitely a new message |
| 57 | +{ "sessionUpdate": "agent_message_chunk", "content": { "type": "text", "text": "Fixed the issues." } } |
| 58 | +``` |
| 59 | + |
| 60 | +## What we propose to do about it |
| 61 | + |
| 62 | +Add a `messageId` field to `AgentMessageChunk` and `UserMessageChunk` session updates, and to the `session/prompt` response. This field would: |
| 63 | + |
| 64 | +1. **Provide stable message identification** - Each message gets a unique identifier that remains constant across all chunks of that message. |
| 65 | + |
| 66 | +2. **Enable reliable message boundary detection** - Clients can definitively determine when a new message starts by observing a change in `messageId`. |
| 67 | + |
| 68 | +3. **Create an extension point for future features** - Message IDs can be referenced in future protocol enhancements. |
| 69 | + |
| 70 | +### Proposed Structure |
| 71 | + |
| 72 | +When the Client sends a user message via `session/prompt`: |
| 73 | + |
| 74 | +```json |
| 75 | +{ |
| 76 | + "jsonrpc": "2.0", |
| 77 | + "id": 2, |
| 78 | + "method": "session/prompt", |
| 79 | + "params": { |
| 80 | + "sessionId": "sess_abc123def456", |
| 81 | + "prompt": [ |
| 82 | + { |
| 83 | + "type": "text", |
| 84 | + "text": "Can you analyze this code?" |
| 85 | + } |
| 86 | + ] |
| 87 | + } |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +The Agent assigns a `messageId` to the user message and returns it in the response: |
| 92 | + |
| 93 | +```json |
| 94 | +{ |
| 95 | + "jsonrpc": "2.0", |
| 96 | + "id": 2, |
| 97 | + "result": { |
| 98 | + "messageId": "msg_user_001", |
| 99 | + "stopReason": "end_turn" |
| 100 | + } |
| 101 | +} |
| 102 | +``` |
| 103 | + |
| 104 | +For agent message chunks, the Agent includes the `messageId`: |
| 105 | + |
| 106 | +```json |
| 107 | +{ |
| 108 | + "jsonrpc": "2.0", |
| 109 | + "method": "session/update", |
| 110 | + "params": { |
| 111 | + "sessionId": "sess_abc123def456", |
| 112 | + "update": { |
| 113 | + "sessionUpdate": "agent_message_chunk", |
| 114 | + "messageId": "msg_agent_001", |
| 115 | + "content": { |
| 116 | + "type": "text", |
| 117 | + "text": "Let me analyze your code..." |
| 118 | + } |
| 119 | + } |
| 120 | + } |
| 121 | +} |
| 122 | +``` |
| 123 | + |
| 124 | +If the Agent sends `user_message_chunk` updates, it uses the user message ID: |
| 125 | + |
| 126 | +```json |
| 127 | +{ |
| 128 | + "jsonrpc": "2.0", |
| 129 | + "method": "session/update", |
| 130 | + "params": { |
| 131 | + "sessionId": "sess_abc123def456", |
| 132 | + "update": { |
| 133 | + "sessionUpdate": "user_message_chunk", |
| 134 | + "messageId": "msg_user_001", |
| 135 | + "content": { |
| 136 | + "type": "text", |
| 137 | + "text": "Can you..." |
| 138 | + } |
| 139 | + } |
| 140 | + } |
| 141 | +} |
| 142 | +``` |
| 143 | + |
| 144 | +The `messageId` field would be: |
| 145 | + |
| 146 | +- **Required** on `agent_message_chunk` and `user_message_chunk` updates |
| 147 | +- **Required** in `session/prompt` responses as `messageId` |
| 148 | +- **Unique per message** within a session |
| 149 | +- **Stable across chunks** - all chunks belonging to the same message share the same `messageId` |
| 150 | +- **Opaque** - Clients treat it as an identifier without parsing its structure |
| 151 | +- **Agent-generated** - The Agent generates and manages all message IDs, consistent with how the protocol handles `sessionId`, `terminalId`, and `toolCallId` |
| 152 | + |
| 153 | +## Shiny future |
| 154 | + |
| 155 | +Once this feature exists: |
| 156 | + |
| 157 | +1. **Clear message boundaries** - Clients can reliably render distinct message bubbles in the UI, even when multiple messages of the same type are sent consecutively. |
| 158 | + |
| 159 | +2. **Better streaming UX** - Clients know exactly which message element to append chunks to, enabling smoother visual updates. |
| 160 | + |
| 161 | +3. **Foundation for editing** - With stable message identifiers, future protocol versions could add: |
| 162 | + - `message/edit` - Agent updates the content of a previously sent message |
| 163 | + - `message/delete` - Agent removes a message from the conversation |
| 164 | + - `message/replace` - Agent replaces an entire message with new content |
| 165 | + |
| 166 | +4. **Message metadata** - Future capabilities could reference messages by ID: |
| 167 | + - Annotations or reactions to specific messages |
| 168 | + - Citation or cross-reference between messages |
| 169 | + - Tool calls that reference which message triggered them |
| 170 | + |
| 171 | +5. **Enhanced debugging** - Implementations can trace message flow more easily with explicit IDs in logs and debugging tools. |
| 172 | + |
| 173 | +Example future editing capability: |
| 174 | + |
| 175 | +```json |
| 176 | +{ |
| 177 | + "jsonrpc": "2.0", |
| 178 | + "method": "session/update", |
| 179 | + "params": { |
| 180 | + "sessionId": "sess_abc123def456", |
| 181 | + "update": { |
| 182 | + "sessionUpdate": "message_update", |
| 183 | + "messageId": "msg_abc123", |
| 184 | + "updateType": "replace", |
| 185 | + "content": { |
| 186 | + "type": "text", |
| 187 | + "text": "Actually, let me correct that analysis..." |
| 188 | + } |
| 189 | + } |
| 190 | + } |
| 191 | +} |
| 192 | +``` |
| 193 | + |
| 194 | +## Implementation details and plan |
| 195 | + |
| 196 | +### Phase 1: Core Protocol Changes |
| 197 | + |
| 198 | +1. **Update schema** (`schema/schema.json`): |
| 199 | + - Add required `messageId` field (type: `string`) to `AgentMessageChunk` |
| 200 | + - Add required `messageId` field (type: `string`) to `UserMessageChunk` |
| 201 | + - Add required `messageId` field (type: `string`) to `PromptResponse` |
| 202 | + |
| 203 | +2. **Update Rust SDK** (`rust/client.rs` and `rust/agent.rs`): |
| 204 | + - Add `message_id: String` field to `ContentChunk` struct |
| 205 | + - Add `message_id: String` field to `PromptResponse` struct |
| 206 | + - Update serialization to include `messageId` in JSON output |
| 207 | + |
| 208 | +3. **Update TypeScript SDK** (if applicable): |
| 209 | + - Add `messageId` field to corresponding types |
| 210 | + |
| 211 | +4. **Update documentation** (`docs/protocol/prompt-turn.mdx`): |
| 212 | + - Document the `messageId` field and its semantics |
| 213 | + - Clarify that the Agent generates all message IDs |
| 214 | + - Show that `messageId` is returned in prompt responses |
| 215 | + - Add examples showing message boundaries |
| 216 | + - Explain that `messageId` changes indicate new messages |
| 217 | + |
| 218 | +### Phase 2: Reference Implementation |
| 219 | + |
| 220 | +5. **Update example agents**: |
| 221 | + - Modify example agents to generate and include `messageId` in chunks |
| 222 | + - Use simple ID generation (e.g., incrementing counter, UUID) |
| 223 | + - Demonstrate consistent IDs across chunks of the same message |
| 224 | + |
| 225 | +6. **Update example clients**: |
| 226 | + - Update clients to consume `messageId` field |
| 227 | + - Use IDs to properly group chunks into messages |
| 228 | + - Demonstrate clear message boundary rendering |
| 229 | + |
| 230 | +### Backward Compatibility |
| 231 | + |
| 232 | +Since this adds a **required** field, this would be a **breaking change** and should be part of a major version bump of the protocol. Agents and Clients will need to coordinate upgrades. |
| 233 | + |
| 234 | +Alternatively, the field could initially be made **optional** to allow gradual adoption: |
| 235 | + |
| 236 | +- Agents that support it advertise a capability flag during initialization |
| 237 | +- Clients check for the capability before relying on `messageId` |
| 238 | +- After wide adoption, make it required in a future version |
| 239 | + |
| 240 | +## Frequently asked questions |
| 241 | + |
| 242 | +### What alternative approaches did you consider, and why did you settle on this one? |
| 243 | + |
| 244 | +1. **Continue using `_meta` field** - This is the current workaround but: |
| 245 | + - Not standardized across implementations |
| 246 | + - Doesn't signal semantic importance |
| 247 | + - Easy to overlook or implement inconsistently |
| 248 | + |
| 249 | +2. **Detect message boundaries heuristically** - Clients could infer boundaries from timing, content types, or session state: |
| 250 | + - Unreliable and fragile |
| 251 | + - Doesn't work for all scenarios (e.g., consecutive same-type messages) |
| 252 | + - Creates inconsistent behavior across implementations |
| 253 | + |
| 254 | +3. **Use explicit "message start/end" markers** - Wrap messages with begin/end notifications: |
| 255 | + - More complex protocol interaction |
| 256 | + - Requires additional notifications |
| 257 | + - More state to track on both sides |
| 258 | + |
| 259 | +4. **Client-generated message IDs** - Have the Client generate IDs for user messages: |
| 260 | + - Inconsistent with protocol patterns (Agent generates `sessionId`, `terminalId`, `toolCallId`) |
| 261 | + - Adds complexity to Client implementations |
| 262 | + - Requires coordination on ID namespace to avoid collisions |
| 263 | + - Agent is better positioned as single source of truth |
| 264 | + |
| 265 | +The proposed approach with `messageId` is: |
| 266 | + |
| 267 | +- **Simple** - Just one new field with clear semantics |
| 268 | +- **Flexible** - Enables future capabilities without further protocol changes |
| 269 | +- **Consistent** - Aligns with how other resources (sessions, terminals, tool calls) are identified in the protocol |
| 270 | +- **Centralized** - Agent as single source of truth for all IDs simplifies uniqueness guarantees |
| 271 | + |
| 272 | +### Who generates message IDs? |
| 273 | + |
| 274 | +The **Agent generates all message IDs**, for both user and agent messages: |
| 275 | + |
| 276 | +- **For user messages**: When the Client sends `session/prompt`, the Agent assigns a message ID and returns it as `messageId` in the response |
| 277 | +- **For agent messages**: The Agent generates the ID when creating its response |
| 278 | + |
| 279 | +This is consistent with how the protocol handles other resource identifiers: |
| 280 | + |
| 281 | +- `sessionId` - generated by Agent in `session/new` response |
| 282 | +- `terminalId` - generated by Agent in `terminal/create` response |
| 283 | +- `toolCallId` - generated by Agent in tool call notifications |
| 284 | + |
| 285 | +Benefits of this approach: |
| 286 | + |
| 287 | +- **Single source of truth** - Agent controls all ID generation |
| 288 | +- **Simpler for Clients** - No ID generation logic needed |
| 289 | +- **Better uniqueness guarantees** - Agent controls the namespace |
| 290 | +- **Protocol consistency** - Matches established patterns |
| 291 | + |
| 292 | +### Should this field be required or optional? |
| 293 | + |
| 294 | +While making it required provides the clearest semantics, it would be a breaking change. The recommendation is to: |
| 295 | + |
| 296 | +1. Make it **optional** initially with a capability flag |
| 297 | +2. Strongly encourage adoption in the documentation |
| 298 | +3. Make it **required** in the next major protocol version |
| 299 | + |
| 300 | +This provides a migration path while moving toward a stronger protocol guarantee. |
| 301 | + |
| 302 | +### How should Agents generate message IDs? |
| 303 | + |
| 304 | +The protocol doesn't mandate a specific format. Agents may use: |
| 305 | + |
| 306 | +- UUIDs (e.g., `msg_550e8400-e29b-41d4-a716-446655440000`) |
| 307 | +- Prefixed sequential IDs (e.g., `msg_1`, `msg_2`, ...) |
| 308 | +- Hash-based IDs |
| 309 | +- Any other unique identifier scheme |
| 310 | + |
| 311 | +Clients **MUST** treat `messageId` as an opaque string and not rely on any particular format or structure. |
| 312 | + |
| 313 | +### What about message IDs across session loads? |
| 314 | + |
| 315 | +When a session is loaded via `session/load`, the Agent may: |
| 316 | + |
| 317 | +- Preserve original message IDs if replaying the conversation history |
| 318 | +- Generate new message IDs if only exposing current state |
| 319 | + |
| 320 | +The protocol doesn't require message IDs to be stable across session loads, though Agents MAY choose to make them stable if their implementation supports it. |
| 321 | + |
| 322 | +### Does this apply to other session updates like tool calls or plan updates? |
| 323 | + |
| 324 | +This RFD specifically addresses `agent_message_chunk` and `user_message_chunk` updates. Other session update types (like `tool_call`, `agent_thought_chunk`, `plan`) already have their own identification mechanisms: |
| 325 | + |
| 326 | +- Tool calls use `toolCallId` |
| 327 | +- Plan entries can be tracked by their position in the `entries` array |
| 328 | +- Agent thoughts could benefit from message IDs if they're considered distinct messages |
| 329 | + |
| 330 | +Future RFDs may propose extending `messageId` to other update types if use cases emerge. |
| 331 | + |
| 332 | +## Revision history |
| 333 | + |
| 334 | +- **2025-11-09**: Initial draft |
0 commit comments