Skip to navigation

RFC-5: Agent Mesh Protocol

Specification for the AMP wire protocol
View as Markdown

Abstract

The Agent Mesh Protocol (AMP) defines how agents and people exchange messages and tasks through a broker. Participants hold one outbound session each. The broker resolves addresses, routes tasks by job, stores them durably with leases, and enforces policy.

Changelog

VersionDateChanges
v0.12026-10-02Initial draft.
v0.22026-10-05Scopes: an optional partition of a namespace (section 3.5), the envelope and task scope field, and scope checks in routing and policy. Scope grants (section 11.1): grant on granted and deliver, grant_s in welcome limits, and the broker’s JWKS endpoint.

1. Introduction

The Agent Mesh Protocol (AMP) is an open protocol for communication between autonomous agents, and between agents and people, through a broker. Each participant holds one outbound connection to a broker. The broker resolves addresses, routes work to participants by job, stores tasks durably, and enforces policy.

AMP complements two existing protocols:

  • The Model Context Protocol (MCP) connects an agent to its tools and data. AMP does not replace it.
  • The Agent2Agent protocol (A2A) connects agents point to point over HTTPS. AMP reuses A2A’s task states and part types, and defines a gateway so the two interoperate (section 15).

1.1 Design principles

  1. Dial out only. A participant never needs to accept inbound connections. It works behind NAT, inside private networks, and on intermittent hosts.
  2. Address work, not hosts. A sender can address a job. The broker chooses who does the work.
  3. Durable by default. A task outlives the connection, the process, and the host that started it.
  4. One point of policy. Authorization, rate limits, and budgets are enforced at the broker, so a participant cannot bypass them.
  5. Opaque participants. A participant exposes jobs, never its internals: no shared memory, prompts, or tools.
  6. Transport neutral. AMP defines messages and semantics. Bindings map them onto a transport (section 4) and onto broker implementations (Appendix C).

1.2 Conventions

The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL are to be interpreted as described in RFC 2119 and RFC 8174 when, and only when, they appear in all capitals.

  • All objects are JSON (RFC 8259) encoded as UTF-8. Field names are snake_case.
  • Timestamps are RFC 3339 strings in UTC.
  • Durations are integers in seconds unless the field name ends in _ms.
  • The normative schema is the JSON Schema. Where this document and the schema disagree, the schema wins.
  • Receivers MUST ignore unknown fields. Senders MUST NOT rely on a receiver understanding a field the version it negotiated does not define.

2. Roles and architecture

RoleDescription
ParticipantAn agent or a human client. Holds one session with one broker. Has one address
BrokerAccepts sessions, authenticates participants, resolves addresses, routes and stores messages and tasks, enforces policy
GatewayA participant that bridges another protocol (for example A2A) into the mesh

A broker has four logical components. An implementation MAY split or merge them.

ComponentResponsibility
RegistryParticipant cards and presence
Task storeTask records, state history, and leases
Policy engineAuthorization, rate limits, budgets
RouterResolution of a target into deliveries

Brokers MAY federate (section 13). A participant always talks to exactly one broker; federation is invisible to it.

3. Identifiers and addressing

3.1 Namespaces

A namespace is an isolation boundary. Addresses, jobs, and topics are scoped to one namespace. A namespace name MUST match ^[a-z0-9][a-z0-9-]{0,62}$.

3.2 Participant addresses

A participant address is <namespace>/<name>, where <name> matches ^[a-z0-9][a-z0-9._-]{0,127}$. Example: acme/analyst.

The broker assigns or confirms the address during session setup (section 5.1). A participant MUST NOT choose an address outside the namespaces its credential permits.

3.3 Targets

The to field of an envelope is a target:

FormExampleResolves to
Participantacme/analystThat participant
Jobjob:summarize or job:acme/summarizeOne participant that advertises the job (section 9)
Topictopic:documents or topic:acme/documentsEvery subscriber of the topic

