Skip to main content
Two sources are supported: config.yaml takes precedence, .env is the fallback. The recommended pattern keeps secrets in .env and references them from config.yaml with ${VAR} (the loader expands them), so config lives in one declarative file. Most config.yaml keys have a matching MSTEAMS_BRIDGE_* env var (e.g. realtime.azure_endpointMSTEAMS_BRIDGE_AZURE_ENDPOINT, secretMSTEAMS_BRIDGE_SECRET); config.yaml wins where both are set. Exceptions: path is config-only, and the SharePoint site id uses TEAMS_SHAREPOINT_SITE_ID (shared with the chat plane).

config.yaml

%LOCALAPPDATA%\hermes\config.yaml (Windows) / ~/.hermes/config.yaml:

.env

%LOCALAPPDATA%\hermes\.env / ~/.hermes/.env - the secret store (used directly, or referenced above). A fully env-only setup works too:
backend: openai uses public OpenAI instead of Azure - set the OpenAI key in place of the Azure endpoint/deployment fields.
For the hosted StandIn bridge to connect, bind a reachable address: set host: 0.0.0.0 (or keep 127.0.0.1 behind a tunnel that forwards to it) - the default loopback bind is only reachable from the same machine. allow_remote_worker has no effect on inbound connections; it only governs where outbound “call me back” requests may be sent (see the key reference below).

Key reference

Hermes implements the same feature set as the OpenClaw plugin; keys are snake_case and each has a matching MSTEAMS_BRIDGE_* env var.

Inbound policy (voice): Hermes vs OpenClaw

Hermes has no inboundPolicy key and no pairing mode for calls - the allowlist is the inbound policy:
  • allowlist set → only those AAD object ids may call (add allowlist_allow_names: true to also match display names, which is weaker).
  • allowlist empty → all inbound callers are denied, unless you explicitly opt out with allow_all: true (env: MSTEAMS_BRIDGE_ALLOW_ALL). An unset allowlist never means “open to all”.
  • When allowlist is empty, it inherits TEAMS_ALLOWED_USERS (the chat plane’s allowlist), so one AAD list can cover both voice and chat.
The openclaw pairing approve msteams <CODE> steps you will see in the Quickstart and Teams app pages are OpenClaw-only. The Hermes equivalents are: edit allowlist for voice callers, and for Teams chat DMs (handled by the Hermes gateway’s Teams platform, not msteams_bridge) either approve the pairing code the bot sends an unknown sender (hermes pairing approve teams <CODE>) or set TEAMS_ALLOWED_USERS - see the bot ignores Teams chat.

Run

Show config + readiness:
Serve the bridge (use --handler streaming for streaming mode, which needs ffmpeg):
Or run it standalone:
Register both endpoints in your StandIn dashboard, with the same secret: StandIn dials the public addresses, so both have to be reachable from the internet. There is no port in either public URL when you mount by path, which is the documented way to expose them - see Expose your agent for the two commands and the probe recipes.

Microsoft Graph permissions

The bot’s Azure AD app needs admin-consented application permissions - see the canonical Graph permissions table on the Create the Azure bot page.
The secret must byte-match the value StandIn shows you, or the HMAC handshake fails: calls connect and immediately hang up, and chat relays are rejected.

Gateway-managed mode (two layers)

To have hermes gateway run host the voice bridge itself (one process, no separate serve), enable both the plugin and the platform:
In this mode scheduled jobs can deliver by voice: a cron job with deliver=msteams_bridge places a call and speaks the result. Set MSTEAMS_BRIDGE_HOME_AAD to the AAD object id the gateway should call for those deliveries (the callee must also be on the allowlist). Run gateway mode or standalone serve, never both - the second owner fails loudly on the port bind.

Voice & language tuning lives in Hermes

STT and TTS provider selection, voices, and language hints are host configuration, not plugin keys: set Hermes’s own stt: and tts: blocks in ~/.hermes/config.yaml (see the Hermes TTS and voice-mode feature docs). The plugin inherits them - streaming mode dispatches the host’s text_to_speech tool and transcription chain, so all Hermes STT/TTS providers apply unchanged. The call also answers with the host’s SOUL.md identity and knows the host’s installed skills (chat/call parity).