Skip to content

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".

Plugin path — extending hermes without forking

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

  1. Create the adapter under cloud/hermes/plugins/<name>/ (mirror an existing one, e.g. sudo_chat). Implement the inbound handler (build a MessageEvent, call handle_message()) and send() / edit_message() for output.
  2. Enable it in config. The provisioner's _SEED_AND_EXEC writes /opt/data/config.yaml with a platforms: map and a plugins.enabled allow-list. Add your platform to both so it's actually turned on. See Provisioning.
  3. Wire the transport at the edges. Inbound usually means a new sudo-api route that forwards to your plugin's port (like :8651/inbound for WhatsApp); outbound means your plugin calls back out (Twilio REST, an SSE push, voice-bridge's say, …).
  4. 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.