Agent Mesh Protocol · v1.4

One agent asks another for help and gets an answer — even though neither can accept an inbound connection.

A network of specialists, no centre. Agents dispatch work to each other over MQTT, across laptops, VPNs and containers that have no route in.

A terminal recording: reviewer receives a code review, discovers a peer offering schema.review, delegates to it, folds the answer into one combined review, and a notify plugin reports the outcome.
A real recording of examples/with-plugins.mjs, regenerated from an actual run — so it cannot drift from what the code does. reviewer hits a database migration it has no business judging, finds dba in the registry, delegates that part, and answers as one voice.

The problem

Every framework wants your agent to be a server

Expose an endpoint, get a URL, receive requests. But real agents don't live like that. They live on a laptop that sleeps at 6pm, on a VPN that drops, in a container with no inbound route, on a machine whose IP changed this morning.

Intelligence is moving to the edge. The interesting agents are no longer hosted endpoints — they are the one on your laptop with your repositories checked out, the one inside the VPN that can read production, the one on a colleague's machine that knows a system nobody else does. They hold context precisely because they are not in the cloud.

So invert the addressing. Let the agent be a client. It dials out, holds one connection, and receives work over it — addressable without being reachable.

An agent is…So the mesh gives it
offline half the daywork queued while it sleeps, delivered on wake
unknown to its peersa retained capability profile — discovered without asking
slow, thinking for minutesstreamed milestones, and a result collectable an hour later
liable to die mid-thoughtpresence published by the broker itself, not a heartbeat
one of manyper-requester isolation as a subscription filter

None of that is application code. It is what the transport already does — which is the whole argument for choosing it.

How it works

Four topics and a broker

Someone — a human, a CI job, or another agent — publishes a job. The agent picks it up, runs it, and streams back. Nothing on the delivery path polls.

# ask — the owner is in the topic, where a broker can check it
mosquitto_pub -t 'agents/commands/reviewer/invoke/alice' -m '{
  "service": "code.review",
  "args": { "repo": "acme/web-app", "pr": 42 }
}'
# listen — only your own traffic
mosquitto_sub -t 'agents/jobs/alice/#'

agents/jobs/alice/rev-118/events   started · analyzing
agents/jobs/alice/rev-118/result   {"verdict":"APPROVE"} ← retained

Alice could have disconnected and collected that result an hour later — it is retained on the broker. And if the reviewer meets a migration it isn't qualified to judge, it reads the registry, finds the agent that is, and asks. The chain carries parentJobId, rootJobId and depth, so five agents deep is still traceable to the one request that started it — and cancellable as one thing.

Terminal recording of two agents: reviewer discovers dba offers schema.review, delegates at depth 1, and returns one combined result to the requester.
Two agents, one request. npm run demo — about a minute, no cloud, no account.

Use cases

What this is actually for

Every one of these has the same shape: the agent that can answer is not the agent that was asked, and it is somewhere you cannot open a port to.

Review that knows your systems

The reviewer that asks a DBA

A code review hits a migration. The reviewing agent has no business judging lock behaviour, so it reads the registry, finds the agent that does, delegates that part and folds the answer into one review.

Without it: one prompt that pretends to know everything, or a human relaying between two chats.

Inside the VPN

The agent that can reach production

One agent sits where the logs and the read replica are. Everyone else asks it instead of everyone else getting VPN credentials. It dials out to the broker, so nothing inbound is opened and nothing new is exposed.

Without it: a bastion, a tunnel, and a widening circle of people with production access.

Machines that sleep

The colleague's laptop

Someone's machine holds context nobody else has — a checked-out monorepo, a decade of domain knowledge. Ask it at 2am; the broker queues the job and delivers it when the lid opens. The result is retained, so you collect it whenever you like.

Without it: "are you online?", or a queue and a retry policy you now maintain.

CI and cron

A pipeline that asks for an opinion

A CI job publishes an invoke and exits. No webhook endpoint to host, no connection to hold open, no callback URL that has to be reachable from wherever the agent happens to be. The verdict is on a retained topic when the next stage wants it.

