-
Notifications
You must be signed in to change notification settings - Fork 144
RFD: introduce messageId field
#244
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
16a1117
d65b8f6
af8db66
2a71030
92fb18e
9d6bc2a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,324 @@ | ||
| --- | ||
| title: "Message ID" | ||
| --- | ||
|
|
||
| Author(s): [@michelTho](https://github.com/michelTho), [@nemtecl](https://github.com/nemtecl) | ||
|
|
||
| ## Elevator pitch | ||
|
|
||
| Add a `messageId` field to `agent_message_chunk`, `user_message_chunk`, and `agent_thought_chunk` session updates, to `session/prompt` requests and responses, to uniquely identify individual messages within a conversation. Both clients and agents can generate message IDs using UUID format. This enables clients to distinguish between different messages beyond changes in update type and lays the groundwork for future capabilities like message editing and session deduplication. | ||
|
|
||
| ## Status quo | ||
|
|
||
| Currently, when an Agent sends message chunks via `session/update` notifications, there is no explicit identifier for the message being streamed: | ||
|
|
||
| ```json | ||
| { | ||
| "jsonrpc": "2.0", | ||
| "method": "session/update", | ||
| "params": { | ||
| "sessionId": "sess_abc123def456", | ||
| "update": { | ||
| "sessionUpdate": "agent_message_chunk", | ||
| "content": { | ||
| "type": "text", | ||
| "text": "Let me analyze your code..." | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| This creates several limitations: | ||
|
|
||
| 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. | ||
|
|
||
| 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. | ||
|
|
||
| 3. **Limited future capabilities** - Without stable message identifiers, it's difficult to build features like: | ||
| - Message editing or updates | ||
| - Message-specific metadata or annotations | ||
| - Message threading or references | ||
| - Undo/redo functionality | ||
|
|
||
| As an example, consider this sequence where a Client cannot reliably determine message boundaries: | ||
|
|
||
| ```json | ||
| // First agent message chunk | ||
| { "sessionUpdate": "agent_message_chunk", "content": { "type": "text", "text": "Analyzing..." } } | ||
|
|
||
| // More chunks... but is this still the same message or a new one? | ||
| { "sessionUpdate": "agent_message_chunk", "content": { "type": "text", "text": "Found issues." } } | ||
|
|
||
| // Tool call happens | ||
| { "sessionUpdate": "tool_call", ... } | ||
|
|
||
| // Another agent message - definitely a new message | ||
| { "sessionUpdate": "agent_message_chunk", "content": { "type": "text", "text": "Fixed the issues." } } | ||
| ``` | ||
|
|
||
| ## What we propose to do about it | ||
|
|
||
| Add a `messageId` field to `AgentMessageChunk`, `UserMessageChunk`, and `AgentThoughtChunk` session updates, and to the `session/prompt` response. This field would: | ||
|
|
||
| 1. **Provide stable message identification** - Each message gets a unique identifier that remains constant across all chunks of that message. | ||
|
|
||
| 2. **Enable reliable message boundary detection** - Clients can definitively determine when a new message starts by observing a change in `messageId`. | ||
|
|
||
| 3. **Create an extension point for future features** - Message IDs can be referenced in future protocol enhancements. | ||
|
|
||
| ### Proposed Structure | ||
|
|
||
| When the Client sends a user message via `session/prompt`, it can optionally include a `messageId`: | ||
|
|
||
| ```json | ||
| { | ||
| "jsonrpc": "2.0", | ||
| "id": 2, | ||
| "method": "session/prompt", | ||
| "params": { | ||
| "sessionId": "sess_abc123def456", | ||
| "messageId": "4c12d49b-729c-4086-bfed-5b82e9a53400", | ||
| "prompt": [ | ||
| { | ||
| "type": "text", | ||
| "text": "Can you analyze this code?" | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| The Agent echoes the `messageId` in the response (or assigns one if the client didn't provide it): | ||
|
|
||
| ```json | ||
| { | ||
| "jsonrpc": "2.0", | ||
| "id": 2, | ||
| "result": { | ||
| "messageId": "4c12d49b-729c-4086-bfed-5b82e9a53400", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm I see why this is here (associate the turn it was in response to) but feels weird here as just
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, if the user did not pass this in, would the agent respond with the message id the agent assigned to that message? |
||
| "stopReason": "end_turn" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| For agent message chunks, the Agent generates and includes a `messageId`: | ||
|
|
||
| ```json | ||
| { | ||
| "jsonrpc": "2.0", | ||
| "method": "session/update", | ||
| "params": { | ||
| "sessionId": "sess_abc123def456", | ||
| "update": { | ||
| "sessionUpdate": "agent_message_chunk", | ||
| "messageId": "ea87d0e7-beb8-484a-a404-94a30b78a5a8", | ||
| "content": { | ||
| "type": "text", | ||
| "text": "Let me analyze your code..." | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| If the Agent sends `user_message_chunk` updates (e.g., during `session/load`), it uses the user message ID: | ||
|
|
||
| ```json | ||
| { | ||
| "jsonrpc": "2.0", | ||
| "method": "session/update", | ||
| "params": { | ||
| "sessionId": "sess_abc123def456", | ||
| "update": { | ||
| "sessionUpdate": "user_message_chunk", | ||
| "messageId": "4c12d49b-729c-4086-bfed-5b82e9a53400", | ||
| "content": { | ||
| "type": "text", | ||
| "text": "Can you..." | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| The `messageId` field would be: | ||
|
|
||
| - **Optional** on `agent_message_chunk`, `user_message_chunk`, and `agent_thought_chunk` updates | ||
| - **Optional** in `session/prompt` requests (client-provided) and responses (agent-provided or echoed) | ||
| - **UUID format** - Both clients and agents SHOULD use UUID format to avoid collisions | ||
| - **Unique per message** within a session | ||
| - **Stable across chunks** - all chunks belonging to the same message share the same `messageId` | ||
| - **Opaque** - Implementations treat it as an identifier without parsing its structure | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a thought, not a blocker:
What do you think? |
||
| - **Dual-origin** - Clients generate IDs for user messages, agents generate IDs for agent messages | ||
|
|
||
| ## Shiny future | ||
|
|
||
| Once this feature exists: | ||
|
|
||
| 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. | ||
|
|
||
| 2. **Better streaming UX** - Clients know exactly which message element to append chunks to, enabling smoother visual updates. | ||
|
|
||
| 3. **Foundation for editing** - With stable message identifiers, future protocol versions could add: | ||
| - `message/edit` - Agent updates the content of a previously sent message | ||
| - `message/delete` - Agent removes a message from the conversation | ||
| - `message/replace` - Agent replaces an entire message with new content | ||
|
|
||
| 4. **Message metadata** - Future capabilities could reference messages by ID: | ||
| - Annotations or reactions to specific messages | ||
| - Citation or cross-reference between messages | ||
| - Tool calls that reference which message triggered them | ||
|
|
||
| 5. **Enhanced debugging** - Implementations can trace message flow more easily with explicit IDs in logs and debugging tools. | ||
|
|
||
| Example future editing capability: | ||
|
|
||
| ```json | ||
| { | ||
| "jsonrpc": "2.0", | ||
| "method": "session/update", | ||
| "params": { | ||
| "sessionId": "sess_abc123def456", | ||
| "update": { | ||
| "sessionUpdate": "message_update", | ||
| "messageId": "ea87d0e7-beb8-484a-a404-94a30b78a5a8", | ||
| "updateType": "replace", | ||
| "content": { | ||
| "type": "text", | ||
| "text": "Actually, let me correct that analysis..." | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Implementation details and plan | ||
|
|
||
| ### Phase 1: Core Protocol Changes | ||
|
|
||
| 1. **Update schema** (`schema/schema.json`): | ||
| - Add optional `messageId` field (type: `string`) to `ContentChunk` (used by `AgentMessageChunk`, `UserMessageChunk`, `AgentThoughtChunk`) | ||
| - Add optional `messageId` field (type: `string`) to `PromptRequest` (client-provided) | ||
| - Add optional `messageId` field (type: `string`) to `PromptResponse` (agent-provided or echoed) | ||
|
|
||
| 2. **Update Rust SDK** (`rust/client.rs` and `rust/agent.rs`): | ||
| - Add `message_id: Option<String>` field to `ContentChunk` struct | ||
| - Add `message_id: Option<String>` field to `PromptRequest` struct | ||
| - Add `message_id: Option<String>` field to `PromptResponse` struct | ||
| - Update serialization to include `messageId` in JSON output when present | ||
|
|
||
| 3. **Update TypeScript SDK** (if applicable): | ||
| - Add `messageId` field to corresponding types | ||
|
|
||
| 4. **Update documentation** (`docs/protocol/prompt-turn.mdx`): | ||
| - Document the `messageId` field and its semantics | ||
| - Clarify that clients can provide `messageId` for user messages | ||
| - Clarify that agents generate `messageId` for agent messages | ||
| - Show that `messageId` can be provided in prompt requests and is returned in responses | ||
| - Add examples showing message boundaries | ||
| - Explain that `messageId` changes indicate new messages | ||
|
|
||
| ### Phase 2: Reference Implementation | ||
|
|
||
| 5. **Update example agents**: | ||
| - Modify example agents to generate and include `messageId` in chunks | ||
| - Use simple ID generation (e.g., incrementing counter, UUID) | ||
| - Demonstrate consistent IDs across chunks of the same message | ||
|
|
||
| 6. **Update example clients**: | ||
| - Update clients to consume `messageId` field | ||
| - Use IDs to properly group chunks into messages | ||
| - Demonstrate clear message boundary rendering | ||
|
|
||
| ### Backward Compatibility | ||
|
|
||
| The `messageId` field is **optional** to ensure this is a non-breaking change. Agents SHOULD include the `messageId` field, but it is not required for v1 compatibility. Features that rely on `messageId` (such as future message editing capabilities) will implicitly require the field to be present - Agents that don't provide it simply won't support those features. | ||
|
|
||
| Making this field required will be considered for a future v2 version of the protocol after wide adoption. | ||
|
|
||
| ## Frequently asked questions | ||
|
|
||
| ### What alternative approaches did you consider, and why did you settle on this one? | ||
|
|
||
| 1. **Continue using `_meta` field** - This is the current workaround but: | ||
| - Not standardized across implementations | ||
| - Doesn't signal semantic importance | ||
| - Easy to overlook or implement inconsistently | ||
|
|
||
| 2. **Detect message boundaries heuristically** - Clients could infer boundaries from timing, content types, or session state: | ||
| - Unreliable and fragile | ||
| - Doesn't work for all scenarios (e.g., consecutive same-type messages) | ||
| - Creates inconsistent behavior across implementations | ||
|
|
||
| 3. **Use explicit "message start/end" markers** - Wrap messages with begin/end notifications: | ||
| - More complex protocol interaction | ||
| - Requires additional notifications | ||
| - More state to track on both sides | ||
|
|
||
| 4. **Agent-only message IDs** - Have the Agent generate all IDs (including for user messages): | ||
| - Consistent with protocol patterns (`sessionId`, `terminalId`, `toolCallId`) | ||
| - But creates complexity for returning user message IDs (response comes after streaming) | ||
| - Not all agents support passing user message IDs through | ||
| - Clients may need IDs immediately for deduplication or multi-client scenarios | ||
|
|
||
| The proposed approach with `messageId` is: | ||
|
|
||
| - **Simple** - Just one new field with clear semantics | ||
| - **Flexible** - Enables future capabilities without further protocol changes | ||
| - **Practical** - Clients generate IDs for their messages, agents for theirs | ||
| - **Collision-safe** - UUID format ensures uniqueness across both sides | ||
|
|
||
| ### Who generates message IDs? | ||
|
|
||
| **Both clients and agents can generate message IDs**, each for their own messages: | ||
|
|
||
| - **For user messages**: The Client generates a UUID and includes it as `messageId` in the `session/prompt` request. The Agent echoes this ID in the response. If the client doesn't provide one, the agent MAY assign one. | ||
| - **For agent messages**: The Agent generates the UUID when creating its response and includes it in session update chunks. | ||
|
|
||
| This differs from other protocol identifiers (`sessionId`, `terminalId`, `toolCallId`) which are agent-generated, but provides practical benefits: | ||
|
|
||
| - **Immediate availability** - Clients have the ID as soon as they send the message, without waiting for a response | ||
| - **Deduplication** - Clients can use IDs to deduplicate messages on `session/load` or when echoing to multiple clients | ||
| - **Collision-safe** - UUID format ensures uniqueness without coordination | ||
| - **Adapter-friendly** - Adapters for agents that don't support message IDs can simply not pass them through | ||
|
|
||
| ### Should this field be required or optional? | ||
|
|
||
| The field is **optional** for v1 to ensure backward compatibility. Agents SHOULD include `messageId`, but it is not required. Features that depend on `messageId` (such as message editing) will implicitly require it - if an Agent doesn't provide `messageId`, those features simply won't be available. | ||
|
|
||
| Making this field required will be considered for a future v2 version of the protocol. | ||
|
|
||
| ### What format should message IDs use? | ||
|
|
||
| Both clients and agents **SHOULD** use UUID format for message IDs. This ensures: | ||
|
|
||
| - **No collisions** - UUIDs are globally unique without coordination | ||
| - **Interoperability** - Both sides use the same format | ||
| - **Simplicity** - Standard libraries available in all languages | ||
|
|
||
| Implementations **MUST** treat `messageId` as an opaque string and not rely on any particular structure beyond it being a valid identifier. | ||
|
|
||
| ### What about message IDs across session loads? | ||
|
|
||
| When a session is loaded via `session/load`, the Agent may: | ||
|
|
||
| - Preserve original message IDs if replaying the conversation history | ||
| - Generate new message IDs if only exposing current state | ||
|
|
||
| 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. | ||
|
|
||
| ### Does this apply to other session updates like tool calls or plan updates? | ||
|
|
||
| This RFD addresses `agent_message_chunk`, `user_message_chunk`, and `agent_thought_chunk` updates. | ||
| Other session update types (like `tool_call`, `plan`) already have their own identification mechanisms: | ||
|
|
||
| - Tool calls use `toolCallId` | ||
| - Plan entries can be tracked by their position in the `entries` array | ||
|
|
||
| Future RFDs may propose extending `messageId` to other update types if use cases emerge. | ||
|
|
||
| ## Revision history | ||
|
|
||
| - **2026-01-29**: Updated to allow both clients and agents to generate message IDs using UUID format | ||
| - **2025-11-09**: Initial draft | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One potential issue I see with this: