Skip to main content
Molecule AI
Blog

Engineering

Building the ACP adapter: how Hermes Agent became a Molecule workspace runtime

NousResearch's Hermes Agent now runs as a native workspace on Molecule AI. Here's the adapter we built — and what the Agent Client Protocol made simple.

By Molecules AI Engineering 7 min read

If you have opened the workspace picker recently, you may have noticed a new option: Hermes Agent, built by NousResearch. Pick it, and Hermes runs as a first-class Molecule workspace — sessions, multi-modal messages, tool approvals — the same way Claude Code or Codex does. This post is about the engineering work that got it there.

Why Hermes

We do not want Molecule to be a single-agent product. The architecture is explicit about this: the control plane is intentionally adapter-agnostic, and no single SDK gets preferential treatment. What that means in practice is that when a capable open-source agent appears, we should be able to offer it as a runtime without rebuilding what the original authors already perfected.

Hermes fits that description. NousResearch describes it as “the self-improving AI agent built by Nous Research,” and the substance behind that claim is real. The current release (v0.12.0, April 30, 2026) ships with 90+ tools, persistent cross-session memory, and native integrations for Telegram, Discord, Slack, WhatsApp, Signal, and Email. It supports any underlying LLM — Nous Portal, OpenRouter (200+ models), NVIDIA NIM, OpenAI — and you can switch with a single command (hermes model). Six terminal backends let it run locally, in Docker, over SSH, in Daytona, Singularity, or Modal.

The v0.12.0 release also introduced the autonomous Curator: a background process that runs on a 7-day cycle, grades the agent’s accumulated skill library, prunes low-value skills, and consolidates overlapping ones. Hermes nudges itself to persist knowledge, searches its own past conversations, and creates new skills from experience — the Curator is what keeps that process from becoming noise over time. hermes curator status shows you where things stand.

We did not want to reimplement any of that. We wanted to wrap it.

The ACP layer

The Agent Client Protocol (ACP) is the protocol Molecule uses to standardize how the control plane talks to agent runtimes. Think of it as the agent-level equivalent of what MCP does for tool calls: a defined lifecycle (start, message, session close), a transport (JSON-RPC over stdout), and a contract for multi-modal content (text, image, audio). Any runtime that speaks ACP can be picked from the workspace selector and managed uniformly by the platform.

The protocol handles the things that would otherwise require bespoke integration per SDK: session identity, message threading, content types, and the handshake for tool permission approval. Our runtime adapter glossary entry describes this as “a thin module that translates the platform’s standard lifecycle calls into the underlying SDK’s API” — which is exactly what we built here.

The fork story

The work started with a problem outside our control. The original GitHub fork we had been tracking (HongmingWang-Rabbit/hermes-agent) was suspended on 2026-05-06. We migrated to our own Gitea instance at git.moleculesai.app/molecule-ai/hermes-agent and picked up from there.

Once we had a stable home for the codebase, we landed the “register_platform_adapter patch series” — a focused set of commits that added the acp_adapter/ module on top of the upstream Hermes source without touching its core. The goal was to keep the diff reviewable and the upstream delta narrow. Hermes gets updated; we do not want to be maintaining a large fork.

Inside the adapter

The entry point is straightforward. Running hermes acp (or hermes-acp, or python -m acp_adapter.entry) starts the adapter. The first thing entry.py does is load ~/.hermes/.env for configuration, then direct all logging to stderr. That stderr-only logging constraint is not cosmetic — stdout is the ACP JSON-RPC transport. Any log line that leaks to stdout corrupts the protocol framing. We found a few places in Hermes internals that wrote directly to stdout and patched them out; the hermes doctor command now surfaces this as a diagnostic.

After that, the entry point calls asyncio.run(acp.run_agent(agent, use_unstable_protocol=True)). The use_unstable_protocol flag opts into ACP features that are not yet stable-spec — in practice, this is what gives us multi-modal content support. We will drop that flag as the spec stabilizes.