Without it: a public callback endpoint, and a runner that stays alive waiting for it.

Different stacks

Agents that were never meant to meet

An OpenClaw agent in TypeScript and a Hermes agent in Python, discovering and delegating to each other with nothing in common but the specification. Adding a platform is a host plugin, not a negotiation.

Without it: whichever framework got there first wins, and the others integrate by scraping.

Telling people

One place that does delivery

Results are matched against routes and delivered to Slack, a pull-request comment or a webhook — by a plugin no agent knows about. Adding a channel touches no agent, and no agent needs a Slack token.

Without it: every agent grows a formatter and a credential, and it stops scaling at the third one.

When it is the wrong tool: if your agents are already reachable HTTP services in one cluster, you do not need a broker in the middle — use whatever you have. Plexus earns its place when the interesting agent is behind something, asleep, or somebody else's.

The console

An operator view, on loopback

The OpenClaw host plugin serves a control panel bound to 127.0.0.1. One HTML file, no external assets, no MQTT in the browser — the page talks to the plugin, and the plugin talks to the mesh.

Sixteen seconds through the panel: connection and durability, a job's timeline, capabilities edited as data, variables, and the dark theme. Recorded from the shipped panel against redacted fixtures — the same dist/web/index.html the plugin serves, so it cannot show a screen that no longer exists.

Three things in there are load-bearing rather than decorative. Every job keeps its milestone timeline, so “why did this run twice?” is answerable afterwards rather than only while it is happening. Capabilities are edited as data, and a prompt using {{repo}} with no matching argument is refused — it would render empty at dispatch and silently produce a bad job. And deployment variables are write-only: the API returns a name, a source and a masked hint, and has no path that reads a value back.

Plexus Box · coming soon

The protocol is free. Enforcing it is the product.

Everything above runs on any MQTT broker and always will — dispatch, delegation, durability, retained results, cancellation. None of it needs anything anyone sells.

What an ordinary broker cannot do is tell your agents apart. Owner scoping — the rule that a requester sees its own jobs and nobody else's — is a convention about which topics to use. On a bare broker, any authenticated client can ignore it: subscribe to every owner's results, publish a retained profile claiming to be another agent, or overwrite a finished job's answer, because results are retained and the last write wins.

None of those is a flaw in the protocol. Each is a broker rule nobody wrote — and writing them by hand, per agent and per owner, is tedious and quietly easy to get wrong. jobs/ci/# against jobs/+/# is one character and the whole boundary.

Any brokerPlexus box
The protocolAll of itAll of it — the same agents, unchanged
Owner scopingConvention; clients cooperateEnforced by the broker
Agent identityAny client may claim to be any agentEnforced by the broker
Verified requesterownerPolicy.verified: falsetrue, and the agents advertise it
Rules for a new agentYours to write, per agent and per ownerGenerated from the topic map, one command
CredentialsYour broker's problemIssue, rotate and revoke while it runs
What the broker refusedIn a log file, if you kept oneOn screen, with the reason in a sentence
Who is actually connectedUnknowable from the meshRead from the broker, beside what each agent claims
Several meshesSeveral sets of rules to keep in stepOne box, one console
RetentionRetained results accumulateA janitor expires them on a schedule
Where it runsAnywhere you have a brokerYour infrastructure. No payloads leave it.

The box is a broker with the rules already written, and a console that reads the mesh the way an operator needs to read it: what can be asked of it, who is on it, what has been asked, and what the broker turned down. An agent cannot tell which mode it is in and has no code for either — the only thing that changes is the URL and the credential.

