Adding a platform plugin¶
This is how we add a new way to reach the agent without forking hermes. All three of
our surfaces (twilio_whatsapp, sudo_chat, sudo_voice) are built this way.
The extension point¶
hermes has a documented plugin path: code placed in /opt/data/plugins/ is
auto-discovered by the upstream plugin loader at boot. The provisioner bind-mounts our
plugins directory there (read-only) when it spawns each per-user container. The contract
is upstream's gateway/platforms/ADDING_A_PLATFORM.md → "Plugin Path".
Why a plugin gets you everything for free¶
Because the adapter registers as a first-class platform, the agent automatically gets, for your new surface:
send_message(target="<your_platform>", chat_id=…, content=…)— proactive sends.cronjob(deliver="<your_platform>", …)— scheduled sends.- System-prompt hints, status display, and the other ~16 integration points upstream documents — without touching core.
Platform metadata (sender name, reply-to, etc.) arrives as structured fields on the
MessageEvent, not as text prepended to the prompt.
The recipe¶
- Create the adapter under
cloud/hermes/plugins/<name>/(mirror an existing one, e.g.sudo_chat). Implement the inbound handler (build aMessageEvent, callhandle_message()) andsend()/edit_message()for output. - Enable it in config. The provisioner's
_SEED_AND_EXECwrites/opt/data/config.yamlwith aplatforms:map and aplugins.enabledallow-list. Add your platform to both so it's actually turned on. See Provisioning. - Wire the transport at the edges. Inbound usually means a new
sudo-apiroute that forwards to your plugin's port (like:8651/inboundfor WhatsApp); outbound means your plugin calls back out (Twilio REST, an SSE push, voice-bridge'ssay, …). - Pick a port distinct from the existing ones (api_server
:8642, twilio_whatsapp:8651, sudo_chat:8652, sudo_voice:8653).
Worked examples to copy from¶
| Plugin | Inbound | Outbound | Notes |
|---|---|---|---|
sudo_chat |
sudo-api POSTs :8652/inbound |
POSTs /v1/internal/chat/push → SSE fan-out |
Per-token streaming. |
twilio_whatsapp |
sudo-api POSTs :8651/inbound |
Twilio Messages REST | Routed by phone via family_members. |
sudo_voice |
(proactive-only) | POSTs voice-bridge :18087/v1/internal/voice/say |
Returns 404 if device offline. |
Config schema gotcha
hermes uses a top-level platforms: map (each value has enabled/extra). Older
api_server: + gateways: top-level keys are silently ignored — and api_server
then falls back to 127.0.0.1, unreachable across the docker network. Always use the
platforms: shape.
Don't override the image ENTRYPOINT
Upstream's entrypoint activates the venv that puts hermes on $PATH. Override only
at docker run time and call upstream's entrypoint from the wrapper, the way
_SEED_AND_EXEC does. See Provisioning.