Replacing a Rabbit's Brain: Connecting the R1 to Hermes
I replaced the OpenClaw agent on my R1 with Hermes, running locally on a MacBook Pro.
A Rabbit R1 sits on my desk next to an M1 MacBook Pro. A few days ago I got it talking to a Pinata-hosted OpenClaw agent. The MacBook runs Hermes, a self-hosted AI agent on Opus 4.6 (switching between Anthropic and OpenAI as needed). It handles my Telegram messages, runs cron jobs, writes code, and remembers things across sessions. The whole stack runs on 8 cores and 16 GB of RAM under macOS 26.3.1. It’s been up for 12 days.
I wanted the R1 to talk to the same agent. Same memory, same tools, same conversation history. A proper channel into Hermes, like Telegram already is. The shim is on GitHub with an llms.txt that lets your Hermes agent handle the setup.
The Creation SDK Dead End
Rabbit has an open-source Creations SDK for building tiny web apps on the R1’s 240×282px screen. We built a working prototype: a chat UI backed by a Python server exposed over Tailscale Funnel. Got it running on the device in under an hour.
Creations are apps. Sandboxed mini-programs you launch from a menu. We wanted Hermes to be the R1’s brain, the thing it talks to when you hold the push-to-talk button.
The OpenClaw Shim
Rabbit ships a setup script at rabbit.tech/r1-openclaw.sh that pairs the R1 with an OpenClaw instance over WebSocket. The same pairing flow from the Pinata setup, minus the auth proxy workaround. OpenClaw is a multi-channel AI gateway. Hermes has a similar gateway architecture. We didn’t need OpenClaw. We needed to speak the same protocol.
The script generates a QR code with a JSON bootstrap payload: the MacBook’s LAN address, a port, and a token. The R1 scans it and connects over WebSocket.
The QR payload:
{
"type": "clawdbot-gateway",
"version": 1,
"ips": ["192.168.50.42"],
"port": 18789,
"token": "***",
"protocol": "ws"
}
We pulled the OpenClaw npm package (182 MB unpacked, 21,000+ files) and read the source. The protocol is a challenge-response handshake that pairs the device, then messages flow as chat.send from the client, ack’d by the server, streamed back as delta/final event pairs.
Auth runs two layers: a gateway token from the QR for the first connection, and a per-device token issued during pairing for every reconnect. The R1 caches its device token. If the server only accepts the gateway token, reconnection fails.
Two Iterations
Standalone Python server. An aiohttp WebSocket server implementing enough protocol to pair and respond. The R1 connected, paired, sent a test message.
Gateway platform adapter. The Hermes gateway runs as a launchd LaunchAgent on this Mac and manages platform connections. The R1 shim became another platform adapter, like Telegram.
Telegram Adapter ─┐
Discord Adapter ─┤── GatewayRunner._handle_message() ── AIAgent
R1 Shim Adapter ─┘
Every adapter converts platform-specific messages into a common event format, then calls the shared _handle_message pipeline. Hermes is open-source, and the gateway has a clean adapter pattern. Adding the R1 meant one new file (~300 lines) and small patches to three existing ones, all uncommitted on top of upstream main.
Five Bugs
Device token rejection: the R1 caches its pairing token and uses it on reconnect, but our first shim only accepted the gateway token.
Wrong response event type: we sent event: "agent" for responses, but the R1 expected event: "chat" with a specific message shape. The spinner spun forever.
Platform enum mismatch: the adapter registered as one platform type, the session source used another. Auth rejected everything. One-line fix.
Missing toolset registration. Stale .pyc bytecache. Both quick fixes once spotted.
Where It Stands
The R1 talks to the same agent that answers my Telegram messages. Same persistent memory, same session store in SQLite, same tools.
✓ telegram connected
✓ r1_shim connected
Gateway running with 2 platform(s)
The whole stack runs on a single M1 MacBook Pro with 16 GB of RAM: gateway, both adapters, browser daemons, and a web API server running concurrently. 748 processes across the system, load average around 1.5.
Streaming sends responses as a single delta+final pair. Token-by-token streaming would be better. Voice and camera aren’t wired yet.
Architecture
┌──────────┐ WebSocket (OpenClaw protocol) ┌─────────────────┐
│ Rabbit R1 │ ◄──────────────────────────────────► │ r1_shim adapter │
│ (device) │ QR bootstrap → pair → chat.send │ (port 18789) │
└──────────┘ └────────┬────────┘
│
_handle_message()
│
┌────────▼────────┐
│ GatewayRunner │
│ Opus/Codex/etc │
│ Python 3.11 │
│ M1 MacBook Pro │
└────────┬────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ Sessions │ │ Memory │ │ Tools │
│ (SQLite) │ │ (files) │ │ (terminal │
└───────────┘ └───────────┘ │ web, etc)│
└───────────┘
~300 lines of new Python for the shim. ~15 lines of patches across 3 upstream files. Repo and llms.txt here.