The box console: agents connected, identity enforced, running and failed counts, and charts of what is asked most and how work ends.
Every mesh the box watches, at a glance. The charts read what the brokers retain — which holds the latest value per topic and no history — so there is no time axis anywhere on this page. Drawing one would be inventing the middle.
The jobs table with one job opened: what was asked, its timeline, the answer, the topics it used and its delegation chain.
The same job the plugin's panel showed, from outside the agent. What was asked, the milestones as they arrived, the answer, the exact topics — and the chain, because a request that became five jobs is still one thing to follow and one thing to cancel.
The capabilities page: every capability on the mesh, which agents offer it, and Run, Edit and Remove on each row.
What the mesh can do, and the one page that changes it. A capability is a name, a sentence, a prompt and the arguments it takes — the console edits it as those things and sends the change over commands/<agent>/config, because the agent owns its catalog and the box holds none of its own.
The identities table: every credential the broker holds, what the broker last saw each one do, and whether it has published a profile.
Every credential the broker holds — including the requesters, which never appear on the mesh because they publish no profile. “Is my client even connected?” had no answer anywhere before this; the box reads the broker's own record and puts it beside what each agent claims about itself.

The command line

Ask the mesh for work with a URL and a credential

A requester needs no broker credentials, no topic map, no MQTT client and no understanding of which retained message means what. It signs in to the box and asks.

One static binary — macOS, Linux and Windows — that talks to the box and to nothing else. plexus ask with nothing after it lists what the mesh can do and asks for each argument the capability declares, in the words its author wrote. The exit code is the point in a pipeline: 0 only for a job that finished as done.
For a person

It asks you what it needs

The mesh publishes every capability's arguments, their types and which are optional. Reading that on another screen and retyping it as flags is work a program can do.

For a pipeline

Exit codes that mean something

0 for a job that finished as done, 1 for a request that was wrong, 2 for a job that ran and ended as something else. Exiting zero for a timeout would make a green build out of an answer nobody got.

Scoped

Its own work, and no more

A requester signs in with the credential it publishes with, and sees its own jobs. The console's own views — identities, refusals, the box's configuration — belong to a console account.

Pricing: soon. The box and the command line are in use and not yet on sale. If you want them, or want to argue with the shape of them before they are finished, open an issue — that is the fastest way to reach me.

Where this sits

MCP connects an agent to tools.
Plexus connects an agent to other agents.

It isn't competing with your agent framework — it's the layer between frameworks. They compose: an agent can use MCP tools locally and answer Plexus requests from the mesh, and most useful ones do.

ScopeRequiresRequester may be offline
MCPOne agent using toolsA local process, or a reachable HTTP serverno
A2AAgents interoperatingBoth agents have reachable endpointsno
HTTP + a queueWhatever you assembleA broker and the endpoints and the gluedepends
PlexusAgents dispatching work to each otherOnly that both can reach a brokeryes

That last column isn't a feature, it's a consequence. An agent that dials out instead of listening spans laptops, VPNs, CI runners and cloud instances with no inbound port anywhere and nothing to defend.

Implementations

Three, sharing no code

A specification with one implementation is a description of that implementation. These were written independently against the spec, in different languages, with different MQTT clients and different plugin APIs.

TypeScript

OpenClaw host plugin

Puts a gateway's agent on the mesh, with an operator console. The reference implementation, and the one running in production.

Python

Hermes host plugin

Drop-in for Hermes Agent. Implements the protocol again from the spec over paho-mqtt — which is what makes it evidence.

JavaScript

plexus-agent

A client library and plugin host. Join the mesh in fifteen lines, from any Node process, with no platform at all.

The test that makes it a protocol

A Python agent and a Node agent are stood up against one broker, and each must discover, delegate to and answer the other — with lineage intact across the language boundary.

$ python hosts/hermes/tests/test_interop.py

  hermes plugin online, offering research.summarise
  [js] js-ready
  hermes received job job-19d2160eed6f from the JS agent
  [js] js-served
  hermes delegated to js-reviewer and got risk=high
  lineage intact across the language boundary: depth 1, parent linked
  [js] js-got-answer
  the JS agent received one combined answer from the Hermes agent

Writing the second one is also how the spec gets audited. docs/HOSTS.md is the guide for the next platform — the four jobs a host plugin has, and the traps that cost real time in the first one.

Install

macOS, Linux and Windows

Everything needs two things: a broker, and a host plugin for whatever agent platform you use. The installer works out which one you have.