A job or topic without a namespace resolves in the sender’s namespace. A target in another namespace MUST be fully qualified, and the broker MUST reject it unless a policy grant allows it (section 11).

Job and topic names match ^[a-z0-9][a-z0-9._-]{0,127}$.

3.4 Message and task identifiers

  • id (message): chosen by the sender, unique per sender for at least 24 hours. RECOMMENDED: a UUIDv7 or ULID.
  • task_id: chosen by the broker when a task is created. Opaque to participants.

3.5 Scopes

A broker MAY partition a namespace into scopes. A scope bounds a piece of work: who it may reach, and what the broker lets it read and write.

  • A scope name matches ^[a-z0-9][a-z0-9._-]{0,127}$ and is unique within its namespace.
  • The broker maps every credential to a set of scopes in its namespace (section 10). The set MAY be empty.
  • A participant with no scopes belongs to the namespace’s default scope, named default. A participant with one or more scopes does not. default is reserved and MUST NOT name another scope.
  • A participant keeps one address in every scope it belongs to.
  • The broker lists the participant’s scopes in welcome (section 5.1).

In a partitioned namespace, the scope travels with every interaction. Every envelope a participant sends MUST set scope, every envelope the broker delivers carries it, and every task records it. The broker MUST NOT infer a missing scope.

A broker that does not partition namespaces MUST reject an envelope that sets scope with invalid_envelope. It MUST NOT deliver it unscoped.

4. Transport

4.1 Sessions

A session is one long-lived, ordered, bidirectional stream of frames between a participant and a broker. AMP defines two transport bindings. A broker MUST support at least one. A broker SHOULD support WebSocket.

BindingDetails
WebSocketRFC 6455. Subprotocol amp.v1alpha1. One frame per text message, encoded as JSON
gRPCBidirectional stream amp.v1alpha1.BrokerService/Session in amp.proto. Each stream message wraps one frame

All sessions MUST run over TLS 1.2 or later.

4.2 Frames

Every frame is a JSON object with a type and a session-unique seq (a positive integer, increasing per direction).

TypeDirectionPurpose
helloparticipant to brokerOpen a session (section 5.1)
welcomebroker to participantAccept a session
sendparticipant to brokerSubmit an envelope
acceptedbroker to participantEnvelope accepted (or task created)
deliverbroker to participantDeliver an envelope
ackparticipant to brokerDelivery processed
nackparticipant to brokerDelivery not processed; redeliver later
claimparticipant to brokerClaim an offered task
grantedbroker to participantLease granted
heartbeatparticipant to brokerKeep the session and any leases alive
releaseparticipant to brokerGive up a lease
subscribe / unsubscribeparticipant to brokerManage topic subscriptions
queryparticipant to brokerQuery the registry or a task
resultbroker to participantAnswer to query, subscribe, or unsubscribe
erroreitherA failure, referencing the seq that caused it
goodbyeeitherClose the session

A frame that answers another carries re, the seq of the frame it answers.

5. Session lifecycle

5.1 Opening

  1. The participant opens the transport and sends hello:
{ "type": "hello", "seq": 1, "versions": ["0.1"], "credential": { "kind": "bearer", "token": "…" }, "card": { … } }
  1. The broker authenticates the credential (section 10), selects the highest common version, registers the card, and replies with welcome:
{ "type": "welcome", "seq": 1, "re": 1, "version": "0.1", "session_id": "s_7h2", "address": "acme/analyst", "scopes": ["research", "sales"],
"heartbeat_interval_s": 10, "limits": { "max_envelope_bytes": 1048576, "max_concurrent_leases": 4 } }
  1. If authentication fails or no version is common, the broker sends error and closes the transport.

A participant MUST NOT send any frame other than hello before it receives welcome.

5.2 Heartbeats

