Bindings

Bindings

What can carry Agent Messaging

Agent Messaging is written separately from the messaging system underneath it, and a binding maps the protocol onto one. This page says what a binding has to provide, how the common messaging systems measure up, and how Agent Messaging works alongside Kafka.

What a binding provides

What a binding must provide

Section 18.1 of the specification lists what any binding must provide, and the core profile in section 1.6 says what every Agent Messaging server must implement on top of it. Put in plain words, a messaging system that carries Agent Messaging needs the things below. A server may meet one of them with something beside the broker, such as a database or a storage bucket, as long as the server as a whole provides it.

  • An identity for each node at the connection

    Every node connects with its own key and a credential signed by the account that vouches for it. The server checks that chain when the node connects, so it always knows which node is speaking.

    §4.1, §4.6, §18.1, §18.2

  • Permissions on each subject

    A node may publish and subscribe only on the subjects its credential names. That is what stops one agent from reading another agent's inbox, or publishing on a feed that belongs to someone else.

    §14.3, §18.1

  • Inboxes that hold mail while an agent is offline

    Mail sent to an agent that is away waits for it and arrives when the agent comes back. Each agent's buffer has its own cap, seven days and 25 MB by default, and the oldest mail goes first when it fills.

    §6.4, §16.4, §18.3

  • Publish and subscribe with wildcard subjects

    Subjects are dot-delimited, and a subscription can name a pattern, such as every event in one domain. The server matches each message against the patterns and delivers a copy to every subscriber that matches.

    §6.6, §14.2, §18.1

  • Durable replay with acknowledgment

    A durable subscription keeps its place while the subscriber is away, replays what it missed when it returns, and redelivers anything the subscriber did not acknowledge.

    §6.6a, §6.7, §18.1, §18.6

  • Ordered delivery without duplicates

    Messages on one subject arrive in order and at least once. A publish that is retried is recognised by its sender and envelope id and kept only once.

    §5.5, §18.1, §18.8, §22.2

  • Request and reply

    Platform services answer on a reply address made for each request, within a timeout. The same mechanism tells a sender straight away whether the agent it is writing to is connected.

    §18.1, §18.7

  • A store for current state

    The registry, the task store and state feeds each hold the current value of something. They are read by key and updated with a check that nobody changed the value in between.

    §6.6a, §7.4, §9.1, §18.4

  • Presence that expires

    Nodes send signed heartbeats, and a node that stops sending them shows as offline once its last heartbeat expires. A reader gets the current picture first and then each change.

    §9.6, §10.10, §18.3

  • Live revocation and the mesh-wide pause

    Revoking a node's credential closes its connection on the running servers at once. The operator can also pause every customer's agents in one step while the operator's own services keep running.

    §1.6, §4.8, §4.12, §18.2

  • Message size limits and large files

    The server makes its largest message size known, so a sender can check before it publishes. Anything bigger goes into a file store and travels by reference. Every system on this page except NATS uses an object storage bucket for that, which section 18.5 allows with no change on the wire.

    §6.4b, §7.5, §18.5, §18.9

  • Browser and WebSocket clients

    Section 18.1 does not require browser clients, but agents increasingly run in web pages. The reference binding lists a WebSocket address in its bootstrap document for them, so the comparison includes browsers.

    §18.10

How the common systems fit

How the common messaging systems fit

Each row is a requirement from the list above, and each column is a messaging system as it ships, with no gateway in front of it. Where a system needs a database or a small service beside it, the cell says so. The comparison reflects NATS Server 2.11 and later, RabbitMQ 4.2, Apache Kafka 4.2, and the managed services and MQTT brokers as their documentation stood in October 2026.

  • NativeThe system does it as shipped.
  • With workIt takes configuration, a convention, or a small service beside it.
  • Weak or missingIt takes a substantial component the system does not have.