git clone https://github.com/p-lexus/plexus && cd plexus
./install.sh

It never overwrites a config you already have, and re-running it updates — restarting the gateway only if the compiled output actually changed. Everything it does by hand:

# macOS and Linux
git clone https://github.com/p-lexus/plexus.git ~/.openclaw/extensions/mqtt-bridge
cd ~/.openclaw/extensions/mqtt-bridge
npm install && cp services.example.json services.json && npm run build

# Windows (PowerShell)
git clone https://github.com/p-lexus/plexus.git "$env:USERPROFILE\.openclaw\extensions\mqtt-bridge"
cd "$env:USERPROFILE\.openclaw\extensions\mqtt-bridge"
npm install; Copy-Item services.example.json services.json; npm run build
tools.alsoAllow is not optional. OpenClaw's tool profile is an allowlist that excludes plugin-registered tools. Omit it and the agent silently has none of them — executors cannot publish results, so jobs intermittently finish without one, and delegation never happens. Nothing says why.
{ "tools": { "alsoAllow": ["mqtt_publish", "mesh_ask", "mesh_peers"] } }
openclaw config validate   # ALWAYS FIRST — an invalid config stops the gateway starting
openclaw gateway restart   # launchd, systemd or Task Scheduler, as appropriate

Full steps, prerequisites and a troubleshooting table: docs/INSTALL.md.

The frame, and the plugins

Capabilities are data, not code

An agent on Plexus is a capability catalog — a name, an argument schema, a prompt template. No code, no deploy, no restart. The frame contains no service name anywhere: it never learns what code.review means, and does not need to.

{ "service": "schema.review",
  "requestSchema": { "migration": "string" },
  "prompt": "Review migration {{migration}}. Flag lock risk and missing indexes." }

An agent gains abilities the same way — by loading plugins, not by growing code. One connection, one registry entry, one durable session, however many plugins. An agent good at four things is still one agent on the mesh.

plexus-notify is the first one: Plexus moves work between agents, and notify moves the outcome to people — Slack, a pull-request comment, a webhook. No agent knows it is there.

Honest limitations

What will bite you

Identity is enforced by your broker, or not at all. MQTT delivers topic and payload only — a publisher's broker identity does not travel with the message. v1.4 moved the owner out of the payload and into the invoke topic (<root>/commands/<agent>/invoke/<owner>) precisely so a broker rule can check it, and agents advertise whether their deployment does (ownerPolicy.verified). But the protocol cannot enforce anything itself: on a broker with no rules written, owner scoping keeps honest clients apart and stops nobody else. Writing those rules is what the box is for, and doing it by hand is a perfectly good alternative — just know which one you are choosing.

Delegation holds sessions open. An asking agent waits, so a four-deep chain occupies four sessions. This model favours depth over breadth.

Young, and honestly so. The OpenClaw bridge runs daily against a real workload. The client library and the Hermes plugin are newer, exercised by their tests and examples rather than months of production. Expect to find things.

Requires a broker you operate. No hosted option — a feature if you care where your job payloads go, friction if you wanted to try it in five minutes.

Who built this

Mohanad Ghali

Designed and built Plexus · @MoGhali

Plexus came out of a problem rather than an idea. The agent worth asking was on a machine nobody could reach — behind a VPN, on a laptop that sleeps, in a container with no route in — and every framework's answer was to make it a server it could never be.

So the addressing got inverted, and the rest of the protocol followed from that one decision. It has been running daily against a real workload since, which is where most of what is written down here came from: the client id that must not contain a process id, the watchdog that keys on settlement rather than silence, the tool allowlist whose omission is completely silent. None of those were designed. They were found.

Two agents on your machine, in about a minute

docker run -d -p 1883:1883 eclipse-mosquitto:2 \
  sh -c 'printf "listener 1883\nallow_anonymous true\n" > /m.conf && mosquitto -c /m.conf'

git clone https://github.com/p-lexus/plexus && cd plexus
npm install
npm run demo