The participant MUST send heartbeat at least once per heartbeat_interval_s. A broker MUST consider the session dead after three missed intervals. A heartbeat extends every lease the participant holds (section 8.3).

5.3 Closing and reconnecting

Either side MAY send goodbye and close. On reconnect, a participant sends a new hello. The broker MUST redeliver every envelope that it delivered and that was not acknowledged. A new session has a new session_id; leases from the old session survive only until they expire.

6. Objects

6.1 Card

A card describes a participant. It is sent in hello and held by the registry while the session is alive.

FieldTypeRequiredDescription
namestringyesDisplay name
descriptionstringnoWhat the participant does
kindagent | human | gatewayyesParticipant type
jobsJob[]noJobs the participant accepts tasks for
acceptsstring[]noPart types it accepts. Default: ["text"]. A task addressed to a connected participant that does not accept one of its part types is rejected, naming the part type
max_concurrentintegernoMaximum tasks leased at once. Default 1
subscriptionsstring[]noTopics to subscribe to on connect
metadataobjectnoFree-form, opaque to the broker

A Job:

FieldTypeRequiredDescription
namestringyesJob name (section 3.3)
descriptionstringnoWhat the job does
input_schemaobjectnoJSON Schema for the task’s data parts
output_schemaobjectnoJSON Schema for result data parts
examplesstring[]noExample requests

6.2 Envelope

FieldTypeRequiredDescription
idstringyesSender-chosen message ID (section 3.4)
fromaddressbroker-setAuthenticated sender. The broker MUST overwrite any value the participant sends
totargetyesSection 3.3
kindmessage | task | status | eventyesSection 7
task_idstringfor status; set by broker on taskThe task this envelope belongs to
reply_tostringnoThe id of the envelope this answers
stateTaskStatefor statusThe task state this envelope reports or requests (section 8.1)
artifactobjectno, status only{ "artifact_id", "name", "final" }. The artifact’s content is parts
ttl_sintegernoLifetime. Default 3600. Maximum 604800
sent_attimestampbroker-setWhen the broker accepted it
partsPart[]yes, at least one (except status)Content
traceobjectnotraceparent and tracestate per W3C Trace Context
scopestringin a partitioned namespaceThe scope this envelope belongs to (section 3.5). A status carries its task’s scope
metadataobjectnoFree-form, delivered unchanged

6.3 Part

typeFieldsDescription
texttext (string), media_type (default text/plain)Text content
fileuri (string), name, media_type, size_bytes, sha256A file by reference. Content MUST NOT be inlined
datadata (object), schema (URI, optional)Structured JSON

A receiver that does not accept a part type MUST answer with error code unsupported_part.

6.4 Task

FieldTypeDescription
task_idstringBroker-assigned ID
namespacestringOwning namespace
requesteraddressWho created it
targettargetThe original to
scopestring | absentThe scope the task was sent in. Absent only in a namespace without scopes
assigneeaddress | nullWho holds or last held the lease
stateTaskStateSection 8.1
attemptintegerNumber of leases granted so far
max_attemptsintegerDefault 3
created_at, updated_attimestamp
expires_attimestampcreated_at plus ttl_s
historyStatus[]Every state change, oldest first
artifactsArtifact[]Results produced so far

A Status is { "state", "at", "by", "message" }, where message is an optional Part array.

An Artifact is { "artifact_id", "name", "parts", "final" }. final is true on the last chunk of a streamed artifact.

6.5 Error

FieldTypeDescription
codestringSection 12
messagestringHuman-readable
retryablebooleanWhether the same request may succeed later
retry_after_sintegerOptional backoff hint

7. Messaging

7.1 Envelope kinds

KindPurposeDelivery
messageA one-off message, optionally answered with reply_toTo the resolved target
taskA request for work. Creates a taskSection 8
statusA state change, progress, or artifact on an existing taskTo the requester, and to the assignee when it comes from the requester
eventA notificationTo every subscriber of the topic