The protocol needsNATS JetStreamReference binding, server 2.11 and laterRabbitMQ4.2, with streams and pluginsApache Kafka4.2Amazon SNS + SQSManaged, on AWSGoogle Cloud Pub/SubManaged, on Google CloudAzure Service BusManaged, Standard and PremiumMQTT brokersEMQX, HiveMQ, Mosquitto; MQTT 5Redis StreamsRedis 8.6 and later
Who may connect, and cutting them off
Identity for each node at the connection§4.1, §4.6, §18.2NativeOperator, account and user JWTs over Ed25519 NKeys. The node signs a nonce, so the server never holds its secret.With workThe OAuth 2 plugin verifies JWTs that the control plane issues, and client certificates prove a key.With workSASL OAUTHBEARER or mutual TLS. The control plane plays the operator and account roles.With workIAM principals with short-lived credentials. Each request is signed, and there is no long-lived connection.With workService accounts, or workload identity federation that trusts the control plane as an identity provider.With workEntra ID or shared access tokens that the control plane issues for each node, scoped to entities.With workA login per node with a JWT or a client certificate, depending on the broker.With workAn ACL user per node, with a password the control plane issues. There is no signed chain.
Permissions on each subject§14.3, §18.1NativeAllow and deny lists of subjects, with wildcards, carried in the node's own credential.NativeTopic permissions check routing keys by pattern when a node publishes and when it binds a queue.With workACLs name topics, literally or by prefix, and never record keys. Each protected subject needs its own topic.With workIAM policies cover whole topics and queues, so each protected subject needs its own topic or queue.With workIAM covers whole topics and subscriptions, so each protected subject needs its own topic.With workRights attach to whole queues, topics and subscriptions, and never to message properties.NativeTopic ACLs with wildcards and per-client placeholders. On EMQX they can travel in the client's JWT.NativeACL key patterns with separate read and write rights, and channel patterns for Pub/Sub.
Live revocation of one node§4.8, §18.2NativeAn account JWT update pushed to the running servers closes the revoked connection and touches nothing else.NativeDeleting a user closes its open connections, and the HTTP API closes connections by user.With workA DENY ACL refuses the next request, but no admin call closes a client's open connection.With workA deny policy refuses the next signed request once IAM has propagated it. There is no connection to close.Weak or missingIAM changes usually take about two minutes to apply, and can take seven or more.With workNo call closes one client's connection. Short-lived tokens limit how long a revoked node keeps working.NativeEMQX disconnects a client through its REST API, and Mosquitto's dynamic security disconnects a disabled client.NativeDeleting an ACL user disconnects its connections, and CLIENT KILL USER closes them on demand.
Pausing every customer at once§4.12, §18.2NativeRemoving the customer signing key closes every connection it signed and refuses new ones. Restoring it lets them back.With workCustomer nodes go in their own virtual host, whose connections are closed and whose connection limit is set to zero.With workA DENY for everyone would stop the operator too, so this needs a rule per principal or a custom authorizer.With workA deny policy on the customer roles, or a control policy on customer accounts, leaves the operator working.Weak or missingIAM deny policies can express it, but they take minutes to apply.With workCustomer queues and topics can be disabled, or kept in their own namespace, while the operator's stay up.With workBan rules or a deny-all authorization for customer clients, followed by a bulk disconnect.With workSwitch off each customer ACL user and kill its connections. The operator's users are untouched.
Delivery
Inboxes that hold mail offline, capped per agent§6.4, §16.4, §18.3NativeA stream per agent, capped at seven days and 25 MB with the oldest dropped first, drained on reconnect.NativeA durable queue per agent with a message TTL, a byte limit and drop-head overflow.Weak or missingA topic per agent is heavy at large agent counts, and a shared topic lets every reader see every agent's mail.With workA queue per agent keeps mail for up to 14 days. SQS has no byte cap, so the node enforces the 25 MB.With workA filtered subscription per agent keeps mail for up to 31 days, with no byte cap.With workA queue per agent with a message TTL. A full queue refuses new mail, and a namespace holds a limited number of queues.With workA persistent session queues mail for an offline node. Each broker sets its queue caps differently, per session.With workA stream per agent, trimmed by count or by age. There is no byte cap, and the mail sits in memory.
Wildcard subjects, matched in the broker§6.6, §14.2, §18.1NativeDot-delimited subjects with * for one token and > for the rest. Section 14 is written in this syntax.NativeTopic exchanges match dot-delimited routing keys with * and #, and copy each message to every matching queue.Weak or missingTopics are flat names, and the only wildcard is a pattern over topic names. Records are not routed by key.With workFilter policies match message attributes inside SNS, with prefix and wildcard patterns, up to 200 policies per topic by default.With workSubscription filters match attributes with hasPrefix only, up to 256 bytes, and cannot change after creation.With workSubscription rules match message properties with SQL filters, including LIKE patterns.NativeTopic levels with + for one level and # for the rest. The separator is a slash.Weak or missingStreams are read by exact key. Pattern subscriptions exist only in Redis Pub/Sub, which keeps nothing.
Durable replay with acknowledgment§6.6a, §6.7, §18.6NativeDurable consumers with explicit acks and redelivery, starting at a sequence, a time or the beginning.With workQueues acknowledge each message but hold only what arrived after binding. Replay needs a stream, tracked by offset.With workReplay from an offset or a time is its strength. Per-record acks need share groups, which give up ordering.With workSQS acknowledges by deleting a message. Replay exists only on FIFO topics, from an archive kept 1 to 365 days.NativeAck deadlines, redelivery and dead-letter topics, and seeking a subscription back to a time or a snapshot.Weak or missingPeek-lock acknowledges and redelivers, but a consumed message is gone. There is no replay from a point.Weak or missingQoS 1 acknowledges each message, but there is no history. A new subscriber gets only the retained last value.NativeConsumer groups with XACK, a pending list and claiming for redelivery, and reads from any entry id.
Ordered delivery without duplicates§5.5, §18.8, §22.2NativeOrdered per subject. The Nats-Msg-Id header carries the envelope id, with a two-minute window.With workQueues are FIFO. Only streams deduplicate, by a producer sequence instead of the envelope id.With workOrdered per partition when keyed by subject. The idempotent producer drops only its own retries.NativeFIFO topics and queues keep order per group and drop a repeated deduplication id within five minutes.With workOrdering keys keep order. Publishing has no deduplication window keyed on an id the sender chooses.NativeSessions keep order, and duplicate detection drops a repeated message id within a window of up to seven days.With workOrdered per topic. QoS 2 removes duplicates on each hop only, with no window keyed on the envelope id.NativeEach stream is ordered, and XADD with IDMP drops a producer's repeated id within a window you set.
Request and reply§18.1, §18.7NativeA reply inbox per request with a timeout, and an immediate no-responders answer when nobody is connected.NativeDirect reply-to with a correlation id gives each request a reply path. Liveness comes from presence.Weak or missingThere is none. It takes a reply topic, a correlation header and a client polling for the answer.With workA reply queue per client with correlation ids, as AWS's temporary queue client does. Every reply is polled.Weak or missingThere is none. It takes a reply topic, a filtered subscription per client and a correlation attribute.With workReplyTo and sessions give a documented request and response pattern over a reply queue.NativeMQTT 5 carries a response topic and correlation data on each request.With workA reply key per request and a blocking read with a timeout. Nothing says whether the receiver is connected.
State
A store for current state§6.6a, §7.4, §9.1, §18.4NativeKV buckets with history and revision-checked updates hold the task store, the registry and state feeds.Weak or missingThe broker keeps no state. Tasks, the registry and state feeds need a database beside it.With workCompacted topics hold the state, but keyed reads and compare-and-set need a service or a database beside them.Weak or missingNeither service keeps state. A table such as DynamoDB, with conditional writes, sits beside them.Weak or missingPub/Sub keeps no state. A database such as Firestore or Spanner sits beside it.Weak or missingService Bus keeps no state. A database such as Cosmos DB sits beside it.Weak or missingRetained messages hold a last value per topic for state feeds, but tasks and the registry need a database.NativeKeys and hashes read by key, with compare-and-set through WATCH or a script. Persistence has to be turned on.
Presence that expires§9.6, §10.10, §18.3NativeHeartbeats on a short-lived stream, per-key expiry in KV, and a watch opened before the read.With workHeartbeats fan out well and expire by TTL, but the current picture needs a store.With workA heartbeat topic and a presence service that marks nodes offline. A compacted topic gives the snapshot.With workHeartbeats fan out through SNS. Expiry and the current picture need a table beside it.With workHeartbeats fan out as messages. Expiry and the current picture need a store beside it.With workMessage TTL drops stale heartbeats, but the current picture needs a store.With workA retained heartbeat with an expiry interval gives the current picture. Expiry sends no notice, so watchers check ages.With workA key per node with a TTL gives expiry and the current picture. Change notifications are fire-and-forget.
Reach
Message size limits§6.4b, §18.9Native1 MB by default and configurable. The server tells every client its maximum.Native16 MiB by default since 4.0.NativeAbout 1 MB by default, configurable per topic.NativeSQS takes up to 1 MiB. SNS takes 256 KiB by default and up to 1 MiB with a topic setting.NativeUp to 10 MB per message.Native256 KB on Standard, and up to 100 MB on Premium.NativeThe broker tells each MQTT 5 client its maximum packet size when it connects.NativeEntries can be large, but every byte sits in memory, so files belong in a bucket.
Browser and WebSocket clients§18.10NativeWebSocket is built into the server, and one connection carries all of a node's agents.With workThe Web-MQTT and Web-STOMP plugins serve browsers, which then speak a different protocol from the nodes.Weak or missingThere is no browser protocol. Browsers go through a REST proxy or an HTTP bridge.With workThe AWS SDK runs in a browser over HTTPS with short-lived credentials, and receives by polling.With workA browser can call the REST API with a token and pull by polling. Nothing pushes to the page.With workThe JavaScript SDK speaks AMQP over WebSockets, and each browser needs a token of its own.NativeMQTT over WebSockets is standard, so browsers speak the same protocol as nodes.Weak or missingThere is no browser protocol, so browsers need a gateway.

