RFC-MACP-0006
RFC-MACP-0006
Multi-Agent Coordination Protocol (MACP) — Transport Bindings
Document: RFC-MACP-0006 Version: 1.5.0-draft Status: Community Standards Track Updates: RFC-MACP-0001
Changelog — 1.5.0-draft: §3.2's bookkeeping-entries list now names
SessionCancelexplicitly (previously reachable only via the trailing "any other internal log entry" catch-all), citing RFC-MACP-0001 §7.3 where it is defined. Clarifying prose only — RFC-MACP-0001 §7.3 already calledSessionCancela "terminal annotation", the same class of entry this section already exempted from ordinal-consumption and subscribe-delivery (issue #159). This section's item 4 ("only accepted-history envelopes are delivered") means accepted-history is a necessary condition for delivery, not sufficient — bookkeeping entries are part of accepted history (RFC-MACP-0001 §7.3, §7.5) yet are excluded from delivery by this bullet; that reading already applied toSessionSuspend/SessionResumeand is unchanged by addingSessionCancelto the same list.Changelog — 1.4.0-draft: §3.2 gains a Redelivery subsection stating the client-side counterpart of RFC-MACP-0001 §8.2: a client MUST tolerate being redelivered an envelope it has already observed (both from at-least-once transport and from ordinary re-subscribe replay), MUST key duplicate detection on
message_id, and MUST NOT let a repeat advance its sequence position, count against a Mode cardinality rule, or mutate accumulated state. The sequence-counting sentence is tightened to "distinct accepted envelopes" accordingly.Changelog — 1.3.0-draft: §3.2 now defines what the passive-subscribe sequence is — the 1-based ordinal of accepted session-scoped envelopes, exclusive
after_sequence, internal entries consuming no ordinals, stability across restart and compaction, andFAILED_PRECONDITIONfor a resume below the compacted base. Previously the sequence was specified only behaviorally ("starting fromafter_sequence + 1"), which left clients no defined way to compute their own position.Changelog — 1.2.0-draft: §3.8 now describes the paged
ListSessionscontract (page_size, opaquepage_token,next_page_token), which shipped incore.protowithout a matching prose update. The prose is a correction, not a wire change: it states what the proto already defines, including that only an emptynext_page_tokensignals completion.Changelog — 1.1.0-draft: passive session subscription is promoted from the former Appendix A.1 to core
StreamSessionsemantics in §3.2.subscribe_session_idandafter_sequenceare normative on every runtime that advertisessessions.stream = true; they are not an optional capability.
1. Introduction
MACP is transport-agnostic. This document defines standard transport bindings for MACP messages.
Normative transport: gRPC over HTTP/2.
See RFC-MACP-0001 Section 9 for core transport requirements. For discovery of transport endpoints in manifests, see RFC-MACP-0005.
Registered transport identifiers are listed in registries/transports.md.
2. Envelope Transmission
All transports MUST carry canonical MACP Envelopes defined in RFC-MACP-0001.
Transport bindings MUST preserve:
- within-session acceptance order,
- idempotency semantics,
- session isolation,
- structured error signaling appropriate to the transport.
3. gRPC Binding (Normative)
Conformant implementations MUST support the gRPC binding.
The canonical gRPC service is defined in schemas/proto/macp/v1/core.proto. The proto file is the authoritative source; the listing below is reproduced for convenience.
service MACPRuntimeService {
rpc Initialize(InitializeRequest) returns (InitializeResponse);
rpc Send(SendRequest) returns (SendResponse);
rpc StreamSession(stream StreamSessionRequest) returns (stream StreamSessionResponse);
rpc GetSession(GetSessionRequest) returns (GetSessionResponse);
rpc CancelSession(CancelSessionRequest) returns (CancelSessionResponse);
rpc GetManifest(GetManifestRequest) returns (GetManifestResponse);
rpc ListModes(ListModesRequest) returns (ListModesResponse);
rpc WatchModeRegistry(WatchModeRegistryRequest) returns (stream WatchModeRegistryResponse);
rpc ListRoots(ListRootsRequest) returns (ListRootsResponse);
rpc WatchRoots(WatchRootsRequest) returns (stream WatchRootsResponse);
// Extension mode lifecycle
rpc ListExtModes(ListExtModesRequest) returns (ListExtModesResponse);
rpc RegisterExtMode(RegisterExtModeRequest) returns (RegisterExtModeResponse);
rpc UnregisterExtMode(UnregisterExtModeRequest) returns (UnregisterExtModeResponse);
rpc PromoteMode(PromoteModeRequest) returns (PromoteModeResponse);
// Ambient Signal observation
rpc WatchSignals(WatchSignalsRequest) returns (stream WatchSignalsResponse);
// Session lifecycle observation (RFC-MACP-0001 §7.3)
rpc ListSessions(ListSessionsRequest) returns (ListSessionsResponse);
rpc WatchSessions(WatchSessionsRequest) returns (stream WatchSessionsResponse);
// Governance policy lifecycle (RFC-MACP-0012)
rpc RegisterPolicy(RegisterPolicyRequest) returns (RegisterPolicyResponse);
rpc UnregisterPolicy(UnregisterPolicyRequest) returns (UnregisterPolicyResponse);
rpc GetPolicy(GetPolicyRequest) returns (GetPolicyResponse);
rpc ListPolicies(ListPoliciesRequest) returns (ListPoliciesResponse);
rpc WatchPolicies(WatchPoliciesRequest) returns (stream WatchPoliciesResponse);
}3.1 Send
Send is the authoritative per-message request/ack surface.
A compliant runtime MUST use SendResponse.ack for standard per-message acceptance or rejection signaling.
3.2 StreamSession
StreamSession is an optional interactive envelope stream advertised by sessions.stream.
A runtime that advertises sessions.stream = true MUST implement StreamSession with these semantics:
- the stream carries canonical MACP Envelopes only,
- once the stream is bound to a non-empty
session_id, all subsequent session-scoped envelopes on that stream MUST use the samesession_id, - accepted envelopes emitted by the server MUST appear in authoritative acceptance order for that session,
- the stream MUST NOT invent ad-hoc pseudo-envelopes (informal messages that are not canonical MACP Envelopes) whose payloads encode JSON-only acks or errors unless an explicitly negotiated experimental capability allows it.
StreamSession is not the standard replacement for unary Send acknowledgements. Clients that require standard per-message negative acknowledgements SHOULD use Send.
A runtime MAY echo back accepted client-submitted envelopes on the stream as part of the authoritative accepted sequence.
Session-scoped Signal envelopes are invalid in the base protocol and MUST NOT be used as a stream-attach mechanism. Clients that need zero-mutation observation of an existing session MUST use the passive session subscription semantics defined below.
Passive Session Subscription
StreamSessionRequest carries two session-binding shapes:
envelope— a session-scoped coordination envelope. StandardStreamSessionbehavior as specified above.subscribe_session_id(with optionalafter_sequence) — a passive-subscribe frame that binds the stream to an existing session's broadcast without emitting a coordination message.
A single StreamSessionRequest MUST NOT set both envelope and subscribe_session_id; a runtime MUST reject such a request as an invalid argument.
On a request with subscribe_session_id set, a runtime that advertises sessions.stream = true MUST:
- Authorize the caller. The caller MUST be an authenticated declared participant of the session, or an observer identity admitted by deployment policy (RFC-MACP-0004 §4). Unauthorized callers MUST be rejected.
- Replay accepted session envelopes in strict acceptance order starting from
after_sequence + 1. Whenafter_sequence = 0, replay begins at the session's first accepted envelope. - Switch seamlessly to live broadcast on the same stream after replay drains, preserving within-session acceptance order.
- Never replay rejected envelopes (RFC-MACP-0001 §7.2); only accepted-history envelopes are delivered.
Sequence semantics. For resume to be interoperable, a runtime MUST implement after_sequence against the following definition of the session sequence:
- The sequence is the 1-based ordinal of accepted session-scoped envelopes, assigned in acceptance order. The first envelope accepted on a session has ordinal 1.
- Entries a runtime records for its own bookkeeping — the
SessionSuspend/SessionResumeannotations of RFC-MACP-0001 §7.5, theSessionCancelterminal annotation of RFC-MACP-0001 §7.3, TTL expiry, storage checkpoints, and any other internal log entry — MUST NOT consume ordinals. Client-visible ordinals are therefore contiguous. after_sequenceis exclusive. Replay resumes atafter_sequence + 1;after_sequence = 0replays from the session's first accepted envelope.
The Envelope carries no sequence field on the wire (RFC-MACP-0001 §6), so a client can determine its position only by counting the distinct accepted envelopes it has been delivered — see Redelivery below, since the same envelope may arrive more than once and a repeat MUST NOT advance the count. Two obligations follow, and a runtime MUST satisfy both:
- The envelopes delivered on a subscribe stream MUST be exactly those that consume ordinals. A runtime MUST NOT deliver an internal annotation on this stream: a client cannot distinguish it from an ordinal-consuming envelope, and would over-count.
- An ordinal MUST be stable for the life of the session — across runtime restarts, storage migration, and log compaction. A runtime that renumbers accepted envelopes breaks every resuming client silently, because the client asks to continue from a position that no longer denotes the same envelope and history is skipped or repeated with no error surface.
Compaction. A runtime that compacts session history MUST record, in the compaction checkpoint, the number of accepted ordinals it discarded, so that surviving envelopes retain their original ordinals instead of being renumbered from 1. A resume whose after_sequence falls below the compacted base MUST be rejected with gRPC status FAILED_PRECONDITION, identifying the lowest ordinal still available; it MUST NOT be silently served from the oldest surviving envelope. As with §3.8's INVALID_ARGUMENT, this is a transport status rather than a MACP error code — the codes in registries/error-codes.md are envelope-scoped, and a resume request carries no Envelope.
Redelivery. A client MUST tolerate being delivered an envelope it has already observed. Two independent mechanisms produce this, and neither is a malfunction:
- MACP assumes at-least-once delivery semantics at the transport layer (RFC-MACP-0001 §8), so a transport MAY present the same envelope more than once.
- Re-subscribing replays history. A client that reconnects, or that opens a subscription with an
after_sequenceat or below a position it has already consumed, is delivered every accepted envelope after that point again — including ones it has already applied.after_sequence = 0on a reconnect replays the entire session.
A redelivered envelope is the same message, not a new one. Clients MUST key duplicate detection on message_id — the identity the runtime itself uses for this purpose (RFC-MACP-0001 §8.2) — and MUST observe the following:
- A redelivery MUST NOT advance the client's sequence position; only a distinct accepted envelope does. A client that counts raw delivery events rather than distinct envelopes arrives at a position ahead of the true one, and its next resume silently skips history.
- A redelivery MUST NOT count a second time against any Mode cardinality rule. Where a Mode limits a participant to one message of a kind — RFC-MACP-0007 §5 rule 3's one
Voteperproposal_id, RFC-MACP-0011 §5 rule 3's one ballot perrequest_idper eligible participant — "a second" means a distinctmessage_id, never the same envelope arriving twice. - A consumer that accumulates state per envelope — appending to a list, incrementing a counter — MUST be idempotent with respect to
message_id. Re-applying a redelivered envelope MUST NOT change derived state.
Runtimes are already required to enforce duplicate handling at their own ingress (RFC-MACP-0001 §8.2), so a duplicate never enters accepted history and is never broadcast. The requirements above are the client-side counterpart: a client reconstructing session state from a stream is handed the same envelope twice through ordinary reconnection, and MUST arrive at the state the runtime holds.
A stream opened with a passive-subscribe frame is conventionally read-only. Implementations MAY refuse subsequent envelope frames on such a stream; clients that intend to coordinate on the session SHOULD open a separate StreamSession or use unary Send.
When a transport-level or stream-fatal error occurs, the runtime SHOULD use native gRPC stream termination semantics.
3.3 WatchModeRegistry and WatchRoots
These watch streams are optional discovery hints.
ListModes SHOULD return only standards-track mode descriptors. Extension mode descriptors are discoverable through implementation-defined surfaces and are not part of the base ListModes response.
A runtime MUST advertise mode_registry.list_changed = true before WatchModeRegistry can be assumed interoperable.
A runtime MUST advertise roots.list_changed = true before WatchRoots can be assumed interoperable.
A watch notification indicates that the corresponding registry or roots view may have changed. Clients SHOULD re-query the full surface (ListModes or ListRoots) after receiving a change notification. A minimal compliant implementation MAY send an initial change hint immediately after stream establishment and then remain idle until a later change occurs.
3.4 WatchSignals
WatchSignals is an optional server-streaming RPC that broadcasts Ambient Signal Envelopes to all subscribers.
Signals are non-binding messages on the ambient plane (per RFC-MACP-0001 §5.1). They MUST carry empty session_id and empty mode in the Envelope. Signals MUST NOT enter any session's accepted history or mutate session state.
A WatchSignalsResponse contains the full Envelope of each accepted Signal. The SignalPayload within the envelope MAY include a correlation_session_id to indicate which session the signal relates to, without making the Signal session-scoped.
Use cases include:
- progress notifications from agents working on evaluations
- heartbeat and liveness signals
- status updates between coordination steps
A runtime that supports WatchSignals MUST broadcast all accepted Signal envelopes to all active subscribers. Signals are ephemeral — they are not persisted and are not available for replay.
3.5 GetSession
GetSession returns a SessionMetadata snapshot for the given session. The response includes the session's identity, state, timing, and bound version fields (mode_version, configuration_version, policy_version), as well as the current participant list, per-participant activity summaries (ParticipantActivity), context_id (if set at session creation), and extension_keys (the keys of any extension blocks bound at session creation). The ParticipantActivity entries provide participant_id, last_message_at_unix_ms, and message_count for each participant that has sent at least one accepted message.
3.6 Extension Mode Lifecycle RPCs
ListExtModes, RegisterExtMode, UnregisterExtMode, and PromoteMode manage the lifecycle of non-standards-track (extension) coordination modes. These RPCs are implementation-defined surfaces for registering, discovering, and promoting experimental modes. See RFC-MACP-0002 for extension mode semantics and the relationship between extension and standards-track modes.
3.7 Policy Lifecycle RPCs
Five RPCs manage the governance policy lifecycle (see RFC-MACP-0012):
rpc RegisterPolicy(RegisterPolicyRequest) returns (RegisterPolicyResponse);
rpc UnregisterPolicy(UnregisterPolicyRequest) returns (UnregisterPolicyResponse);
rpc GetPolicy(GetPolicyRequest) returns (GetPolicyResponse);
rpc ListPolicies(ListPoliciesRequest) returns (ListPoliciesResponse);
rpc WatchPolicies(WatchPoliciesRequest) returns (stream WatchPoliciesResponse);RegisterPolicy and UnregisterPolicy mutate the policy registry. GetPolicy and ListPolicies are read-only queries. WatchPolicies is a server-streaming RPC for policy registry change notifications.
See RFC-MACP-0012 Section 7 for registration constraints and evaluation semantics.
3.8 Session Lifecycle Observation RPCs
ListSessions and WatchSessions provide programmatic session lifecycle observation.
rpc ListSessions(ListSessionsRequest) returns (ListSessionsResponse);
rpc WatchSessions(WatchSessionsRequest) returns (stream WatchSessionsResponse);ListSessions returns a bounded page of SessionMetadata for the sessions the runtime currently knows about (active and terminal, subject to whatever retention the runtime applies to terminal sessions). It is paginated: a single response is not required to carry the full result set. The pagination fields are defined in core.proto — page_size and page_token on ListSessionsRequest, next_page_token on ListSessionsResponse. A runtime MUST advertise sessions.list_sessions = true before ListSessions can be assumed interoperable.
Page size. page_size is the maximum number of entries the client wants in one response. page_size = 0 means server-chosen default; a runtime MUST substitute a bounded default rather than returning every known session. A runtime MAY cap the effective page size below the requested value, and the cap MUST be applied silently — clamping is not an error, and the client observes it only as a page smaller than it asked for. A runtime MUST reject a negative page_size.
Continuation. page_token is an opaque continuation token; an empty page_token begins a new traversal. The client MUST pass a non-empty next_page_token back verbatim as the next request's page_token. Tokens are implementation-defined and MAY be short-lived; clients MUST NOT parse them, construct them, or carry one across runtimes. A runtime that cannot decode a supplied page_token — malformed, expired, or issued elsewhere — MUST reject the request with gRPC status INVALID_ARGUMENT; the HTTP binding maps this to 400 Bad Request. INVALID_ARGUMENT here is a transport status, not a MACP error code — the codes in registries/error-codes.md are envelope-scoped, and ListSessions carries no Envelope.
Completion. Only an empty next_page_token means the result set is complete. A short page — one carrying fewer entries than page_size, including an empty one — does not mean completion while next_page_token is non-empty. Clients MUST continue paging until the token comes back empty, and MUST NOT treat page length as a termination signal.
Traversal semantics. A page is not a point-in-time snapshot, and next_page_token is a position, not a snapshot handle. Runtimes SHOULD traverse in a stable order such that a session present for the whole traversal is returned exactly once; a session created or removed mid-traversal MAY or MAY NOT appear, depending on where it falls relative to the current position. Clients that need to observe what changes during or after a traversal SHOULD pair ListSessions with WatchSessions.
WatchSessions is a server-streaming RPC that emits SessionLifecycleEvent notifications. Each event carries an EventType (CREATED, RESOLVED, EXPIRED, SUSPENDED, RESUMED, or CANCELLED), the affected SessionMetadata, and an observed_at_unix_ms timestamp. A runtime MUST advertise sessions.watch_sessions = true before WatchSessions can be assumed interoperable.
Session lifecycle events are ephemeral — they are not persisted and are not available for replay. Clients that disconnect MAY miss events. Control-planes and UIs SHOULD use ListSessions for initial sync and WatchSessions for incremental updates.
4. HTTP Binding
The HTTP binding is OPTIONAL. Implementations providing it MUST preserve Envelope semantics and MUST map error codes to HTTP status codes per the error-codes registry.
Example endpoints:
POST /macp/envelope
POST /macp/session/start
GET /macp/session/{id}
POST /macp/session/{id}/cancel
GET /.well-known/macp.json5. WebSocket Binding
The WebSocket binding is OPTIONAL. Implementations providing it MUST frame each Envelope as a single WebSocket message and MUST preserve per-session ordering.
6. Message Bus Binding
The message bus binding is OPTIONAL. Implementations providing it MUST preserve per-session message ordering and MUST define how authoritative acceptance is established.
Example topics:
macp.signals
macp.sessions.{session_id}Possible systems:
- Kafka
- NATS
- RabbitMQ
7. Transport Selection
Implementations MUST support at least the gRPC binding. Additional transports are OPTIONAL.
- gRPC -- high throughput coordination
- HTTP -- simple integrations
- WebSocket -- interactive coordination
- Message Bus -- distributed systems
8. Security
All transports MUST use encrypted transport. Authentication requirements follow RFC-MACP-0004.