For platform discovery, acp_registry/agent.json declares the agent to the Molecule control plane:

{
  "schema_version": 1,
  "name": "hermes-agent",
  "display_name": "Hermes Agent",
  "description": "AI agent by Nous Research with 90+ tools, persistent memory, and multi-platform support",
  "icon": "icon.svg",
  "distribution": {
    "type": "command",
    "command": "hermes",
    "args": ["acp"]
  }
}

The distribution.command field tells the platform how to launch the runtime. Everything else — session lifecycle, message routing, tool approval — flows over ACP once the process is running.

The server.py module defines HermesACPAgent, which handles the ACP side of things: session creation and teardown, incoming messages (text, image, audio), outbound content framing, and the list of available commands the platform can surface in the UI. Two supporting modules do focused work:

  • acp_adapter/auth.pydetect_provider() inspects the loaded environment to determine which LLM backend is configured and passes that through to ACP’s authentication layer.
  • acp_adapter/permissions.pymake_approval_callback() constructs the callback the platform uses when it needs user approval before Hermes executes a tool. This is how the Molecule tool-approval UI wires up to Hermes’s 90+ tools without the platform needing a static list of them.
  • acp_adapter/session.pySessionManager and SessionState track active sessions. Hermes’s AIAgent class is synchronous, so we run it in a ThreadPoolExecutor(max_workers=4, thread_name_prefix="acp-agent"). Four workers covers the parallel session load we expect in practice without over-provisioning on the machines where Hermes typically runs.

MCP tool discovery (discover_mcp_tools() in server.py) deserves a note. Hermes can connect to external MCP servers and expose their tools alongside its own built-ins. The adapter queries this list at session start and forwards it to the platform so the workspace UI can display which tools are actually available in a given session — not a static manifest, but the live tool set.

The adapter also handles liveness probes gracefully. The ACP spec does not define ping/health/healthcheck methods, but some infrastructure layers send them anyway. We catch those calls, return a -32601 (method not found) response, and suppress the traceback noise. This is a small thing that made the Gitea CI logs significantly less noisy.

CI realities

Getting CI to pass on our Gitea act_runner was more work than the adapter itself. A few issues that each required a real commit to fix:

Shallow clones. Gitea’s act_runner defaults to shallow clones, and some of our test fixtures assumed full history. We updated the workflow to fetch with --unshallow when the test job runs.

AsyncMock warnings. Python 3.12 changed how AsyncMock reports unused awaits in tests. The upstream Hermes test suite had several of these; they do not fail, but they generate enough warning output to obscure real failures. We patched the affected tests.

SIGKILL cleanup. The adapter’s test suite starts and stops subprocess instances of the ACP server. On the act_runner, processes that did not exit cleanly within the timeout were leaving open file descriptors that caused the next test run to fail. We added explicit SIGKILL cleanup with a short wait before asserting the process is gone.

setup-uv cache pressure. The act_runner instances share a cache volume. setup-uv’s default cache behavior was causing lock contention across parallel jobs. We pinned the cache key to the workflow run ID for jobs that install from source.

None of these are interesting problems in themselves, but they are the kind of thing that makes a new CI environment feel hostile for the first week. Worth documenting so the next person who sets up an act_runner integration knows what to expect.

The result

Hermes runs as a first-class Molecule workspace. You select it the same way you select Claude Code or Codex — the control plane does not treat it differently, because the ACP adapter is the same shape as every other runtime adapter in the set. Sessions persist, tool approvals flow through the standard UI, and multi-modal content works out of the box.

From Hermes’s side, nothing changed. The adapter adds a new entry point and a discovery file; it does not modify Hermes internals. If NousResearch ships a v0.13.0, updating the fork is a merge, not a rewrite.

The architecture page lists the current adapter set: Claude Code, Codex, Hermes (sidecar), OpenClaw, and Google ADK in development. The pattern is consistent: we take capable agents as they exist and meet them at the protocol boundary. That is what ACP is for.