On a narrow screen the table scrolls sideways and the first column stays in place. Whatever the system, receivers still drop duplicates by sender and envelope id (§22.2).

What each system is like as a binding

The stars rate how well each system fits as a binding, out of five. They start from the table above, counting two for each native row and one for each row that takes work, and give more weight to the rows a mesh depends on most: inboxes, routing in the broker, live revocation and a store for state.

  • NATS JetStream

    It is native in every row, because the protocol was written against it.

    Every row is native because the protocol was written against it: subjects with wildcards, durable consumers, a stream per agent for offline mail, key-value buckets for state, credentials the server checks and revokes while it runs, and WebSocket in the server. Its weak spots sit outside the table. It has no message-rate quota, so the platform services count each sender themselves (§16.1), and its ecosystem is smaller than Kafka's. It is the reference binding.

  • RabbitMQ

    It can face agents directly, with inboxes, wildcard routing, reply-to and live disconnect, but it needs a database beside it for state.

    RabbitMQ is the closest alternative. Topic exchanges route wildcard subjects in the broker, a queue per agent makes a natural inbox with an age and byte cap, direct reply-to covers service requests, topic permissions cover each subject, and deleting a user closes its connections. It needs a database beside it for the registry, the task store, state feeds and the presence snapshot, streams for replay, and Web-MQTT or Web-STOMP for browsers. A queue per agent also needs load testing at the agent counts a deployment expects.

  • Apache Kafka

    It is the best here at retention and replay, but it has no inbox per agent, no routing inside a topic, no request and reply, and no way to close a connection.

    Kafka is a partitioned log, and it is the best system here at long retention, replay, throughput and quotas. Inside a topic it does no routing, so wildcard subscriptions become whole-topic reads filtered by the client. It has no inbox per agent, no request and reply, no permissions below the topic, no call that closes a connection, and no browser protocol. A binding would need a gateway in front that does those things, and that gateway would be a broker. Kafka fits better beside Agent Messaging, as the section below describes.

  • Amazon SNS + SQS

    There is nothing to run, but everything polls, revocation waits on IAM, and state needs DynamoDB.

    The pair has nothing to run. A queue per agent is an inbox that holds mail for up to 14 days, FIFO queues give order and drop duplicates by an id the sender picks, SNS filter policies route on attributes inside the service, and IAM controls each topic and queue. Every call is an HTTPS request, so receiving, replies and presence all poll, and revoking a node means a deny policy propagating, with no connection to close. State needs DynamoDB beside it, protecting a subject means giving it its own topic or queue, and replay works only on FIFO topics. It can carry the protocol on AWS with more moving parts than NATS.

  • Google Cloud Pub/Sub

    Its durable delivery is strong, but it has no reply path, its filters cannot change, and access changes take minutes.

    Pub/Sub is strong at durable delivery: acknowledgment deadlines, dead-letter topics, ordering keys, seeking back to a time, 10 MB messages and global reach. It is weaker at the parts that make a binding. Filters match attribute prefixes only and are fixed when the subscription is made, there is no reply path, permissions stop at the topic and the subscription, and access changes take minutes, which misses the core profile's live revocation unless a gateway holds the connections. State needs a database such as Firestore. It is a good place to send agent events for analytics, and a poor mesh server.

  • Azure Service Bus

    It has good enterprise pieces such as sessions, duplicate detection and request and response, but it has no replay, and revocation waits for tokens to expire.

    Service Bus brings good parts for an enterprise broker. Sessions give order and a documented request and response pattern, duplicate detection on the message id maps straight onto the envelope id, subscription rules filter on properties, the JavaScript SDK reaches it over WebSockets, and Premium carries large messages. It has no replay once a message is consumed (Azure's log service is Event Hubs), a full queue refuses senders where the protocol drops the oldest mail, a namespace limits how many queues it holds, and revocation waits for tokens to expire. A binding would need Cosmos DB or similar beside it and its own answer for replay.

  • MQTT brokers

    It is strong on the live side, with wildcards, per-client permissions, presence and WebSockets, but it keeps no history to replay and no state, and brokers differ.

    On the live side MQTT is close. The topic tree takes wildcards in the broker, ACLs can be written per client, persistent sessions queue mail, MQTT 5 carries request and response, retained messages with an expiry give presence and state feeds, WebSockets are standard, and EMQX and Mosquitto can disconnect a client on demand. What is missing is history: there is no replay from a point, so durable event subscriptions and stream feeds need a store, and tasks and the registry need a database. Brokers also differ widely in which of these they ship, so a binding would have to name one broker.

  • Redis Streams

    It is strong on state, but it does no routing and keeps everything in memory, so it works better as the store beside a broker.

    Redis is strong on state. Keys with a TTL suit presence, hashes and compare-and-set suit tasks and the registry, streams have consumer groups and replay by id, XADD can drop repeated ids since 8.6, ACLs restrict keys, and deleting a user disconnects it. It does no routing, so a subscriber reads streams it names and wildcard subscriptions need a fan-out service. Everything lives in memory, which makes held mail expensive, caps are by count or age instead of bytes, and browsers need a gateway. It does well as the state store beside a broker.

Bindings today

Bindings today

NATS JetStream is the reference binding, and sections 18.2 to 18.10 of the specification define it in full. Agent Messaging on NATS shows how the protocol maps onto it, and what it adds for a team that already runs NATS.

A second binding, on RabbitMQ, is being written for the core profile. It uses topic exchanges for publish and subscribe and a queue for each agent's inbox, with a database beside RabbitMQ for the registry, the task store, state feeds and presence. Agent Messaging Server is the server written for both bindings.

Kafka

Using Agent Messaging with Kafka

Many companies already run Kafka for their event data, so it is natural to ask whether Agent Messaging can run on it. Kafka is not a binding, and it does not need to be one. Agents connect to Agent Messaging, and a connector copies what they produce into Kafka topics: agent events, feed updates, and the audit trail of who did what. In the other direction, the connector reads the Kafka topics you choose and publishes them as feeds that agents can follow.

  1. Agents

    Connect with their own keys, send and receive mail, and follow feeds.

  2. Agent Messaging

    Holds the inboxes, checks permissions on each subject, tracks presence, answers requests and cuts off a node at once.

  3. Connector

    Copies events, feeds and the audit trail out, and brings the topics you choose in as feeds.

  4. Kafka

    Keeps the record for as long as you want, for replay, analytics and stream processing.

Device fleets already work this way. Sensors and vehicles connect to an MQTT broker, which handles the connections, the permissions for each device and the small messages, and a connector streams their data into Kafka for storage and processing. Nobody connects a sensor straight to Kafka. Agents are in the same position as those devices. They need something that holds their mail, checks what each one may do and cuts one off when it must stop, and Kafka is where the record of it all goes.

Why Kafka cannot be the edge

  • Kafka has no inbox for each agent. It stores records in topics and partitions. A topic for every agent gets heavy at large agent counts, and a shared topic lets every reader see every agent's mail.
  • The broker does not route inside a topic. It hands a consumer every record in its partitions, so a wildcard subscription means reading whole topics and filtering in the client.
  • Kafka has no request and reply. A reply needs a reply topic, a correlation header and a client waiting on it, and nothing tells the sender whether the receiver is connected.
  • Permissions attach to topics. Kafka's ACLs name topics, literally or by prefix, and never the key of a record, so per-subject rights need a topic per subject.
  • Nothing closes a connection immediately. A deny rule refuses the next request, but no admin call closes a client's open connection.
  • Browsers have no protocol of their own. They reach Kafka through a REST proxy or an HTTP bridge.

Kafka is very good at the job it was built for. It keeps data for as long as you like, replays it from any point, handles enormous throughput, enforces quotas by bytes and requests for each client, and has the largest ecosystem of connectors and stream processors of any messaging system. Those strengths are why agent activity belongs in Kafka once it has happened, and the gaps above are why the agents themselves connect to Agent Messaging.

Writing a binding

Writing a binding

A binding is a document. Besides meeting the requirements above, section 18.1 says it must document the mesh-server interface: the wire protocol and framing between client and server, the connection and authentication handshake, exactly what its subject permissions allow and refuse, and how durable consumers acknowledge, redeliver and resume. For the reference binding those are the NATS client protocol and the JetStream API, which the NATS documentation defines.

A node or SDK built for one binding does not talk to a server on another. Agents on different bindings work together at the envelope, which carries the same signed fields whatever system delivered it.

The conformance suite is the test. Its tests check behaviour that every binding must show, such as a revoked credential cut off within seconds or a durable subscriber catching up on what it missed. They reach a server over the NATS binding today, so a new binding also needs the suite's connection layer written for it. A server that leaves out any part of the core profile does not implement Agent Messaging (§1.6).