7.2 Sending

A participant sends send with an envelope. The broker MUST, in order:

  1. Validate the envelope against the schema. On failure: invalid_envelope.
  2. Set from and sent_at.
  3. Evaluate policy (section 11). On failure: the policy error code.
  4. Resolve the target. A participant target that does not exist: unknown_target.
  5. Persist the envelope (and, for task, create the task).
  6. Reply accepted with { "id", "task_id"? }.

A broker MUST NOT deliver an envelope before it has sent accepted, and MUST NOT deliver an envelope it rejected.

7.3 Delivering and acknowledging

The broker sends deliver with the envelope and a delivery_id. The participant MUST answer with ack or nack referencing that delivery_id. Unacknowledged deliveries are redelivered after ack_wait_s (default 30) and on reconnect.

event envelopes are delivered to each subscriber independently. A subscriber that joins later does not receive earlier events unless it asks for replay with subscribe and from (a timestamp or sequence).

8. Tasks

8.1 States

submitted, working, input-required, completed, failed, canceled, rejected.

completed, failed, canceled, and rejected are terminal. A broker MUST reject any change to a task in a terminal state with task_final.

FromToCaused by
(new)submittedA task envelope is accepted
submittedworkingA lease is granted
workinginput-requiredThe assignee sends status with that state
input-requiredworkingThe requester replies with a status or message that sets task_id
workingcompleted, failed, rejectedThe assignee sends status
workingsubmittedThe lease expires or is released, and attempt is below max_attempts
workingfailedThe lease expires and attempt equals max_attempts
any non-terminalcanceledThe requester sends status with state: canceled
any non-terminalfailedexpires_at passes

8.2 Offering and claiming

When a task is submitted, the router selects a participant (section 9) and delivers the task envelope to it as an offer. The offered participant claims it with claim { task_id } or declines with nack. On a claim, the broker grants a lease with granted { task_id, lease_id, expires_at } and moves the task to working. In a partitioned namespace, granted also carries a scope grant (section 11.1).

A participant MUST NOT send status for a task without a valid lease, except a requester sending canceled or a reply to input-required.

8.3 Leases

  • A lease lasts lease_s (default 60).
  • Every heartbeat extends every lease the session holds by lease_s from receipt.
  • release { task_id } ends a lease and returns the task to submitted.
  • When a lease expires, the broker MUST treat any later status from the old holder as lease_lost.
  • At most one valid lease exists per task.

8.4 Progress and artifacts

The assignee reports progress with status envelopes in state working. It attaches results with status envelopes that set artifact; the artifact’s content is the envelope’s parts. A streamed artifact is sent as several envelopes with the same artifact_id, the last with final: true. The broker appends each artifact to the task and forwards the status to the requester.

8.5 Reading a task

A requester or assignee MAY read a task with query { "task": "<task_id>" }. The broker returns the Task object in result.

9. Routing

For a job target, the broker MUST choose among eligible participants:

  1. In the resolved namespace.
  2. With a live session.
  3. Advertising the job.
  4. Accepting every part type in the envelope.
  5. Holding fewer leases than its max_concurrent.
  6. Permitted by policy to receive from the sender.
  7. A member of the envelope’s scope, or of the default scope when the envelope has none.

Among eligible participants, a broker SHOULD choose the one with the fewest active leases, breaking ties round robin. A broker MAY use another strategy and MUST document it.

If no participant is eligible, the task stays submitted and is offered when one becomes eligible, until expires_at.

A broker MUST NOT offer the same task to two participants at once.

10. Authentication and identity

  • A participant authenticates in hello with a credential. AMP defines bearer (a token) and mtls (the TLS client certificate). A broker MAY define others.
  • The broker MUST map every credential to exactly one address and a set of permitted namespaces.
  • Credentials SHOULD be short-lived. A RECOMMENDED pattern is token exchange: the participant presents a credential it already holds from its platform, and the broker’s auth service issues a broker-scoped token.
  • The broker MUST set from from the authenticated identity, never from participant input.
  • A gateway MAY act for external parties. It MUST record the external identity in metadata.on_behalf_of, and policy decides whether a gateway may do so.

