Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Relay-Hermes

Relay-Hermes connects Hermes Agent to Relay as an always-on messaging platform.

Relay events arrive over the Relay v1 acknowledged WebSocket. Hermes replies are sent through Relay's REST API.

Delivery model

For each Relay event, the plugin:

  1. commits the complete envelope and event_id to a durable SQLite inbox;
  2. sends a cumulative WebSocket ACK only after the commit;
  3. deduplicates replayed event_id values;
  4. starts a Hermes turn only for an inbound message.received event;
  5. explicitly marks the Chat Read when Hermes starts processing the turn;
  6. sends each Message through POST /v1/chats/{chatId}/messages with an Idempotency-Key.

Transport acknowledgement and Read are separate. A WebSocket ACK never marks a Chat Read.

Relay-Hermes does not poll for events and does not expose a public HTTP server. It also does not add reactions, edits, or typing indicators.

Recovery

The SQLite inbox survives gateway restarts. If processing fails, the event returns to pending; the durable transport checkpoint does not move backward.

The state directory and SQLite database are bound to one Relay account using the normalized API origin and a one-way Agent Token fingerprint. The token is never written to state. Changing the token or API origin while reusing a state directory fails before startup requeues work or reads a FULL-sync snapshot. On supported Linux systems every directory component and the database are opened through pinned descriptors with no-follow checks before SQLite receives an already-open file descriptor path. SQLite never connects to the mutable configured pathname and cannot create through a dangling database symlink or a replaced directory. The directory is forced to mode 0700, including when it already exists. Use a separate RELAY_STATE_DIR for every staging or production Agent. Pre-binding databases are not adopted automatically; move one aside only after accounting for its pending work.

If Relay reports that a checkpoint is outside retention, the plugin follows the WebSocket FULL-sync flow. It pages through visible Chats and Messages, validates the snapshot, atomically stores it with full_sync_through, and sends full_sync_complete only after the transaction commits. Historical Messages rebuild local indexes and never become new Hermes turns.

Install

Hermes supports Git-installed directory plugins:

hermes plugins install RelayMessenger/Relay-Hermes --enable

The same repository can be installed as a Python package. Its hermes_agent.plugins entry point registers the identical platform adapter.

Create an Agent and copy its Agent Token from Relay Console, then save the token in ~/.hermes/.env:

RELAY_AGENT_TOKEN=your_agent_token

An Agent using this WebSocket must not have a saved webhook subscription. Relay rejects the WebSocket upgrade with HTTP 409 until those subscriptions are removed.

Start Hermes in the foreground:

hermes gateway run

For an always-on installation, use Hermes's service commands:

hermes gateway install
hermes gateway start

The Hermes platform id is relayapp. Hermes already reserves relay for its generic connector platform.

Configuration

RELAY_AGENT_TOKEN is the only required setting.

Setting Default Meaning
RELAY_BASE_URL https://api.relayapp.im Relay API origin
RELAY_ALLOWED_CONTACTS all reachable Contacts Comma-separated Contact ids allowed to start turns
RELAY_STATE_DIR <Hermes profile home>/relay Profile-scoped durable SQLite inbox directory
RELAY_REPLY_TO_MODE auto Reply anchor policy: off, first, all, or auto
RELAY_GROUP_CHAT_POLICY mentions Group Chat policy: mentions or all
RELAY_HOME_CHAT unset Chat id for cron and direct hermes send delivery
RELAY_HOME_CHAT_NAME Chat id Human label for the home Chat

RELAY_BASE_URL must be an HTTPS origin, except that HTTP is accepted for loopback development. A configured invalid value fails closed and is never replaced with the production default.

Slash-command limitation

Relay Contact chat is enabled, but Relay slash commands are disabled on the pinned Hermes core. Its slash-policy resolver reads the gateway runner's primary platform config and ignores source.profile, so a primary profile operator grant could otherwise authorize a secondary-profile Contact. The adapter therefore withholds every Relay message whose first non-whitespace character is / from Hermes, installs deny-only slash policy as defense in depth, and registers relayapp as ineligible for /update. This is consistent for primary and secondary profiles; use a trusted local CLI or authenticated dashboard for operator commands.

