Skip to content

Hermes adapter

hermes-chorus-adapter is an independent Python wheel that registers the chorus_signals platform through Hermes Agent’s supported hermes_agent.plugins entry point. The plugin does not replace Hermes gateway behavior. Its current host distribution is a narrow downstream security overlay because official Hermes 0.18.2 pins vulnerable aiohttp, cryptography, and Pillow releases.

The plugin is SSE-only. The old webhook option was removed because it had no Hermes-owned HTTP listener and did not match Chorus’s webhook registration or secret lifecycle.

From the Chorus repository root:

Terminal window
python3.12 -m venv .venv-hermes-chorus
source .venv-hermes-chorus/bin/activate
HERMES_BUILD_DIR="$(mktemp -d)"
python3.12 -m venv "$HERMES_BUILD_DIR/build-venv"
"$HERMES_BUILD_DIR/build-venv/bin/python" -m pip install \
--only-binary=:all: --require-hashes \
-r packages/hermes-chorus-adapter/hermes_chorus_adapter/locks/build-requirements.lock
curl --fail --location --proto '=https' --tlsv1.2 \
--output "$HERMES_BUILD_DIR/hermes_agent-0.18.2-py3-none-any.whl" \
'https://files.pythonhosted.org/packages/0c/4c/91652c61450763bfe165c65b83026503de0ac9ddad2c11ee522490bf4c2d/hermes_agent-0.18.2-py3-none-any.whl'
"$HERMES_BUILD_DIR/build-venv/bin/python" scripts/build_hermes_security_overlay.py \
--input "$HERMES_BUILD_DIR/hermes_agent-0.18.2-py3-none-any.whl" \
--output-dir "$HERMES_BUILD_DIR/overlay"
"$HERMES_BUILD_DIR/build-venv/bin/python" -m build \
--wheel --no-isolation --outdir "$HERMES_BUILD_DIR/plugin" \
packages/hermes-chorus-adapter
python -m pip install --only-binary=:all: --require-hashes \
-r packages/hermes-chorus-adapter/hermes_chorus_adapter/locks/runtime-requirements.lock
python -m pip install --no-deps \
"$HERMES_BUILD_DIR/overlay/hermes_agent-0.18.2+chorus.security.2-py3-none-any.whl" \
"$HERMES_BUILD_DIR"/plugin/hermes_chorus_adapter-*.whl
python -m pip check
hermes plugins enable chorus_signals --no-allow-tool-override

Plain hermes-agent==0.18.2 is not an acceptable substitute. The adapter’s exact local-version dependency deliberately fails closed unless the overlay is provided in the install operation.

The builder accepts only PyPI’s attested wheel SHA-256 8f02155cfc84b28bd98551cd18dffec0efa9ec070dd08f90f1a850f1c779492f from source commit 9de9c25f620ff7f1ce0fd5457d596052d5159596. It changes dependency metadata to aiohttp==3.14.3, cryptography==50.0.0, and Pillow==12.3.0, updates the matching Hermes platform and tool.vision lazy pins, records a local version and provenance, and regenerates RECORD. All other code, entry points, modes, and the upstream MIT license remain intact. Its Python 3.12 CLI uses stored ZIP entries and verifies the reviewed output digest in scripts/hermes-security-overlay.sha256, so zlib versions cannot change the result. Only the input is PyPI-attested.

The build and runtime locks cover every third-party CPython 3.12/Linux x86_64 wheel with an artifact hash. The locally built overlay and plugin install only afterward with --no-deps, followed by pip check and the security audit.

Hermes/plugin updates and manual dependency changes can invalidate this host. When the plugin requirement check fails, repeat the complete build/install flow and pip check; do not bypass it. This temporary overlay is retired only after an official Hermes release fixes all three pins and passes the compatibility suite.

Configure the selected Hermes profile with:

CHORUS_URL=https://chorus.example.com
CHORUS_API_KEY=cho_...
CHORUS_SENDER_ROLE=ops
CHORUS_ALLOWED_USERS=identity:ouez5anzn1i9j8lz67a3

Hermes authorization is default-deny. Use exact identity record IDs in CHORUS_ALLOWED_USERS; wildcard and CHORUS_ALLOW_ALL_USERS=true configuration fails closed. Optional stream filters are CHORUS_RINGS, CHORUS_SIGNAL_TYPES, CHORUS_SIGNAL_TAGS, and CHORUS_MIN_URGENCY.

Environment-only configuration auto-enables the platform. An explicit YAML configuration uses Hermes’s actual nested plugin surface:

plugins:
enabled: [chorus_signals]
gateway:
platforms:
chorus_signals:
enabled: true
extra:
chorus_url: ${CHORUS_URL}
api_key: ${CHORUS_API_KEY}
sender_role: ops
rings: [ops]
types: [alert, query]

The base URL must be a root HTTPS origin. HTTP is accepted only for exact localhost, 127.0.0.1, or [::1] loopback hosts. Embedded credentials, paths, queries, fragments, and unresolved ${ENV} placeholders invalidate the platform configuration instead of becoming literal credentials.

The subscriber opens GET /signals/stream with a bearer header, incrementally parses bounded raw UTF-8 SSE lines, consumes the canonical message envelope, and reconnects with Last-Event-ID. No API key is placed in a query parameter. Event bytes, cursor bytes, and model-facing content have separate limits; the content and Hermes platform-message cap default to 64 KiB. Ring/identity records are normalized before the adapter calls Hermes’s build_source(...) and await handle_message(event). That supported entry point keeps allowlists, sessions, model invocation, and the base response/send lifecycle intact.

Hermes passes the resulting response back to send(). The plugin emits one sense signal with the source signal ID as parent_id, the configured sender role, and bridge provenance tags. Hermes’s source thread_id is the normalized parent ID, falling back to the root signal ID. It drops bridge-hop input and commits a recent signal ID only after handle_message succeeds. Deterministic invalid input is quarantined and cursor-acknowledged before any model call; model and handler failures remain replayable.

Configured ring/type/tag/role/urgency filters are enforced again in the plugin, so a server or proxy ignoring query parameters cannot widen delivery. A ring filter explicitly excludes direct and broadcast signals.

Correlated replies carry a stable, body-derived Chorus idempotency_key. Hermes retries transient egress with the same generated payload and key. If its bounded delivery sequence still fails, the completed turn is quarantined and cursor-acknowledged rather than invoking the model again.

Terminal 4xx failures stop the stream; 408, 425, 429, 5xx, and transport failures use bounded retry. HTTP 205 is not accepted as an SSE success. Error text never includes response bodies or the API key.

Standalone cron sending is not registered: the plugin intentionally avoids advertising cron until it supplies Hermes’s complete standalone_sender_fn contract.