11. Policy

The broker MUST evaluate policy on every send, claim, and subscribe. AMP specifies the decisions, not the policy language.

DecisionInputsError
May sendsender, target, kindforbidden
May receivecandidate participant, sender(not eligible for routing)
Cross-namespacesender namespace, target namespaceforbidden
Ratesender, kind, windowrate_limited (with retry_after_s)
Budgetnamespace, task cost estimatebudget_exhausted
May subscribeparticipant, topicforbidden

A broker MUST NOT reveal through errors whether a target exists in a namespace the sender may not address. It answers forbidden for both.

A broker that partitions namespaces (section 3.5) MUST also apply these scope rules on every send:

RuleError
Every envelope MUST set scopeinvalid_envelope
The sender MUST be a member of the scopeforbidden
A participant target MUST be a member of the scopeforbidden
A status or a reply MUST carry the scope of its task or of the envelope it answersforbidden

A participant in no scope sends in default and reaches only default. The broker answers forbidden for a target outside the scope whether or not the target exists.

11.1 Scope grants

A scope grant lets a participant prove to another service that it is doing current work in a scope. A broker that partitions namespaces MUST issue grants. Services outside the broker, such as a document store, verify them without calling the broker.

A grant is a JWS in compact form, signed with Ed25519 (alg: EdDSA).

FieldWhereValue
typheaderamp-grant+jwt
kidheaderThe broker key that signed it
issclaimThe broker’s URL
audclaimThe audience the broker is configured for
subclaimThe participant’s address
scopeclaimThe scope
task_id or message_idclaimThe work the grant is bound to. Exactly one is set
iat, expclaimIssue time and expiry

The broker issues grants as follows:

EventGrant
A claim succeedsgranted carries grant, bound to the task
A message is delivereddeliver carries grant, bound to the message
A task is offeredNo grant. The participant gets one when it claims
A lease is extended and the current grant is past half its lifeThe broker sends granted again, without re, with the same lease_id, the new expires_at, and a fresh grant
A lease is lost or released, or the task is finalNo new grant

A grant expires grant_s seconds after it is issued. The broker states grant_s in welcome limits. RECOMMENDED: 300.

The broker MUST publish its public keys as a JWK Set at /.well-known/jwks.json on its HTTP listener, and MUST keep a retired key there until every grant it signed has expired. A verifier MUST check the signature, aud, exp, and that scope matches the resource it guards. It SHOULD also check that sub is still a member of the scope.

12. Errors

CodeRetryableMeaning
invalid_framenoThe frame is malformed
invalid_envelopenoThe envelope fails the schema
unsupported_versionnoNo common protocol version
unauthenticatednoCredential missing or invalid
forbiddennoPolicy denies the action
unknown_targetnoNo such participant
unsupported_partnoThe receiver does not accept a part type
too_largenoOver max_envelope_bytes
rate_limitedyesToo many requests
budget_exhaustedyesSpend limit reached
task_not_foundnoNo such task, or not visible to the caller
task_finalnoThe task is in a terminal state
lease_lostnoThe caller’s lease is gone
concurrency_limityesClaim would exceed max_concurrent
unavailableyesThe broker is temporarily unable to serve

13. Federation

Brokers MAY federate to span regions or organizations.

  • Every namespace has one home broker, which owns its task store and evaluates its policy.
  • A broker that receives a send for a namespace it does not own MUST forward it to the home broker and relay the result.
  • Federated brokers exchange registry summaries (jobs and presence per namespace) so routing decisions can include remote participants.
  • Brokers authenticate each other with mutual TLS.

Federation does not change the participant-facing protocol.

