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.
Build the reviewed host and enable it
Section titled “Build the reviewed host and enable it”From the Chorus repository root:
python3.12 -m venv .venv-hermes-chorussource .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.lockcurl --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-adapterpython -m pip install --only-binary=:all: --require-hashes \ -r packages/hermes-chorus-adapter/hermes_chorus_adapter/locks/runtime-requirements.lockpython -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-*.whlpython -m pip checkhermes plugins enable chorus_signals --no-allow-tool-overridePlain 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.comCHORUS_API_KEY=cho_...CHORUS_SENDER_ROLE=opsCHORUS_ALLOWED_USERS=identity:ouez5anzn1i9j8lz67a3Hermes 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.
Message path
Section titled “Message path”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.