multi-agent-protocol 0.0.3 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/docs/00-design-specification.md +36 -0
- package/docs/01-open-questions.md +0 -5
- package/docs/02-wire-protocol.md +1 -1
- package/docs/07-federation.md +81 -5
- package/docs/09-authentication.md +748 -0
- package/docs/10-environment-awareness.md +242 -0
- package/docs/10-mail-protocol.md +553 -0
- package/docs/11-anp-inspired-improvements.md +1079 -0
- package/docs/12-anp-implementation-plan.md +641 -0
- package/docs/agent-iam-integration.md +877 -0
- package/docs/agentic-mesh-integration-draft.md +459 -0
- package/docs/git-transport-draft.md +251 -0
- package/package.json +5 -4
- package/schema/meta.json +200 -2
- package/schema/schema.json +1252 -13
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
# MAP Mail Protocol
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Mail Protocol is an **optional extension** to MAP that adds persistent conversation tracking on top of the ephemeral messaging layer. While `map/send` routes messages between agents in real-time, Mail records these interactions as **turns** within **conversations**, creating a queryable history of multi-agent coordination.
|
|
6
|
+
|
|
7
|
+
### Design Rationale
|
|
8
|
+
|
|
9
|
+
MAP's core messaging (`map/send`) is ephemeral: messages are routed and delivered, but the system does not retain them. This is sufficient for simple request-response patterns, but multi-agent orchestration often needs:
|
|
10
|
+
|
|
11
|
+
- **Auditability**: What happened during a complex multi-agent task?
|
|
12
|
+
- **Context sharing**: A new agent joining mid-conversation needs history.
|
|
13
|
+
- **Observability**: Dashboards and UIs need to display interaction timelines.
|
|
14
|
+
- **Trajectory tracking**: Recording an agent's internal tool calls and reasoning steps.
|
|
15
|
+
|
|
16
|
+
Mail addresses these needs without changing the core messaging model. It is a **persistence layer**, not a separate communication channel.
|
|
17
|
+
|
|
18
|
+
### Relationship to `map/send`
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
22
|
+
│ map/send (Transport Layer) │
|
|
23
|
+
│ │
|
|
24
|
+
│ Routes messages between agents in real-time. │
|
|
25
|
+
│ Ephemeral - not retained after delivery. │
|
|
26
|
+
│ │
|
|
27
|
+
│ When meta.mail is present: │
|
|
28
|
+
│ ├── Message is routed normally (unchanged behavior) │
|
|
29
|
+
│ └── Turn is automatically recorded in the conversation │
|
|
30
|
+
│ │
|
|
31
|
+
├─────────────────────────────────────────────────────────────┤
|
|
32
|
+
│ mail/* (Persistence Layer) │
|
|
33
|
+
│ │
|
|
34
|
+
│ Manages conversations, participants, turns, threads. │
|
|
35
|
+
│ Queryable - history available via mail/turns/list. │
|
|
36
|
+
│ Observable - mail.* events emitted for subscriptions. │
|
|
37
|
+
│ │
|
|
38
|
+
│ Two ways to record turns: │
|
|
39
|
+
│ ├── Explicit: mail/turn (direct API call) │
|
|
40
|
+
│ └── Intercepted: map/send with meta.mail (automatic) │
|
|
41
|
+
│ │
|
|
42
|
+
└─────────────────────────────────────────────────────────────┘
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Relationship to A2A Conversations
|
|
46
|
+
|
|
47
|
+
MAP deliberately avoids A2A's conversation/artifact split (see [01-open-questions.md](01-open-questions.md), Q7.5). Mail takes a different approach:
|
|
48
|
+
|
|
49
|
+
| A2A | MAP Mail |
|
|
50
|
+
|-----|----------|
|
|
51
|
+
| Conversations are the transport | `map/send` is the transport; conversations are an overlay |
|
|
52
|
+
| Messages and Artifacts are separate | Turns are the unified record (content type distinguishes) |
|
|
53
|
+
| Every interaction requires a Task | Conversations are optional; agents work without them |
|
|
54
|
+
| Conversation is required for communication | Communication works without mail; mail adds persistence |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Concepts
|
|
59
|
+
|
|
60
|
+
### Conversation
|
|
61
|
+
|
|
62
|
+
A **conversation** is a container for tracking related interactions between participants. It has a lifecycle (active → completed/failed/archived), a type that describes its purpose, and optional hierarchical relationships to other conversations.
|
|
63
|
+
|
|
64
|
+
**Types**:
|
|
65
|
+
- `user-session` - A session initiated by a user
|
|
66
|
+
- `agent-task` - An agent working on a specific task
|
|
67
|
+
- `multi-agent` - Coordination between multiple agents
|
|
68
|
+
- `mixed` - General purpose
|
|
69
|
+
|
|
70
|
+
**Status**: `active` | `paused` | `completed` | `failed` | `archived`
|
|
71
|
+
|
|
72
|
+
### Turn
|
|
73
|
+
|
|
74
|
+
A **turn** is the atomic unit of conversation. It records what a participant intentionally communicates, along with metadata about how it was created.
|
|
75
|
+
|
|
76
|
+
**Content types** (well-known):
|
|
77
|
+
- `text` - Text content (`{ text: string }`)
|
|
78
|
+
- `data` - Structured data (any JSON)
|
|
79
|
+
- `event` - Status/lifecycle events (`{ event: string, ... }`)
|
|
80
|
+
- `reference` - Reference to external content (`{ uri: string, ... }`)
|
|
81
|
+
- `x-*` - Custom types (e.g., `x-tool-call`, `x-reasoning-step`)
|
|
82
|
+
|
|
83
|
+
**Source tracking**:
|
|
84
|
+
- `explicit` - Created directly via `mail/turn`
|
|
85
|
+
- `intercepted` - Auto-recorded from `map/send` with `meta.mail`
|
|
86
|
+
|
|
87
|
+
### Thread
|
|
88
|
+
|
|
89
|
+
A **thread** is a focused sub-discussion within a conversation, rooted at a specific turn. Threads can be nested (a thread within a thread).
|
|
90
|
+
|
|
91
|
+
### Participant
|
|
92
|
+
|
|
93
|
+
A conversation **participant** has a role and permissions that control what they can do within the conversation.
|
|
94
|
+
|
|
95
|
+
**Roles**: `initiator` | `assistant` | `worker` | `observer` | `moderator`
|
|
96
|
+
|
|
97
|
+
**Permissions**: `canSend`, `canObserve`, `canInvite`, `canRemove`, `canCreateThreads`, `historyAccess` (`none` | `from-join` | `full`), `canSeeInternal`
|
|
98
|
+
|
|
99
|
+
### Turn Visibility
|
|
100
|
+
|
|
101
|
+
Individual turns can have visibility restrictions:
|
|
102
|
+
- `all` - All participants can see
|
|
103
|
+
- `participants` - Only specified participant IDs
|
|
104
|
+
- `role` - Only participants with specified roles
|
|
105
|
+
- `private` - Only the author
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Capability Negotiation
|
|
110
|
+
|
|
111
|
+
Mail support is advertised in the `map/connect` response via `capabilities.mail`:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// Connect request (client declares desired capabilities)
|
|
115
|
+
{
|
|
116
|
+
method: "map/connect",
|
|
117
|
+
params: {
|
|
118
|
+
capabilities: {
|
|
119
|
+
mail: { canCreate: true, canViewHistory: true }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Connect response (server declares what's available)
|
|
125
|
+
{
|
|
126
|
+
result: {
|
|
127
|
+
capabilities: {
|
|
128
|
+
mail: {
|
|
129
|
+
enabled: true, // Mail is available
|
|
130
|
+
canCreate: true, // This participant can create conversations
|
|
131
|
+
canJoin: true, // Can join existing conversations
|
|
132
|
+
canInvite: true, // Can invite others
|
|
133
|
+
canViewHistory: true, // Can query history
|
|
134
|
+
canCreateThreads: true // Can create threads
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
If `capabilities.mail` is absent or `enabled` is false, the server does not support mail. Clients should degrade gracefully.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Protocol Methods
|
|
146
|
+
|
|
147
|
+
All mail methods use the `mail/` namespace. They are Tier 3 (Extension) methods.
|
|
148
|
+
|
|
149
|
+
### mail/create
|
|
150
|
+
|
|
151
|
+
Create a new conversation. The caller automatically joins as initiator.
|
|
152
|
+
|
|
153
|
+
**Request**:
|
|
154
|
+
```json
|
|
155
|
+
{
|
|
156
|
+
"method": "mail/create",
|
|
157
|
+
"params": {
|
|
158
|
+
"type": "multi-agent",
|
|
159
|
+
"subject": "Sprint planning discussion",
|
|
160
|
+
"parentConversationId": "conv-parent",
|
|
161
|
+
"parentTurnId": "turn-that-spawned-this",
|
|
162
|
+
"initialParticipants": [
|
|
163
|
+
{ "id": "agent-researcher", "role": "worker" },
|
|
164
|
+
{ "id": "agent-writer", "role": "worker" }
|
|
165
|
+
],
|
|
166
|
+
"initialTurn": {
|
|
167
|
+
"contentType": "text",
|
|
168
|
+
"content": { "text": "Let's plan the sprint." }
|
|
169
|
+
},
|
|
170
|
+
"metadata": { "project": "map-sdk" }
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Response**:
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"result": {
|
|
179
|
+
"conversation": { "id": "conv-001", "type": "multi-agent", "status": "active", ... },
|
|
180
|
+
"participant": { "id": "caller-id", "role": "initiator", ... },
|
|
181
|
+
"initialTurn": { "id": "turn-001", ... }
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### mail/get
|
|
187
|
+
|
|
188
|
+
Get conversation details with optional includes.
|
|
189
|
+
|
|
190
|
+
**Request**:
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"method": "mail/get",
|
|
194
|
+
"params": {
|
|
195
|
+
"conversationId": "conv-001",
|
|
196
|
+
"include": {
|
|
197
|
+
"participants": true,
|
|
198
|
+
"threads": true,
|
|
199
|
+
"recentTurns": 10,
|
|
200
|
+
"stats": true
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### mail/list
|
|
207
|
+
|
|
208
|
+
List conversations with filtering and cursor-based pagination.
|
|
209
|
+
|
|
210
|
+
**Request**:
|
|
211
|
+
```json
|
|
212
|
+
{
|
|
213
|
+
"method": "mail/list",
|
|
214
|
+
"params": {
|
|
215
|
+
"filter": {
|
|
216
|
+
"type": ["user-session", "multi-agent"],
|
|
217
|
+
"status": ["active"],
|
|
218
|
+
"participantId": "agent-001"
|
|
219
|
+
},
|
|
220
|
+
"limit": 20,
|
|
221
|
+
"cursor": "cursor-token"
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### mail/close
|
|
227
|
+
|
|
228
|
+
Close a conversation. Sets status to `completed`.
|
|
229
|
+
|
|
230
|
+
**Request**:
|
|
231
|
+
```json
|
|
232
|
+
{
|
|
233
|
+
"method": "mail/close",
|
|
234
|
+
"params": {
|
|
235
|
+
"conversationId": "conv-001",
|
|
236
|
+
"reason": "Task completed successfully"
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### mail/join
|
|
242
|
+
|
|
243
|
+
Join a conversation with optional catch-up (receive recent history).
|
|
244
|
+
|
|
245
|
+
**Request**:
|
|
246
|
+
```json
|
|
247
|
+
{
|
|
248
|
+
"method": "mail/join",
|
|
249
|
+
"params": {
|
|
250
|
+
"conversationId": "conv-001",
|
|
251
|
+
"role": "worker",
|
|
252
|
+
"catchUp": {
|
|
253
|
+
"from": 1706745600000,
|
|
254
|
+
"limit": 50,
|
|
255
|
+
"includeSummary": true
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Response** includes `history` (recent turns) and optionally `summary` if `includeSummary` was true.
|
|
262
|
+
|
|
263
|
+
### mail/leave
|
|
264
|
+
|
|
265
|
+
Leave a conversation.
|
|
266
|
+
|
|
267
|
+
**Request**:
|
|
268
|
+
```json
|
|
269
|
+
{
|
|
270
|
+
"method": "mail/leave",
|
|
271
|
+
"params": {
|
|
272
|
+
"conversationId": "conv-001",
|
|
273
|
+
"reason": "Work complete"
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### mail/invite
|
|
279
|
+
|
|
280
|
+
Invite a participant to a conversation.
|
|
281
|
+
|
|
282
|
+
**Request**:
|
|
283
|
+
```json
|
|
284
|
+
{
|
|
285
|
+
"method": "mail/invite",
|
|
286
|
+
"params": {
|
|
287
|
+
"conversationId": "conv-001",
|
|
288
|
+
"participant": {
|
|
289
|
+
"id": "agent-reviewer",
|
|
290
|
+
"role": "observer",
|
|
291
|
+
"permissions": { "canSend": false, "historyAccess": "from-join" }
|
|
292
|
+
},
|
|
293
|
+
"message": "Please review the conversation so far."
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### mail/turn
|
|
299
|
+
|
|
300
|
+
Record a turn explicitly. The caller must be a participant.
|
|
301
|
+
|
|
302
|
+
**Request**:
|
|
303
|
+
```json
|
|
304
|
+
{
|
|
305
|
+
"method": "mail/turn",
|
|
306
|
+
"params": {
|
|
307
|
+
"conversationId": "conv-001",
|
|
308
|
+
"contentType": "text",
|
|
309
|
+
"content": { "text": "Here are the research results." },
|
|
310
|
+
"threadId": "thread-001",
|
|
311
|
+
"inReplyTo": "turn-005",
|
|
312
|
+
"visibility": { "type": "all" },
|
|
313
|
+
"metadata": { "source": "research-tool" }
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### mail/turns/list
|
|
319
|
+
|
|
320
|
+
List turns with filtering and pagination.
|
|
321
|
+
|
|
322
|
+
**Request**:
|
|
323
|
+
```json
|
|
324
|
+
{
|
|
325
|
+
"method": "mail/turns/list",
|
|
326
|
+
"params": {
|
|
327
|
+
"conversationId": "conv-001",
|
|
328
|
+
"filter": {
|
|
329
|
+
"threadId": "thread-001",
|
|
330
|
+
"contentTypes": ["text", "data"],
|
|
331
|
+
"participantId": "agent-001",
|
|
332
|
+
"afterTimestamp": 1706745600000
|
|
333
|
+
},
|
|
334
|
+
"limit": 50,
|
|
335
|
+
"order": "asc"
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### mail/thread/create
|
|
341
|
+
|
|
342
|
+
Create a thread rooted at a specific turn.
|
|
343
|
+
|
|
344
|
+
**Request**:
|
|
345
|
+
```json
|
|
346
|
+
{
|
|
347
|
+
"method": "mail/thread/create",
|
|
348
|
+
"params": {
|
|
349
|
+
"conversationId": "conv-001",
|
|
350
|
+
"rootTurnId": "turn-005",
|
|
351
|
+
"subject": "Deep dive on React performance",
|
|
352
|
+
"parentThreadId": "thread-001"
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### mail/thread/list
|
|
358
|
+
|
|
359
|
+
List threads in a conversation.
|
|
360
|
+
|
|
361
|
+
**Request**:
|
|
362
|
+
```json
|
|
363
|
+
{
|
|
364
|
+
"method": "mail/thread/list",
|
|
365
|
+
"params": {
|
|
366
|
+
"conversationId": "conv-001",
|
|
367
|
+
"parentThreadId": "thread-001"
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### mail/summary
|
|
373
|
+
|
|
374
|
+
Get or generate a summary for a conversation.
|
|
375
|
+
|
|
376
|
+
**Request**:
|
|
377
|
+
```json
|
|
378
|
+
{
|
|
379
|
+
"method": "mail/summary",
|
|
380
|
+
"params": {
|
|
381
|
+
"conversationId": "conv-001",
|
|
382
|
+
"scope": { "threadId": "thread-001" },
|
|
383
|
+
"regenerate": false,
|
|
384
|
+
"include": {
|
|
385
|
+
"keyPoints": true,
|
|
386
|
+
"keyDecisions": true,
|
|
387
|
+
"openQuestions": true
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### mail/replay
|
|
394
|
+
|
|
395
|
+
Replay turns from a specific point (for catch-up after reconnection).
|
|
396
|
+
|
|
397
|
+
**Request**:
|
|
398
|
+
```json
|
|
399
|
+
{
|
|
400
|
+
"method": "mail/replay",
|
|
401
|
+
"params": {
|
|
402
|
+
"conversationId": "conv-001",
|
|
403
|
+
"fromTurnId": "turn-010",
|
|
404
|
+
"threadId": "thread-001",
|
|
405
|
+
"limit": 100,
|
|
406
|
+
"contentTypes": ["text", "data"]
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## Turn Interception via map/send
|
|
414
|
+
|
|
415
|
+
When `map/send` includes `meta.mail`, the server automatically records an intercepted turn in addition to routing the message normally. This is the primary mechanism for zero-effort turn recording.
|
|
416
|
+
|
|
417
|
+
```json
|
|
418
|
+
{
|
|
419
|
+
"method": "map/send",
|
|
420
|
+
"params": {
|
|
421
|
+
"to": { "agent": "worker-1" },
|
|
422
|
+
"payload": { "task": "research", "query": "MAP protocol" },
|
|
423
|
+
"meta": {
|
|
424
|
+
"mail": {
|
|
425
|
+
"conversationId": "conv-001",
|
|
426
|
+
"threadId": "thread-001",
|
|
427
|
+
"inReplyTo": "turn-005",
|
|
428
|
+
"visibility": { "type": "all" }
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
**Behavior**:
|
|
436
|
+
1. Message is routed to `worker-1` normally (unchanged `map/send` behavior)
|
|
437
|
+
2. A turn is recorded with `source: { type: "intercepted", messageId: "<id>" }`
|
|
438
|
+
3. A `mail.turn.added` event is emitted
|
|
439
|
+
4. If turn recording fails, the message delivery is NOT affected (non-blocking)
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
## Events
|
|
444
|
+
|
|
445
|
+
Mail emits the following events (delivered via `map/event` to subscribers):
|
|
446
|
+
|
|
447
|
+
| Event Type | Data | Emitted When |
|
|
448
|
+
|------------|------|-------------|
|
|
449
|
+
| `mail.created` | `{ conversationId, type, subject?, createdBy }` | Conversation created |
|
|
450
|
+
| `mail.closed` | `{ conversationId, closedBy, reason? }` | Conversation closed |
|
|
451
|
+
| `mail.participant.joined` | `{ conversationId, participant }` | Participant joins |
|
|
452
|
+
| `mail.participant.left` | `{ conversationId, participantId, reason? }` | Participant leaves |
|
|
453
|
+
| `mail.turn.added` | `{ conversationId, turn }` | Turn recorded (explicit or intercepted) |
|
|
454
|
+
| `mail.turn.updated` | `{ conversationId, turnId, status? }` | Turn status updated |
|
|
455
|
+
| `mail.thread.created` | `{ conversationId, thread }` | Thread created |
|
|
456
|
+
| `mail.summary.generated` | `{ conversationId, summary }` | Summary generated |
|
|
457
|
+
|
|
458
|
+
### Mail Subscription Filtering
|
|
459
|
+
|
|
460
|
+
The `SubscriptionFilter` supports a `mail` field for filtering mail events:
|
|
461
|
+
|
|
462
|
+
```json
|
|
463
|
+
{
|
|
464
|
+
"method": "map/subscribe",
|
|
465
|
+
"params": {
|
|
466
|
+
"filter": {
|
|
467
|
+
"eventTypes": ["mail.turn.added", "mail.participant.joined"],
|
|
468
|
+
"mail": {
|
|
469
|
+
"conversationId": "conv-001",
|
|
470
|
+
"threadId": "thread-001",
|
|
471
|
+
"participantId": "agent-001",
|
|
472
|
+
"contentType": "text"
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
All mail filter fields use AND logic. Only `mail.*` events are matched.
|
|
480
|
+
|
|
481
|
+
---
|
|
482
|
+
|
|
483
|
+
## Error Codes
|
|
484
|
+
|
|
485
|
+
Mail uses error codes in the 10000 range:
|
|
486
|
+
|
|
487
|
+
| Code | Name | Description |
|
|
488
|
+
|------|------|-------------|
|
|
489
|
+
| 10000 | CONVERSATION_NOT_FOUND | Conversation ID does not exist |
|
|
490
|
+
| 10001 | CONVERSATION_CLOSED | Cannot modify a closed conversation |
|
|
491
|
+
| 10002 | NOT_A_PARTICIPANT | Caller is not a participant in the conversation |
|
|
492
|
+
| 10003 | MAIL_PERMISSION_DENIED | Caller lacks required mail capability |
|
|
493
|
+
| 10004 | PARTICIPANT_ALREADY_JOINED | Participant is already in the conversation |
|
|
494
|
+
| 10005 | PARTICIPANT_NOT_FOUND | Participant not found in the conversation |
|
|
495
|
+
| 10006 | TURN_NOT_FOUND | Turn ID does not exist |
|
|
496
|
+
| 10007 | THREAD_NOT_FOUND | Thread ID does not exist |
|
|
497
|
+
| 10008 | INVALID_CONTENT_TYPE | Content type not recognized (must be well-known or `x-` prefixed) |
|
|
498
|
+
| 10009 | THREAD_NESTING_LIMIT | Thread nesting depth exceeded |
|
|
499
|
+
| 10010 | MAIL_NOT_ENABLED | Server does not have mail enabled |
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
## Progressive Adoption
|
|
504
|
+
|
|
505
|
+
Mail is designed for incremental integration. Agents can participate in conversations with zero code changes and adopt richer features over time.
|
|
506
|
+
|
|
507
|
+
### Level 0: Unaware Agent (Zero Changes)
|
|
508
|
+
|
|
509
|
+
Agents that don't know about mail work exactly as before. If an orchestrator sends them a message with `meta.mail`, the agent receives the message normally and ignores the unknown `mail` key. The orchestrator is responsible for tracking the conversation.
|
|
510
|
+
|
|
511
|
+
### Level 1: Pass-Through Agent (One Line)
|
|
512
|
+
|
|
513
|
+
An agent forwards `meta.mail` from incoming messages to its replies. This ensures replies are recorded as turns without the agent understanding mail.
|
|
514
|
+
|
|
515
|
+
```
|
|
516
|
+
Incoming message has meta.mail → agent processes → reply includes meta.mail
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
### Level 2: Conversation-Aware Agent
|
|
520
|
+
|
|
521
|
+
An agent that reads `meta.mail.conversationId` and explicitly records turns via `mail/turn`. Useful for agents that want to log observations, status events, or intermediate results.
|
|
522
|
+
|
|
523
|
+
### Level 3: Orchestrator (Full Integration)
|
|
524
|
+
|
|
525
|
+
An orchestrator creates conversations, manages participants, delegates with mail context, and closes conversations when work is complete.
|
|
526
|
+
|
|
527
|
+
### Level 4: Observer Client (Dashboard/UI)
|
|
528
|
+
|
|
529
|
+
A client that subscribes to `mail.*` events and queries `mail/turns/list` to display conversation timelines. Does not participate in conversations, only watches.
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
## Server Implementation
|
|
534
|
+
|
|
535
|
+
A server that supports mail needs:
|
|
536
|
+
|
|
537
|
+
1. **Storage** for conversations, participants, turns, and threads (in-memory or persistent)
|
|
538
|
+
2. **Turn interception** in the `map/send` handler (record turn when `meta.mail` is present)
|
|
539
|
+
3. **Method handlers** for all `mail/*` methods
|
|
540
|
+
4. **Event emission** for `mail.*` events via the EventBus
|
|
541
|
+
5. **Capability advertisement** in the `map/connect` response (`capabilities.mail`)
|
|
542
|
+
6. **Subscription filtering** for `mail` filter fields on `mail.*` events
|
|
543
|
+
|
|
544
|
+
Mail is enabled per-server. When `mail` is not configured, `mail/*` methods return error 10010 (MAIL_NOT_ENABLED).
|
|
545
|
+
|
|
546
|
+
---
|
|
547
|
+
|
|
548
|
+
## Related Specs
|
|
549
|
+
|
|
550
|
+
- [00-design-specification.md](00-design-specification.md): Protocol overview and method tiers
|
|
551
|
+
- [02-wire-protocol.md](02-wire-protocol.md): JSON-RPC wire format (mail uses the same format)
|
|
552
|
+
- [03-streaming-semantics.md](03-streaming-semantics.md): Event streaming (mail events follow the same semantics)
|
|
553
|
+
- [04-error-handling.md](04-error-handling.md): Error handling patterns (mail errors use the same structure)
|