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.
@@ -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)