14. Delivery semantics

  • At least once. A participant MUST tolerate duplicate deliveries. It SHOULD deduplicate on (from, id).
  • Idempotent tasks. A broker MAY re-offer a task after a lease expires. A participant SHOULD make work idempotent by task_id.
  • Ordering. Envelopes from one sender to one receiver are delivered in send order. No other ordering is guaranteed.
  • Limits. A broker MUST reject an envelope larger than max_envelope_bytes with too_large. The default is 1 MiB. Large content goes by file part.
  • Expiry. A broker MUST NOT deliver an envelope after its ttl_s has passed.

15. Interoperability with A2A

A gateway exposes mesh jobs to A2A clients, and A2A agents to the mesh.

15.1 Inbound (A2A client to mesh)

  • The gateway publishes an A2A agent card whose jobs are the mesh jobs it exposes.
  • message/send and message/stream create a task envelope addressed to the job.
  • tasks/get maps to query { task }. tasks/cancel maps to a status envelope with state: canceled.
  • message/stream events are the task’s status envelopes.

15.2 Outbound (mesh to A2A agent)

The gateway registers as a participant advertising the remote agent’s jobs. When it is offered a task, it claims it, calls the remote agent’s message/send, and translates the response into status envelopes.

15.3 Mapping

AMPA2A
Task statesTask states with the same names (A2A’s auth-required maps to input-required with metadata.reason = "auth")
text, file, data partsTextPart, FilePart (URI form), DataPart
ArtifactArtifact
Card jobsAgent card jobs

16. Observability

  • A sender SHOULD set trace.traceparent. The broker MUST forward trace unchanged and SHOULD record spans for policy, routing, and leasing.
  • A broker SHOULD expose, per namespace and job: queue depth, time to claim, lease expiries, task outcomes, and rejections by error code.

17. Security considerations

  • Impersonation. Prevented by broker-set from (section 6.2). Receivers MUST trust from only as delivered by their broker.
  • Content injection. Parts are untrusted input. A participant MUST treat delivered text, files, and data as data, not instructions, unless its own policy says otherwise.
  • File references. A file URI can point anywhere. A receiver SHOULD fetch only from origins it trusts and SHOULD verify sha256 when present.
  • Enumeration. Section 11 forbids existence leaks across namespaces.
  • Replay. Deduplication by (from, id) within 24 hours bounds replay. Credentials SHOULD be short-lived.
  • Denial of service. Rate limits, budgets, max_concurrent, and max_envelope_bytes bound the load one participant can create.

18. Versioning and extensions

  • The protocol version is MAJOR.MINOR. Drafts below 1.0 use the transport name v1alpha1 (the WebSocket subprotocol and the gRPC package); 1.0 will use v1. A minor version only adds optional fields and frame types. A major version may break compatibility.
  • Version negotiation happens in hello and welcome (section 5.1).
  • Extensions use fields and frame types prefixed with x- or a reverse-DNS name (com.example.priority). A receiver MUST ignore extensions it does not understand.

Appendix A. Example: delegating by job

analyst → broker send { id: m1, to: job:summarize, kind: task, parts: [text "Summarize Q3"] }
broker → analyst accepted { id: m1, task_id: t41 }
broker → writer deliver { delivery_id: d1, envelope: {… task_id: t41 } }
writer → broker claim { task_id: t41 }
broker → writer granted { task_id: t41, lease_id: l1, expires_at: … }
writer → broker send { id: m2, to: acme/analyst, kind: status, task_id: t41, state: working, parts: [text "Reading 12 files"] }
broker → analyst deliver { … status working … }
writer → broker send { id: m3, to: acme/analyst, kind: status, task_id: t41, state: completed, artifact: { artifact_id: a1, name: summary, final: true }, parts: [text "…summary…"] }
broker → analyst deliver { … status completed … }

Appendix B. Schemas

Appendix C. Broker bindings (informative)