RELAY_OPERATOR_CONTACTS and operator_contacts are not supported. Any old setting should be removed; it cannot safely grant Relay slash authority until the pinned Hermes policy becomes profile-aware.

Non-secret settings can instead be placed under gateway.platforms.relayapp.extra in ~/.hermes/config.yaml; environment variables take precedence:

gateway:
  platforms:
    relayapp:
      enabled: true
      extra:
        allowed_contacts:
          - 01993d50-ef7b-7b37-886b-23fd80c7ec12
        group_chat_policy: mentions
        reply_to_mode: auto

Relay resolves the token, API origin, chat allowlist, state directory, and delivery settings through Hermes's active profile secret scope. In a multiplexed gateway, a missing value never falls through to another profile's process environment. The default state path is under the active profile home, so profile inboxes are distinct even when neither profile sets RELAY_STATE_DIR.

Isolated staging

Use a staging Agent Token, the staging API origin, and a separate inbox:

export RELAY_AGENT_TOKEN='staging-agent-token'
export RELAY_BASE_URL='https://api.staging.relayapp.im'
export RELAY_STATE_DIR="$HOME/.hermes/relay-staging"
./scripts/run-staging.sh

The staging helper refuses every other API origin.

Locked Relay contract

This version was audited against Relay Server developer OpenAPI commit 9b4d5bb32cc749c6fd271969948c385300d404d6. The exact contracts/developer/openapi.yaml SHA-256 is f62f431fc0daa48500926bf87753f81c3fdda25ab463b130ca97f2896367e0a5. Those exact public bytes are checked in at contracts/relay-server/9b4d5bb32cc749c6fd271969948c385300d404d6/openapi.yaml; normal CI and RC publication validate that local snapshot without private repository credentials.

The runtime contract used here is:

  • GET /v1/websocket for acknowledged agent events;
  • GET /v1/chats and GET /v1/chats/{chatId}/messages only for a server-directed FULL sync;
  • POST /v1/chats/{chatId}/read with no request body at processing start;
  • POST /v1/attachments, followed by the allocated raw PUT, for local files;
  • POST /v1/chats/{chatId}/messages with Idempotency-Key for every Message send;
  • event envelopes with api_version: v1 and webhook_version: 2026-08-30.

Relay vocabulary in the adapter is Contact, Handle, Chat, Message, and Participant.

Development

Python 3.11 through 3.13 are supported.

python -m venv .venv
.venv/bin/pip install -e '.[dev]'
HERMES_AGENT_SRC=/path/to/hermes-agent .venv/bin/python -m pytest
python -m build
hermes plugins doctor . --ci

Tests cover REST paths and bodies, Message idempotency, explicit Read timing, FULL-sync recovery, WebSocket authentication and acknowledgement ordering, replay deduplication, heartbeat and reconnect behavior, package installation, and Hermes directory-plugin registration.

Release files for relay-hermes 1.0.0rc1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for relay-hermes 1.0.0rc1
File Size Uploaded
relay_hermes-1.0.0rc1.tar.gz 63.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for relay-hermes 1.0.0rc1
File Interpreter ABI Platform
relay_hermes-1.0.0rc1-py3-none-any.whl Python 3 none any Details

Total release size: 103.8 kB

Release files / relay_hermes-1.0.0rc1.tar.gz

Download URL relay_hermes-1.0.0rc1.tar.gz
Size 63.6 kB
Tags Source
SHA-256 checksum
How to use checksums
f99250a0d5b69d853e01f7364d3d9da92c13dc216ce1bc35029a85c48c9371be
BLAKE2b-256 checksum
How to use checksums
996fed43493358c9713ff75830d1bca6739cd895681d9e5314ee7d50bcdbe2ac
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / relay_hermes-1.0.0rc1-py3-none-any.whl

Download URL relay_hermes-1.0.0rc1-py3-none-any.whl
Size 40.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7f0ed7183d68313b55cfd67b3909b91facda8dffa0b68dc3c4001f29a3ace1a1
BLAKE2b-256 checksum
How to use checksums
1a37d076c9734967618e69629fe396aa951da116c66e7dcaaf10fc7668506b7b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page