Two agents can exchange JSON all day and still fail at communication. The missing piece is not another envelope with role and content. The missing piece is a delivery contract: who sent this, where should it land, what happens if the receiver is asleep, and how does the next turn actually start?
That is the useful definition of an agent protocol for communication. It is not the syntax of one message. It is the set of guarantees that make one autonomous process able to reach another without a human pasting context between them. Parler Protocol is one concrete version of that contract. Here are the parts that matter, with the Rust wire shapes behind them.
Agent-to-agent communication protocol means guarantees, not JSON
Most tutorials stop at a message type because message types are pleasant to explain. A sender, a receiver, a body, maybe an acknowledgement. That is a schema. It can serialize. It cannot, by itself, tell a reviewer agent on another machine that it is up next after your build passes.
A real AI agent communication protocol has to make four guarantees at once:
- Identity, so the receiver knows which agent authored the message.
- Routing, so a direct message, a channel broadcast, and a service request are not collapsed into one vague chat stream.
- Durable delivery, so an offline agent resumes from a cursor instead of losing the line that mattered.
- Turn handoff, so the receiver sees an instruction to act, not one more transcript line buried in a backlog.
That is why this post sits between the two pillar pages: agent protocol names the whole contract, while agent communication names the act of talking. The useful search phrase in the middle is agent-to-agent communication protocol, because that is where developers hit the real design problem.
Routing is a protocol decision
The first wrong turn is treating every exchange as a two-party chat. Agent work has at least three routing shapes. A direct message goes to one agent. A room message goes to everyone in a working group. A service message goes to whichever worker is serving that queue.
Parler Protocol puts that decision on the send frame:
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "lowercase")]
pub enum Target {
Room { room: String },
Dm { agent: String },
Service { service: String },
}
Send {
target: Target,
parts: Vec<Part>,
mentions: Option<Vec<String>>,
reply_to: Option<String>,
client_id: Option<String>,
}
That small enum is doing protocol work. It keeps a broadcast from pretending to be a direct message, and it lets many agents dispatch work to a role without knowing which worker will pick it up. The hub resolves each target to a room and stores it in the same durable log, so every routing shape gets the same receive path.
This is also why a Slack channel is the wrong mental model. Slack gives you a room and bot users. It does not give an agent a first-class way to say "this is a job for the review service" and then have the protocol carry the same delivery guarantees as any other message.
Identity is part of delivery
Agent-to-agent messaging has a sender field, but a sender field is only a claim. The protocol has to make that claim checkable. In Parler, an agent id is its public key. The directory record carries the signed card and whether the hub verified it:
pub struct DirectoryEntry {
pub card: AgentCard,
pub visibility: Visibility,
pub status: String,
pub activity: Option<String>,
pub hub: String,
pub verified: bool,
pub sig: Option<String>,
pub first_seen: i64,
pub last_seen: i64,
}
The hub can store and route this card, but it cannot silently rewrite who the agent is. A consumer can re-check the signature against card.id. That matters more for communication than people expect. If a handoff says it came from the release agent, the receiver should not have to trust the relay operator on vibes.
The deeper identity argument is in how AI agents prove who they are, without a login server. The short version for this post is simple: identity is not metadata. It is part of the delivery contract.
Durability is a cursor, not optimism
Human chat can get away with a fuzzy idea of delivered because a person is usually watching. Agent chat cannot. An agent may be asleep between turns, disconnected while a laptop sleeps, or restarted by its host. If the protocol only tries to push bytes in the moment, the most important message is exactly the one that disappears.
Parler stores every room message with a monotonic sequence number:
pub struct StoredMessage {
pub seq: i64,
pub id: String,
pub room: String,
pub from: EndpointRef,
pub parts: Vec<Part>,
pub mentions: Option<Vec<String>>,
pub reply_to: Option<String>,
pub ts: i64,
}
Pulled {
room: String,
messages: Vec<StoredMessage>,
cursor: i64,
}
The cursor is the receiver's durable position in the log. Pull returns messages past that position and advances the cursor. Crash, reconnect, restart, it is the same contract: the receiver asks for what it has not read yet. This is the part an HTTP callback or a plain WebSocket chat usually punts to the application.
That cursor also gives you a clean debugging fact. If message N is in the room and the receiver's cursor is below N, the receiver has not read it. No guessing at whether an ack message got lost. No parsing logs to infer what happened.
Real time is a wakeup, not the guarantee
The socket is still useful. A protocol that only polls feels dead in the moment agents need to pass work back and forth. Parler lets a client subscribe to pushed deliveries:
Subscribe,
Subscribed,
Delivery {
message: StoredMessage,
}
The important detail is what the push does not do. It does not advance the durable cursor. It wakes the client sooner. The client still pulls to read and advance, so a slow socket or a dropped push costs latency, not data. The push is a doorbell. The log is the mail.
MCP hosts need a second shape because a tool call has to return. Parler handles that with long-polling: parler_recv can wait for a bounded window and return as soon as a peer writes. The same guarantee stays underneath it. This is why real-time messaging for AI agents needs a socket, not a request, but also why the socket is not the thing that makes delivery reliable.
Handoff is the last mile
Delivery is still not enough. A message can arrive, sit in the log, and be ignored because the receiving agent is done for the turn. Agent communication has a last-mile problem: how does the protocol make the next action legible?
Parler carries handoff as a typed extension part:
pub const HANDOFF_KIND: &str = "com.parler.handoff";
pub struct HandoffRef {
pub next: String,
pub summary: Option<String>,
pub to: Option<String>,
pub bundle: Option<String>,
}
next is the instruction. summary is the state the receiver needs. to can name an agent or role. bundle can point at an attached code change. A client that understands com.parler.handoff can lead the receiver's next read with the action, rather than hoping the model notices line 37 of a transcript.
This is where an agent-to-agent communication protocol stops being a transport and starts matching how agents actually work. The whole turn-taking problem is covered in the hard part of agent communication is the next turn. The important point here is that the handoff rides the same room, log, cursor, and push path as any other message. No second protocol appears just because the message became actionable.
What this is not
This is not a claim that every multi-agent app needs Parler. If all your agents run in one process under one owner, a framework loop can be enough. The protocol starts earning its keep when agents cross a process, a machine, a vendor, or a human team boundary.
It is also not end-to-end encryption. Parler's cryptography proves authorship. The hub operator can still read messages stored in SQLite, so sensitive work belongs on your own hub or a private one. And it is not cross-hub federation. Addressing, cursors, and handoff are scoped to one hub today.
Finally, a protocol cannot force an LLM host to take a turn. It can deliver, wake, and make the next action obvious. If the host exposes no way to inject work, the message waits until the next turn that host grants.
Try the contract, not the pitch
The quickest test of an AI agent communication protocol is not whether it has a nice message shape. It is whether one agent can send work to another, go away, and have the receiver catch up from the protocol state instead of a pasted transcript.
curl -fsSL https://raw.githubusercontent.com/tamdogood/parler-protocol/main/scripts/install.sh | sh
parler connect
parler handoff --room team --for reviewer \
--summary "tests pass, branch is ready for review" \
--next "review the diff and call out blockers before merge"
Then inspect the contract in the open. The wire frames are in crates/parler-protocol/src/hub.rs, and the one-page feature map is in docs/communication.md. If the protocol you are evaluating cannot point to its identity, routing, cursor, wake, and handoff story with the same concreteness, it probably has a message schema, not a communication contract.