msteams_bridge) is the Python half of the Teams CVI for
Hermes Agent. It hosts the HMAC WebSocket that StandIn connects to, runs the dialogue +
perception, and emits avatar driver cues. Chat-plane features live in the existing Teams adapter.
This adds voice/video (CVI) on top of Hermes Agent’s existing
Teams messaging - run both
for chat + calls.
Requirements
- A working Hermes Agent install (the host).
- A StandIn subscription (standin.komaa.com, free package) - the hosted media bridge connecting to this plugin. See Architecture.
- Realtime mode: an OpenAI / Azure OpenAI realtime key. Streaming mode: needs
ffmpegonPATH.
Install
Install into the same Python environment as Hermes so thehermes_agent.plugins entry-point is
discovered:
documents extra enables show_file document rendering (pypdfium2 +
Pillow). Optional system tools: LibreOffice (soffice on PATH) for showing
Office documents, ffmpeg for streaming-mode TTS decode. The show_web_page
tool uses the host Hermes browser tools - no extra install.
Or with pip:
plugins.enabled in
~/.hermes/config.yaml (the configuration page shows
the full block):
hermes plugins enable msteams_bridge works too when run interactively, but it may
prompt for tool-override consent, so scripts and installers should write the
YAML instead.
Verify
Show config + readiness - the status probe checks every Hermes surface the plugin depends on (vision, STT, TTS, chat delivery, browser, document rendering) and reports two levels:boundaries_ok (the contract surface
exists) and operational_ok (providers/credentials/dependencies actually
work). Anything missing is named explicitly, so a broken Hermes integration
surfaces here and at serve startup rather than mid-call. External paths
(Teams reachability, the StandIn endpoint, provider round trips) are not
probed - those surface on the first real call. Tested against Hermes Agent
0.19.0 (PyPI floor) and the current 0.20.x line:
serve hosts the calling lane: the media WebSocket on 9442, at /msteams/calling. Chat is a
separate process on a separate port - the Hermes gateway’s own Teams platform - so a full connection
needs both of these reachable:
Calling alone gives you a bot that talks but cannot read chat; chat alone gives you chat with no
calls.
Expose both endpoints
StandIn connects to your agent from the internet, so both paths must be reachable - a public host, or a tunnel. The mount commands, the probe recipes, and the values you register live in one place:Expose your agent
The two mount commands for Hermes (
9442 for voice, 3978 for chat), how to check a mount actually
works, and what to paste into the StandIn dashboard.wss://<machine>.<tailnet>.ts.net/msteams/calling and
https://<machine>.<tailnet>.ts.net/api/messages (with the same connection secret) on your
connection in the dashboard, then place a Teams call to your
bot.
Probing the voice path by hand? Pass
--http1.1. Through a funnel, curl without it sends a plain
GET and gets a 404 that looks exactly like a missing mount. And on Hermes the route is
/msteams/calling/{call_id}, so a bare /msteams/calling legitimately 404s even when the bridge
is healthy. Add any segment and a live bridge answers 401. Full details on
Expose your agent.Because both lanes ride path routing on 443, the 8443 and 10000 funnel slots stay free - so a second
runtime (the OpenClaw plugin, say) can share the same machine. Register each runtime as its own
connection with its own secret.
On a StandIn Managed Bot connection the chat lane is the plugin’s own
/msteams/messages
listener on 9444 rather than /api/messages on 3978, and the one-line installer sets both lanes
up for you with --connection managed --runtime hermes and the connection secret from the
dashboard: Quickstart: StandIn Managed Bot.The bot answers but does nothing until recording starts.
require_recording_status defaults to
true, so the plugin engages only once Teams reports recording active - which is why a call can
connect and the agent stay silent or refuse to act. Turn recording on in the call, or set
require_recording_status: false. What the agent then does with a request - replying, posting a
message, running a skill - is Hermes Agent’s own logic and configuration, not the bridge’s.Next: the full configuration reference - config.yaml +
.env, realtime
backend, and Graph permissions.Links
Source
PyPI
Configuration
Gateway-managed mode (one process)
With themsteams_bridge platform enabled in the gateway config, hermes gateway run hosts the voice bridge itself - no separate serve process. Two layers
must both be on:
- the plugin:
msteams_bridgeunderplugins.enabledinconfig.yaml; - the platform: a
msteams_bridgeplatform entry enabled in the gateway configuration (same place as your Teams chat platform).
config.yaml shape:
deliver=msteams_bridge - the bot calls you and speaks the result). If a
standalone hermes msteams-bridge serve is already bound to the port, the
adapter fails loudly instead of splitting traffic - run one or the other.
Using the StandIn one-line installer? Set STANDIN_GATEWAY_MODE=1 in the
environment before running it: the installer then writes both layers
above, stops any standalone bridge, and restarts the gateway instead of
launching serve.
hermes msteams-bridge smoke is an offline install check: it proves config
resolution, the HMAC handshake, wire decode, and session lifecycle against an
ephemeral local listener. It does not test the configured listener,
gateway platform activation, Teams connectivity, provider credentials, or the
public tunnel - the first real call is the test for those.