hermes-mesh
Agent-to-agent session relay for Hermes fleet agents.
Standard A2A is request/response — fine for one-shot jobs, inadequate for
conversational fleet coordination. hermes-mesh adds session-preserving
communication: when one agent dispatches to another, the recipient knows
who asked, what they're responding to, and what action to take.
Tools
mesh_list— list all agents in the local fleet mesh vault.mesh_register— register or update an agent identity in the local mesh vault.mesh_send— send a session-preserving message to another fleet agent.mesh_deregister— remove an agent identity.mesh_sync— sync one or all peer identities from a mesh-peer-registry into the local vault.mesh_publish— publish this agent's webhook URL and Ed25519 public key to a mesh-peer-registry.mesh_health— return mesh health and metrics summary.mesh_refresh_identities— clear the identity cache.
Caller: mesh_send(agent="britney", message="Review this plan")
│
├─ 1. Resolves Britney's identity from the local fleet vault
├─ 2. Pads [mesh][from:linda][to:britney][id:uuid][action:do][reply:yes]
├─ 3. Ed25519 signs with the sender's own private key
└─ 4. POSTs to Britney's mesh adapter endpoint
Britney's gateway receives the message on its mesh platform adapter,
routes it into her active session, and she sees it as an inbound mesh
trigger with full sender context.
mesh_send returns a delivery result with state, status (delivered,
queued, or error), message_id, and task_id. When MESH_OUTBOX_ENABLED=1,
a failed delivery can be queued and retried in the background instead of
returning an immediate hard error.
What it does NOT do
This plugin is a mesh layer, not a full A2A implementation. Standard A2A
operations (discover, call, serve, JSON-RPC, Agent Cards) are handled by the
hermes-agent-a2a plugin, which
provides Google A2A 1.0 compliance. The upstream hermes-agent core does not
provide A2A support.
Install
pip install hermes-mesh
Or from source:
git clone https://github.com/emiltsoi/hermes-mesh.git
cd hermes-mesh
pip install -e .
Configure
1. Enable the mesh platform adapter
Add hermes-mesh to plugins.enabled and enable the mesh platform in
config.yaml:
plugins:
enabled:
- hermes-mesh
platforms:
mesh:
enabled: true
extra:
host: 0.0.0.0
port: 8744
route: receive # listens on /mesh/receive
agent_name: agent0 # local agent name
# Authentication sentinel. Any non-empty value enables Ed25519 verification;
# "INSECURE_NO_AUTH" disables it (loopback-only).
secret: true
# Ed25519 private key PEM path (default: ~/.mesh/keys/<agent_name>.pem).
# private_key_path: /path/to/key.pem
# Optional mesh-peer-registry for syncing/publishing peers.
# registry_url: https://registry.example.com
# allow_insecure_registry: false
# registry_pin: sha256-hex-of-server-certificate-spki
sign_timestamp: false # Set to true to include X-Mesh-Timestamp in the signed payload
# Optional session routing and float source.
target_session: "telegram:dm:<chat_id>"
telegram_bot_token: "..."
telegram_default_chat_id: "..."
# Replay / rate-limit / outbox tuning.
replay_window_size: 10000
replay_window_ttl: 300
rate_limit_per_minute: 0
outbox_interval: 30
Each agent's gateway should listen on its own mesh port. The default port
is 8645.
2. Set up fleet identity
Each agent needs an identity in $HERMES_HOME/fleet/mesh/agents/<name>/identity.yaml:
id: britney
name: britney
description: Principal SWE — Orchestrator
transports:
hermes_webhook:
protocol: hermes-webhook
url: http://127.0.0.1:8745/mesh/receive
auth:
public_key: |
-----BEGIN PUBLIC KEY-----
<britney's-ed25519-public-key>
-----END PUBLIC KEY-----
The hermes_webhook.url must point at the target agent's mesh adapter
endpoint (/mesh/<route>). The hermes_webhook.auth.public_key is the
target's Ed25519 public key; messages are signed with the sender's
own private key for per-agent Ed25519 authentication.
The local vault is the runtime source of truth. To discover peers from a
mesh-peer-registry, call mesh_sync(name="...") or mesh_sync() to pull
all peers into the local vault. The adapter verifies inbound X-Mesh-Signature
headers with the public key in the cached identity.
Mesh delivery is one-way inbound into the target agent's session. If the
agent generates a reply, the platform send() call is intentionally a
no-op; the agent must explicitly call mesh_send(agent="...", message="...")
to send a response back through the mesh.
3. Register local agents
You can also register identities at runtime:
mesh_register(name="britney", url="http://127.0.0.1:8745/mesh/receive", public_key="...", role="agent")
4. Environment
export MESH_AGENT_NAME=linda # Who the sender is
export MESH_WEBHOOK_DELIVERY_RETRIES=3 # Delivery retry count
export MESH_WEBHOOK_DELIVERY_BACKOFF=1.0 # Initial retry backoff in seconds
export MESH_WEBHOOK_DELIVERY_TIMEOUT=10 # Per-attempt timeout
export MESH_WEBHOOK_ALLOW_LOOPBACK=0 # Set to 1 to allow deliveries to loopback/private targets
export MESH_REGISTER_ALLOW_LOOPBACK=0 # Set to 1 to let mesh_register store loopback URLs
export MESH_IDENTITY_CACHE_TTL=1.0 # Identity YAML cache TTL in seconds (0 disables)
export MESH_IDENTITY_CACHE_MAXSIZE=256 # Identity cache max entries
export MESH_VAULT_PATH=~/.hermes/fleet # Custom mesh vault root
export MESH_SIGN_TIMESTAMP=0 # Set to 1 to include X-Mesh-Timestamp in the signed payload
export MESH_REGISTRY_PIN=sha256-hex-of-spki # TLS pinning digest for mesh-peer-registry
export MESH_REGISTRY_ALLOW_INSECURE=0 # Set to 1 to allow http:// registry URLs
export MESH_OUTBOX_ENABLED=0 # Set to 1 to queue failed sends for retry
export MESH_OUTBOX_PATH=~/.hermes/fleet/mesh/outbox
export MESH_OUTBOX_MAX_ATTEMPTS=5
export MESH_OUTBOX_BACKOFF=5.0
export MESH_REPLAY_WINDOW_SIZE=10000
export MESH_REPLAY_WINDOW_TTL=300
export MESH_RATE_LIMIT_PER_MINUTE=0 # 0 disables
export MESH_OUTBOX_INTERVAL=30 # Outbox retry sweep interval in seconds
export TELEGRAM_BOT_TOKEN=[REDACTED:API key param] # For float delivery
export TELEGRAM_HOME_CHANNEL=... # Where floats go
Envelope format
Every mesh_send carries a [mesh] header on the wire:
[mesh][from:<sender>][to:<recipient>][id:<uuid>][action:<do|info>][reply:<yes|no>][ref:<parent-id>] ...
The [mesh] prefix is the canonical format. All mesh peers, including OpenClaw, must send [mesh] envelopes.
Security, replay protection, and durable delivery
hermes-mesh verifies every inbound webhook before routing it into the agent session:
- Ed25519 signatures: verifies
X-Mesh-Signature(Ed25519) against the sender'shermes_webhook.auth.public_keyfrom the local mesh vault. If a sender is not in the vault, callmesh_sync(name="...")to pull it from the mesh-peer-registry. - Timestamp validation: every inbound request must carry an
X-Mesh-Timestampheader. The adapter rejects missing, non-numeric, or stale timestamps. - Replay window: a bounded TTL-based replay cache deduplicates message IDs and prevents replay attacks. Configured via
replay_window_size,replay_window_ttl,MESH_REPLAY_WINDOW_SIZE, andMESH_REPLAY_WINDOW_TTL. - Optional timestamp-included signing: set
MESH_SIGN_TIMESTAMP=1orsign_timestamp: trueto includeX-Mesh-Timestampin the signed payload. Receivers accept both legacy body-only and timestamp-prefixed signatures for backward compatibility. - Durable outbox: when
MESH_OUTBOX_ENABLED=1, failedmesh_senddeliveries are written to disk and retried by a background reaper. AfterMESH_OUTBOX_MAX_ATTEMPTSfailures, items move to adead/folder. - Rate limiting: optional per-sender rate limiting via
rate_limit_per_minute/MESH_RATE_LIMIT_PER_MINUTE.
Use Cases
All of these patterns are powered by mesh_send — the session relay tool that delivers a message into a target agent's live conversation context with full thread continuity. No polling, no separate worker process, no context loss.
Background agents that wake on schedule
You want an agent to do work while you're not watching — poll a feed, check a system, prepare a daily briefing. Most agent frameworks solve this with a separate daemon or polling loop.
The Hermes mesh approach: the agent's session is the ambient worker. A cron job fires mesh_send → routes into the agent's live session → agent wakes with full context intact → acts → replies via the mesh.
Cron tick fires
│
▼
mesh_send → agent's live session
│
▼
Agent session wakes. Full conversation history available.
Agent reads the mesh message, acts, replies.
│
▼
Reply routes back through the mesh to the caller.
No separate worker daemon. No polling. The agent was sleeping — its session was idle. The schedule woke it via mesh_send. When it finishes, it goes back to sleep. The session persists so the next wake has full context from the previous run.
What this enables: daily digests compiled by 7am, monitoring agents that alert only on change, background research that accumulates context over days and delivers when ready.
Specialist chain — humans curate, agents specialize
A complex task needs architecture thinking, domain discovery, and implementation planning. You could throw it all at one agent, but specialists are better.
The Hermes mesh approach: talk to three different agents in sequence via mesh_send, each building full context independently. When you reach execution, you have three expert perspectives — not one confused generalist.
You → mesh_send → Isa (Discovery)
← structured findings with full codebase context
You → mesh_send → Britney (Architecture)
← architecture proposal grounded in Isa's actual findings
You → mesh_send → Linda (Design Review)
← signed-off design with coupling and failure mode analysis
You → Merge all three perspectives → Claude Code executes with full specialist context
Each agent maintained a fully-persistent session. Isa's context is complete — she was inside the codebase, she knows what she found and what she dismissed. Britney responds to Isa's actual findings. Linda reviews the real architecture, not a paraphrase. All routing happens via mesh_send through the mesh — the user never leaves their own interface.
The human is the curator: deciding which specialist to consult, in what order, when to stop prep and start executing.
What this enables: multi-domain tasks handled by actual specialists rather than a single LLM acting as all of them, quality-gated workflows where each specialist signs off before the next stage, reduced hallucination because each specialist's claims are grounded in their own exploration.
Specialist injection — agents loop in specialists mid-chain
During any relay chain, an agent can pull in a specialist via mesh_send without restarting or losing context. The chain pauses, the specialist responds, their output flows back in, the chain continues.
Britney → mesh_send → Linda (design review)
│
Linda detects a coupling issue that spans Isa's domain
│
Linda → mesh_send → Isa: "What's the import graph for module X?"
Isa responds with the graph
│
Linda folds Isa's data into the review
Linda → mesh_send → Britney: "Approved, with one routing change"
The human didn't know to call Isa — Linda did it because the mesh discipline says: wrong domain, route first. No context loss, no chain restart, no paraphrase. The specialist consultation is invisible to the caller.
What this enables: agents that self-correct by consulting the right specialist when they hit a domain boundary, chains that get smarter as they run without human intervention, context that flows through the right expert regardless of who initiated the chain.
Parallel specialist prep — all at once, not one at a time
Same result as the specialist chain, but run in parallel instead of sequence. All three calls to mesh_send fire simultaneously — each agent works in isolation with a complete session, none waiting for the others.
You → mesh_send → Isa (discovery) ─┐
You → mesh_send → Britney (arch) ─┤
You → mesh_send → Linda (review) ─┘
All three act in parallel
│
▼
You receive three independent, fully-contextual responses
Merge → Claude Code executes
Each agent had an uninterrupted, complete session. None of them know about the others until you merge the outputs. The context never got diluted by multitasking — every specialist worked in isolation and delivered a finished result.
What this enables: same quality as sequential specialist prep in a fraction of the time, agents that work at their own pace without blocking each other, human curator assembles the final output from complete specialist perspectives rather than watching a generalist try to do three things at once.
The Mesh: Session-Aware Fleet Messaging
This is the main thing that makes Hermes fleets different from standard A2A.
Standard A2A is orchestration: one agent delegates a task to another, gets a result back, continues. The relationship is client → worker. Context doesn't persist between turns.
Hermes mesh is teamwork: agents hold conversations across sessions, preserve sender context (sender name, message ID being replied to), and route replies through the mesh by convention. Britney can ask Linda a question mid-dispatch and get a threaded reply back — when both agents follow the mesh discipline documented below.
mesh_send is the mesh bridge. The envelope carries sender context — sender name and the message ID being replied to — so the recipient's LLM sees exactly who asked and what they're responding to. Thread continuity within the mesh is preserved by agent discipline, not protocol enforcement: agents agree to route replies through mesh_send back to the sender. This is intentional — convention-based coordination lets agents exercise judgment rather than follow mechanical rules. The fleet's organic interactions (escalation instead of reflex-loop, context-aware routing) emerge from this flexibility.
In a multi-owner or adversarial deployment, this model is insufficient. A protocol-level mechanism would be needed. X-Fleet-Hops (for 1-1 task exchange) could address reflexive loops there; mesh multi-party discussions have no loop problem since each agent routes independently.
This is not a webhook relay. It's a session-to-session handoff where the envelope does the routing work.
What this enables:
- Agents that work as a team, not just a delegation chain
- Cross-fleet coordination without either side needing to know internal topology
- Thread-preserving conversations between agents that outlive a single task
- Mesh discipline: domain routing, reply accountability, full context preserved
Google A2A support is provided by the separate hermes-agent-a2a plugin for external A2A JSON-RPC onboarding. The mesh session relay is a separate transport layer.
Mesh Discipline: The CTA Protocol
Every mesh_send carries a CTA (Call To Action) that tells the recipient what to do with it:
| Field | Values | Meaning |
|---|---|---|
action |
do | info |
do: act on this message. info: log/acknowledge only |
reply |
yes | no |
yes: sender expects a reply. no: fire-and-forget |
Combinations:
action=do + reply=yes: take action, then reply with resultaction=do + reply=no: act on it, no reply neededaction=info + reply=yes: acknowledge and reply when done processingaction=info + reply=no: acknowledge only, no reply needed
Reply rule: All mesh replies use mesh_send back to the originating peer — not a plain Telegram DM or platform DM. Plain DMs break thread continuity. The mesh handles the return path.
Agent prompt for mesh discipline:
When you receive a mesh message:
- Read the CTA —
actiontells you what to do,replytells you whether to respond- If
reply=yes: respond viamesh_sendback to the sender, using the same message ID as a reference (reffield)- Never reply via plain Telegram DM or platform DM — always route through the mesh so the reply carries your full context and reaches the correct thread
- If you need to route the message to another agent first (wrong domain), do so immediately — don't hold it
- If the CTA is ambiguous, reply=yes is the safe default
Example — Britney dispatches to Linda:
mesh_send(
message="Linda, review PR #123 before I merge. Link: https://github.com/...",
agent="linda",
action="do",
reply="yes"
)
# Linda's session receives it attributed to Britney.
# Linda's reply routes back through the mesh to Britney's session.
Example — Linda acknowledges without replying:
mesh_send(
message="Routing to Britney — she owns SWE dispatch.",
agent="britney",
action="info",
reply="no"
)
# Britney receives the update; Linda has already forwarded.
Session Float via Webhook Delivery
The primary session relay mechanism is webhook delivery: mesh_send POSTs the [mesh] envelope to the target agent's hermes_webhook URL (e.g. http://.../mesh/receive), Ed25519 signed with the sender's own private key. The target gateway receives the webhook on its mesh platform adapter and routes it into the configured session. This works without any extra hook handler registered.
hermes-mesh also sends a best-effort Telegram float to the sender's TELEGRAM_HOME_CHANNEL when TELEGRAM_BOT_TOKEN is set. The float is fire-and-forget; the tool result reflects the webhook delivery status, not the float.
Relationship to hermes-agent-a2a
hermes-mesh and hermes-agent-a2a are separate plugins.
Standard A2A (discover, call, serve, Agent Cards, JSON-RPC, security) is
provided by the hermes-agent-a2a plugin;
the upstream hermes-agent core does not include A2A support.
hermes-mesh carries the [mesh][from:...][to:...] envelope, per-agent Ed25519
authentication, and the fleet/mesh/agents vault. The two identity stores
are now fully independent.
OpenClaw interoperability
Hermes agents are not the only mesh participants. The companion openclaw-mesh plugin turns an OpenClaw agent into a full mesh peer using the same vault format, envelope format, and Ed25519 scheme.
A Hermes agent can mesh_send(agent="emts", message="...") and an OpenClaw
agent receives the full [mesh] envelope with sender identity, action, reply
intent, and thread id. Replies flow back the same way. Both agents keep the
session context — who asked, what they asked for, and whether a reply is
expected — across the Hermes/OpenClaw boundary.
Known OpenClaw wake caveat
OpenClaw agents receive the inbound webhook through the openclaw-mesh
plugin, which calls api.runtime.agent.runEmbeddedAgent(...) to inject the
message into the configured session. When the OpenClaw session is idle,
this can deadlock in the OpenClaw two-level lane queue, and the webhook may
return an error. The openclaw-mesh plugin now wraps the call with a
cancellable timeout and returns HTTP 500 on failure so the sender's outbox
can retry. A real fix requires OpenClaw gateway/runtime support for
waking an idle session from a plugin. The workarounds are:
- Keep the OpenClaw session warm before sending, or
- Send an explicit user turn to wake it, then send the mesh message.
Testing
hermes-mesh is a Hermes plugin and its adapter imports gateway.* modules from the
hermes-agent package. When running the test suite outside the agent repository, add
the hermes-agent source tree to PYTHONPATH:
PYTHONPATH=/path/to/hermes-mesh:/path/to/hermes-agent pytest tests/test_mesh.py -q
For example, with a default install:
PYTHONPATH=/home/emil/CascadeProjects/hermes-mesh:\
/home/emil/.hermes/hermes-agent:\
/home/emil/CascadeProjects/mesh-peer-registry \
pytest tests/test_mesh.py -q
This coupling is expected while the project remains a plugin rather than a standalone library.
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hermes_mesh-0.1.15.tar.gz.
File metadata
- Download URL: hermes_mesh-0.1.15.tar.gz
- Upload date:
- Size: 54.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4da50c74f75eb7705f7caf0681f66bc5c332e74d7444b04cd19c6936f87ffaf2
|
|
| MD5 |
66adb50d4a9871b619e0f3d00481f2ec
|
|
| BLAKE2b-256 |
18eb0979a81c63e3fa2c25e15f6518ca4aaa19b57a170de9a8a6d88f79ce7339
|
File details
Details for the file hermes_mesh-0.1.15-py3-none-any.whl.
File metadata
- Download URL: hermes_mesh-0.1.15-py3-none-any.whl
- Upload date:
- Size: 44.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/7.0.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
663087d7b899e231af9ddb48faeb7406e15d559406a95a73b9f70a3c3c35f55c
|
|
| MD5 |
e63551c115c2aebd3ba67eb638f415bf
|
|
| BLAKE2b-256 |
1e7f0a25041f37a19e84762275bd24aeec89cf4a7f9d51e8f46d233f44f7243f
|