Skip to content

Commit 16a1117

Browse files
committed
docs(rfd): introduce messageId field
1 parent 945d13e commit 16a1117

File tree

2 files changed

+339
-1
lines changed

2 files changed

+339
-1
lines changed

docs/docs.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,11 @@
9191
"rfds/about",
9292
{
9393
"group": "Draft",
94-
"pages": ["rfds/session-list", "rfds/session-config-options"]
94+
"pages": [
95+
"rfds/session-list",
96+
"rfds/session-config-options",
97+
"rfds/message-id"
98+
]
9599
},
96100
{ "group": "Preview", "pages": [] },
97101
{ "group": "Completed", "pages": ["rfds/introduce-rfd-process"] }

docs/rfds/message-id.mdx

Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
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

Comments
 (0)