Skip to content

mq9-hermes plugin: Some Technical Thoughts

Starting Point

Hermes Agent (NousResearch's self-improving AI agent) wants to add A2A protocol support. Issue #514 is their official proposal — opened in March 2026, still open as of now. The proposal is thorough: a three-phase implementation plan, SDK selection, and dependency analysis are all covered, but actual code progress has been slow.

Reading that issue and then looking at mq9's current capabilities, I spotted an opportunity: mq9 can serve as a Hermes plugin, letting Hermes use mq9 as the transport and discovery layer for A2A, bypassing most of the implementation work outlined in Issue #514.

This post is my attempt to think through that idea clearly — why do it, what exactly it covers, and how it might be done.

Hermes Agent's Current Communication Shape

Let me first lay out what Hermes can and cannot do today.

What it can do:

  • Connect to 18 platforms (Telegram, Discord, Slack, WhatsApp, Signal, Email, Feishu/Lark, Microsoft Teams, etc.) through a messaging gateway, letting humans talk to agents
  • In-process local delegation — spawn child agents, up to 3 concurrent, depth 2
  • Invoke other agents (claude-code, codex, etc.) via external CLI calls
  • Call tools as an MCP client (tools/mcp_tool.py, 1047 lines, production-ready)

What it cannot do:

  • Communicate with other agents across processes or machines
  • Agent discovery (beyond hardcoded URL config)
  • Expose itself as a service that external agents can call
  • Async tasks (e.g., offline messages, long-running collaborative tasks)

For a concrete real-world example: the community hermes-plugins project includes an MQTT plugin that lets Hermes subscribe to an event stream. Developers are using MQTT as a makeshift agent communication channel, simply because there's no better tool available.

What Issue #514 Actually Proposes

The proposal breaks A2A integration into three phases:

Phase 1: A2A Client. Implement two tools — a2a_discover and a2a_call — so Hermes can actively call remote A2A agents. This requires a new file tools/a2a_tool.py, the a2a-sdk[http-server] dependency, and an a2a_agents block in ~/.hermes/config.yaml.

Phase 2: A2A Server. Implement an AgentExecutor wrapping Hermes AIAgent, auto-generate an Agent Card from toolsets/skills, start an HTTP server exposing endpoints (standalone or integrated into the gateway), and handle streaming and authentication.

Phase 3: Multi-Agent Orchestration. Maintain a multi-URL agent registry, automatically select agents based on task description, support fan-out workflows, and integrate with delegate_tool for hybrid local/remote orchestration.

Problems the proposal honestly acknowledges:

  • The A2A SDK is pre-1.0 (v0.3.24), won't GA until mid-2026, and may break
  • Yet another protocol to maintain (MCP is already there)
  • Security surface from exposing agent endpoints over the network (auth, HTTPS, input sanitization)
  • Agent Cards without signatures can be spoofed (JWS signing is optional)
  • Conceptual overlap with the existing delegate_tool

Open questions from the proposal:

  • Port ownership for A2A server mode (standalone or shared with gateway)
  • Whether A2A tools are core or opt-in
  • Credential storage location (config, env, or vault)
  • Should we wait for A2A SDK v1.0 or build on v0.3.x now
  • Should A2A agent discovery integrate with MCP server listings (MCP-as-registry pattern)

Those last two open questions are especially worth noting. The first reflects the instability of the A2A standardization process. The second reflects the fact that there's no consensus yet on what registry means in the agent ecosystem. NousResearch themselves are torn over whether to reuse MCP for agent registration.

The Scope of Work in Issue #514

Adding Phase 1 + Phase 2 + Phase 3 together, there's a lot:

Protocol layer:

  • Integrate a2a-sdk, handle JSON-RPC over HTTP, SSE streaming, Task state machine
  • Agent Card generation, caching, refresh
  • State management for multi-turn tasks (INPUT_REQUIRED, AUTH_REQUIRED, etc.)
  • Authentication and credential management

Network layer:

  • Start HTTP server exposing /.well-known/agent.json and A2A endpoints
  • Port planning, TLS configuration
  • Client connection management, retries, timeouts

Orchestration layer:

  • Multi-agent registry
  • Match agents to capabilities based on natural language
  • Multi-agent workflows integrated with delegate_tool

Maintenance cost of a pre-1.0 SDK:

  • Track spec changes
  • Pin versions, run regression tests with every upgrade

This is a genuine large engineering effort. The issue has been open for two months without a merge — not because it's low priority, but because doing it right is genuinely hard work.

mq9's Current Capabilities

Looking back at what mq9 already delivers:

Protocol layer:

  • mailbox abstraction, pub/sub model
  • Persistent messages (messages are retained when an Agent is offline and delivered in full when it comes back online)
  • Priority levels (normal/urgent/critical)
  • TTL-based automatic cleanup
  • Key-based compaction (only the latest version per key is kept — friendly semantics for status update patterns)
  • DeliverPolicy (all/new/from_timestamp)
  • QUERY interface (direct lookup of the latest state by key)

Registry:

  • AGENT.REGISTER / DISCOVER / UNREGISTER / REPORT
  • Built-in fastembed-rs + LanceDB for semantic search
  • Tag-based exact-match search
  • raw_card stored and returned as-is (mq9 does not parse AgentCard content; it only extracts mailbox and tags for indexing)

URI scheme:

  • mq9://broker/mailbox and mq9s://broker/mailbox
  • Works in any protocol field that accepts a URL

A few things worth comparing directly against Issue #514:

mq9's mailbox model naturally solves the reliability problem with A2A push notifications. The A2A spec says push is a webhook, but webhook failure handling is not specified. mq9 mailbox persists messages, cleans them up by TTL, and delivers everything in full when a client comes back online — capabilities webhooks simply don't have.

mq9's registry maps directly onto Phase 3's "automatically select agents based on task description." In Issue #514 that capability is something Hermes would have to build itself. mq9 already has it.

mq9's URI scheme means the pushNotification.url field in A2A can be set directly to mq9://... — not a single line of the A2A protocol needs to change.

An Idea: mq9-hermes Plugin

If Hermes integrates mq9 through a plugin, most of the work in Issue #514 can be avoided.

Specifically:

Phase 1 (A2A Client) equivalent in mq9:

  • mq9_discover replaces a2a_discover: fetches AgentCards from the mq9 registry, supporting both tag and semantic search
  • mq9_call replaces a2a_call: sends A2A messages through mq9 mailboxes

No a2a-sdk dependency, no HTTP client logic, no SSE stream handling. The mq9 SDK is the plugin's only dependency.

Phase 2 (A2A Server) equivalent in mq9:

  • mq9_serve: when Hermes starts up, it registers itself with mq9 (with its AgentCard) and subscribes to its own mailbox to receive incoming A2A tasks

No HTTP server to start, no ports to manage, no well-known URL exposure for Agent Card. The mq9 broker acts as the de facto "agent gateway."

Phase 3 (Orchestration) equivalent in mq9:

  • The registry already lives in the mq9 broker
  • Semantic search based on task description is a mq9 registry capability
  • Multi-agent workflows live at the application layer; mq9 provides flexible mailbox composition

What isn't saved:

  • The actual content of AgentCards (that's Hermes's responsibility)
  • Task state machine semantics (that's the A2A protocol itself)
  • Credential management (mq9 doesn't handle this; it's the application layer's job)
  • Security model (mq9 provides the baseline guarantee of unguessable mail_address; complex scenarios add signing at the application layer)

What the Plugin Looks Like

I'm imagining the Hermes plugin structured like this:

~/.hermes/plugins/mq9/
├── plugin.yaml          # metadata
├── mq9_plugin.py        # main entry point
├── tools/
│   ├── mq9_register.py
│   ├── mq9_discover.py
│   ├── mq9_call.py
│   └── mq9_serve.py
└── config_schema.py     # config validation

plugin.yaml:

yaml
name: mq9
version: 0.1.0
description: A2A communication and discovery via mq9
provides_tools:
  - mq9_register
  - mq9_discover
  - mq9_call
  - mq9_serve
provides_hooks:
  - post_setup        # auto-register with mq9 on startup
  - shutdown          # deregister on shutdown
config:
  broker:
    type: string
    default: "mq9://localhost:4332"
  agent_id:
    type: string
    required: true
  ttl:
    type: integer
    default: 300

post_setup hook:

Runs automatically when Hermes starts — reads config, connects to the mq9 broker, allocates an inbox mailbox, registers its own AgentCard, and starts a background subscription loop to handle incoming messages. Developers don't write any code; they just add a few lines to ~/.hermes/config.yaml to enable it.

yaml
plugins:
  enabled:
    - mq9

mq9:
  broker: "mq9://broker.internal:4332"
  agent_id: "hermes-prod-001"
  ttl: 600
  capabilities:
    streaming: true
    push_notifications: true

mq9_discover tool:

When a Hermes agent encounters an intent like "find an agent that can write Python code" during a conversation, it automatically calls mq9_discover:

python
@tool
def mq9_discover(query: str = None, tag: str = None, limit: int = 5):
    """
    Discover agents through mq9 registry.
    
    Args:
        query: Natural language query for semantic discovery
        tag: Exact tag for tag-based filtering
        limit: Max number of agents to return
    
    Returns:
        List of AgentCards
    """
    if query:
        return mq9_client.discover_semantic(query, limit)
    elif tag:
        return mq9_client.discover_by_tag(tag, limit)

What comes back is raw AgentCard JSON — mq9 does not transform or wrap it; it returns exactly what was stored. Hermes parses and handles it as usual.

mq9_call tool:

python
@tool
def mq9_call(target_mailbox: str, message: dict, callback_mailbox: str = None):
    """
    Call a remote agent via mq9 mailbox.
    
    Args:
        target_mailbox: Target agent's mailbox (e.g., "mq9://broker/agent.coder.inbox")
        message: A2A-format message
        callback_mailbox: Optional callback mailbox; auto-created if None
    """
    if callback_mailbox is None:
        callback_mailbox = mq9_client.create_mailbox(ttl=86400)
    
    # Embed the callback into the A2A message's pushNotification.url
    message["pushNotification"] = {"url": callback_mailbox}
    
    # Send
    mq9_client.send(target_mailbox, message)
    
    # Subscribe to the callback mailbox to receive the response
    return mq9_client.subscribe(callback_mailbox)

Note that the message itself is in A2A format (JSON-RPC + Task); mq9 does not parse it. This ensures that as the A2A protocol evolves, the plugin needs no changes.

mq9_serve hook:

On startup, Hermes launches a background subscription loop:

python
def on_post_setup(ctx, config):
    inbox = config.get("inbox") or auto_create_inbox()
    
    # Register with mq9
    mq9_client.register({
        "mailbox": inbox,
        "name": ctx.agent_name,
        "description": ctx.agent_description,
        "skills": derive_skills_from_toolsets(ctx),
        "ttl": config["ttl"]
    })
    
    # Background subscription
    asyncio.create_task(serve_loop(inbox, ctx))

async def serve_loop(inbox, ctx):
    async for msg in mq9_client.subscribe(inbox):
        # msg.body is an A2A tasks/send request
        task = parse_a2a_task(msg.body)
        result = await ctx.agent.execute(task)
        
        # Write the result back to the callback mailbox
        callback = task.get("pushNotification", {}).get("url")
        if callback:
            mq9_client.send(callback, build_a2a_response(result))

Heartbeat:

The REPORT interface lets Hermes periodically report its status (online, load, etc.):

python
@scheduled(interval=60)
def heartbeat():
    mq9_client.report({
        "mailbox": inbox,
        "status": "online",
        "active_tasks": ctx.active_task_count,
        "load": ctx.current_load
    })

Specific Design Decisions

Decision 1: The plugin does not depend on a2a-sdk.

The Issue #514 proposal requires a2a-sdk[http-server]. The mq9-hermes plugin should not depend on it. Reasons:

  • a2a-sdk is pre-1.0 and may break
  • The mq9 protocol layer does not parse A2A content, and neither should the plugin
  • A2A messages are just JSON; Hermes can parse them with the json module

The plugin's mq9_call accepts a dict (the A2A message) and sends it as-is. The plugin's serve_loop receives a byte string and only needs a dict when invoking the Hermes agent. Protocol passthrough — no SDK dependency introduced.

Once a2a-sdk stabilizes, an optional dependency for message validation could be considered, but it's not required.

Decision 2: The plugin does not start an HTTP server.

Issue #514 Phase 2 requires starting an HTTP server to expose /.well-known/agent.json and A2A endpoints. The mq9-hermes plugin does not need that.

Hermes exposes its AgentCard through the mq9 registry and receives tasks through its mq9 mailbox. All communication goes through mq9; no ports need to be listened on directly. This makes plugin deployment simple — the Hermes process doesn't need a new port, doesn't need TLS configuration, and doesn't need to worry about firewalls.

Decision 3: AgentCard is auto-generated by the plugin.

Hermes already has the concept of toolsets and skills. The plugin iterates over these at registration time to generate the skills field of the AgentCard:

python
def derive_skills_from_toolsets(ctx):
    skills = []
    for toolset_name, toolset in ctx.toolsets.items():
        for tool in toolset.tools:
            skills.append({
                "id": f"{toolset_name}-{tool.name}",
                "name": tool.display_name or tool.name,
                "description": tool.description,
                "tags": [toolset_name, *tool.tags],
                "examples": tool.examples or []
            })
    return skills

Users can also override this manually in the plugin config:

yaml
mq9:
  agent_card:
    name: "Custom Hermes Coder"
    description: "Specialized coding agent"
    skills:
      - id: "rust-codegen"
        name: "Rust code generation"
        description: "Generate Rust code from natural language"
        tags: ["rust", "codegen"]
        examples: ["Write a Rust HTTP server"]

Auto-generation is the default; manual config is the override.

Decision 4: Coexist with Hermes's existing delegate_tool.

Hermes already has delegate_tool (local child agent spawning). mq9_call is for remote invocation. Both coexist:

  • Local tasks use delegate_tool (fast, no network overhead)
  • Cross-machine/cross-framework tasks use mq9_call
  • Documentation provides clear guidance on when to choose which

The goal is not to replace delegate_tool, but to complement it.

Decision 5: Minimal install for the plugin.

The plugin should be installable on a machine without a mq9 broker and simply not activate. That is, if mq9.broker is not specified in the config, the plugin silently skips initialization without errors. This lets developers include it by default and enable it on demand.

Questions I Haven't Resolved Yet

Question 1: How does Hermes streaming map onto mq9.

A2A supports SSE streaming responses. Hermes agents also support token-by-token streaming. How do these two streams pass through a mq9 mailbox?

Possible approaches: one message per token using key compaction keyed by task_id (retaining only the latest accumulated content), or retaining all messages and letting the client reassemble. The first saves storage but loses intermediate tokens semantically; the second is complete but generates a lot of messages.

This needs real testing to evaluate.

Question 2: How bidirectional multi-turn works in practice.

A2A's INPUT_REQUIRED state requires the server agent to pause, wait for the client to provide input, then resume. In Hermes's current architecture, agents process a single turn at a time. Supporting pause and resume may require changes to Hermes core, or the plugin maintaining its own state machine.

This is one of the harder implementation challenges. It would need a conversation with NousResearch about whether Hermes's internal API supports that kind of hook.

Question 3: Relationship with Hermes's existing messaging gateway.

Hermes's gateway lets humans talk to agents through Telegram and similar platforms. The mq9-hermes plugin lets agents talk to agents. Are these independent or do they intersect?

A scenario worth thinking through: a human sends a message to agent A via Telegram, agent A finds agent B through mq9 and collaborates, agent B's reply comes back through mq9 to agent A, agent A relays the result to the human via Telegram. How does the plugin support this chain?

It may require carrying the original messaging gateway context (which platform, which user) inside the mq9 message, so agent B knows who ultimately receives the result.

Question 4: Error handling and observability.

Hermes has its own telemetry plugin (structured JSON logs, auto-rotating event log). Plugin events from mq9 (successful registration, incoming subscription message, remote call) should integrate with Hermes's existing telemetry.

How exactly that integration works and what hooks Hermes needs to expose depends on NousResearch's plugin system design.

Question 5: Plugin language.

Hermes is Python, and so is its plugin system. What is the current state of the mq9 SDK? If only a Rust SDK exists, Python bindings are needed.

From what I can see, a Python SDK is in progress for mq9 (part of the 0.4.0 work). But the specific mq9 client capabilities the plugin needs — subscribe, send, register, discover — need to be confirmed as covered by the Python SDK.

An Extension: Not Just Hermes

Taking the mq9-hermes plugin approach and generalizing it, similar adapters could be built for other agent frameworks:

mq9-langchain        # LangChain Tool integration
mq9-autogen          # AutoGen agent transport
mq9-crewai           # CrewAI agent communication
mq9-openclaw         # OpenClaw plugin
mq9-claude-code      # Claude Code (if it opens a plugin interface)

Each adapter does the same kind of thing: wrap mq9 mailbox operations in the framework's native API, let the framework's agents register with the mq9 registry, and let agents send and receive A2A messages through mq9.

The plugin shape will differ across frameworks (Hermes is a drop-in directory, LangChain is a pip package, AutoGen is a config entry), but the core abstraction is the same:

common interface:
  - register(agent_card)
  - discover(query | tag)
  - send(target_mailbox, a2a_message)
  - subscribe(my_mailbox, handler)

These can be packaged into mq9's official SDK, with each framework's plugin as a thin wrapper. Low maintenance cost, consistent behavior across frameworks.

Implementation Path

If you want to pick this up, here's the suggested path:

Step 1: Get the mq9 Python SDK working.

Confirm that mq9 supports the core operations from a Python client: register, discover, send, subscribe. If there are gaps, fill them first.

Step 2: Write a minimal PoC plugin.

Don't integrate with Hermes yet — just validate the plugin shape:

  • One standalone Python script registers with mq9, with a hardcoded AgentCard
  • A second Python script uses mq9_discover to find that agent
  • The second script uses mq9_call to send a message to the first
  • The first script subscribes to its inbox, receives the message, and replies

Once this PoC runs, it proves that the mq9 protocol and SDK work in a real bidirectional communication scenario.

Step 3: Fork hermes-agent and add the plugin.

Follow the Hermes plugin system conventions: drop it into ~/.hermes/plugins/mq9/, declare hooks and tools in plugin.yaml, start registration in post_setup, and implement tools like mq9_call.

The first version covers only Phase 1 (client) + minimal Phase 2 (passive serve). Phase 3 orchestration comes later.

Step 4: Test in a real scenario.

Run two Hermes instances (Hermes-A and Hermes-B), having them discover and call each other through mq9. The scenario to validate:

  • Hermes-A starts and registers itself
  • Hermes-B starts and registers itself
  • A user tells Hermes-A: "find an agent that can write Python and have it write me an HTTP server"
  • Hermes-A calls mq9_discover and finds Hermes-B
  • Hermes-A calls mq9_call to send the task to Hermes-B
  • Hermes-B processes it and replies via mq9
  • Hermes-A delivers the result to the user

Once this end-to-end scenario runs, it's ready to share with the Hermes plugin ecosystem.

Step 5: Talk to NousResearch.

Put together the PoC and demo for the Issue #514 maintainers. The issue already has them thinking about "should we wait for SDK v1.0 or build on v0.3.x now" — the mq9 plugin offers a third option: "don't depend on a2a-sdk at all; implement A2A via mq9 transport."

They may find it interesting. They may not. It doesn't matter — the plugin is valuable to Hermes users regardless.

Final Thoughts

The purpose of this post is to think through the mq9-hermes plugin concept clearly, not to deliver a complete implementation plan. The real build will hit plenty of details this post doesn't cover — some Hermes plugin API that's awkward to use, some A2A state whose semantics don't map cleanly onto a mailbox, some Python SDK capability that isn't finished yet. All of that gets resolved when you're actually writing code.

If you've read this and the thinking makes sense to you and you want to pick up part of it, you can start at any step in the implementation path above. Each step has reasonably clear boundaries and can be completed independently. If you get stuck on a specific piece, that's worth a separate discussion.

🎉 既然都登录了 GitHub,不如顺手给我们点个 Star 吧!⭐ 你的支持是我们最大的动力 🚀