Microsoft Teams Bridge for Hermes Agent
Microsoft Teams voice/video (Conversational Video Interface) for Hermes Agent, packaged as a standalone, pip-installable plugin: install it on top of a normal Hermes install, no fork required.
The plugin (name msteams_call) hosts the HMAC-authenticated WebSocket bridge that
the hosted StandIn media bridge dials into, and drives the call: realtime (OpenAI/Azure
speech-to-speech) or streaming (STT→agent→TTS), camera/screen vision, the avatar
driver cues (expression / visemes / show-to-caller), group-call etiquette, DTMF,
bilingual EN/AR, and meeting recap/minutes (posted to the chat, with a local
.docx artifact).
Two ways to connect to Teams
StandIn Managed Bot (recommended). StandIn provides the Teams bot: install StandIn from the
Teams Store, connect this agent in the StandIn portal, and paste one secret. No Azure bot
registration, no App ID or client secret, no separate chat plane to run. Voice and chat are two lanes of
the SAME StandIn connection - a WebSocket on the calling port and HTTP on the messages port - hosted
by one process, whether that is msteams-call serve or the gateway-resident platform.
plugins:
entries:
msteams_call:
config:
# ONE connection secret from the StandIn portal - covers calls AND chat.
secret: ${MSTEAMS_CALL_SECRET}
The chat listener defaults to 0.0.0.0:8444 because the StandIn gateway must reach it. If you reach
your agent over a private network (Tailscale, VPN, a reverse proxy), set host to that
interface, or firewall the port - the HMAC keeps unauthenticated callers out, but an open port is
still an open port.
One agent instance serves one StandIn connection: the secret is a single value scoped to one tenant binding. Serving several tenants means several instances, each with its own secret. Never share one secret across tenants.
Bring your own Azure bot (advanced). You own the Entra app, client secret and Azure Bot resource,
and the Teams chat plane is handled by Hermes's own platforms/teams adapter rather than here.
Choose this when the bot must live entirely inside your tenant.
Getting started
This plugin adds voice and video (CVI) on top of Hermes Agent's Microsoft Teams messaging. Set those up first:
-
Install Hermes Agent using the official docs at hermes-agent.nousresearch.com.
-
Set up Microsoft Teams messaging in Hermes (bot app + credentials): Teams messaging docs.
-
Subscribe to StandIn (standin.komaa.com, free tier), the hosted media bridge that joins the Teams call and connects to this plugin.
-
Add this plugin. The one-line installer detects your Hermes venv and walks you through the config (mode, shared secret, provider key):
curl -fsSL https://standin.komaa.com/install.sh | bash
Prefer to do it by hand? See Install on Hermes and Configure.
Install on Hermes
Install into the same Python environment as Hermes: it discovers the plugin via
the hermes_agent.plugins entry-point and imports it in-process.
First locate the Hermes venv (the installer puts it under ~/.hermes/.../venv):
find ~ -path "*/.hermes/*/venv" -type d 2>/dev/null
Then install into that venv, targeting its interpreter (Linux/macOS
<venv>/bin/python, Windows <venv>\Scripts\python.exe), or activate the venv
first and drop --python.
A. from PyPI (recommended):
uv pip install --python /path/to/hermes/venv/bin/python hermes-msteams-bridge
Or, with the Hermes venv activated:
pip install hermes-msteams-bridge
B. from GitHub (latest / pre-release):
uv pip install --python /path/to/hermes/venv/bin/python \
"git+https://github.com/komaa-com/hermes-msteams-bridge.git"
C. from a local checkout (development):
git clone https://github.com/komaa-com/hermes-msteams-bridge.git
uv pip install --python /path/to/hermes/venv/bin/python -e ./hermes-msteams-bridge
Installing into the wrong environment means Hermes won't see the plugin. Faster audio (optional): add the
numpyextra, e.g.hermes-msteams-bridge[numpy].
Enable + run
Entry-point plugins are opt-in: add msteams_call to plugins.enabled in
~/.hermes/config.yaml (see Configure below). hermes plugins enable
does not work for pip-installed plugins (it only sees bundled/user-dir plugins),
so enable it in config:
plugins:
enabled:
- msteams_call
entries:
msteams_call:
config:
secret: ${MSTEAMS_CALL_SECRET} # the StandIn connection secret - covers calling AND messages
host: 127.0.0.1 # both lanes; the tunnel terminates TLS and proxies to loopback
# WITHOUT a caller policy the bridge accepts NOTHING: the allowlist IS the policy and an empty
# one denies every inbound call, so a setup that otherwise looks finished answers nothing.
# Name trusted callers here, or set allow_all: true to take whatever StandIn routes to you.
allow_all: true
platforms:
msteams_call:
enabled: true # the gateway hosts the bridge; without this nothing listens
Then run the bridge (handlers: realtime | streaming | echo | logging):
hermes msteams-call serve --handler realtime
And, separately, the Teams chat plane + cron:
hermes gateway run
Configure
Config lives in Hermes's own files (this package ships none). Non-secret settings go
in config.yaml; secrets go in .env and are referenced with ${VAR}.
~/.hermes/config.yaml, under plugins.entries.msteams_call.config:
plugins:
enabled:
- msteams_call # entry-point plugins are opt-in
entries:
msteams_call:
config:
secret: ${MSTEAMS_CALL_SECRET} # MUST match the secret StandIn gave you
host: 127.0.0.1
port: 8443 # voice WS StandIn dials: ws://host:port/msteams/calling
max_call_duration_s: 0 # hard wall-clock cap per call in seconds (0 = unlimited)
meeting_recap: true # optional: post minutes at call end
# share_point_site_id: ${TEAMS_SHAREPOINT_SITE_ID} # optional: future large-file path (file card itself needs only the bot creds)
allowlist: [] # caller AAD object ids (empty = deny all inbound callers)
allow_all: false # explicit opt-in: accept any caller when the allowlist is empty
allowlist_allow_names: false # also match the allowlist against display names (weaker; default off)
session_scope: per-call # per-call | per-thread | per-aad
wake_phrases: [assistant, hermes] # group-call wake phrases (speak only when addressed)
show_file_root: "" # show_file containment root (default <hermes home>/workspace/teams_call_show)
# Outbound "call me back" (StandIn places the return call over its loopback endpoint):
worker_base_url: http://127.0.0.1:9440 # loopback endpoint StandIn exposes for place-call
allow_remote_worker: false # refuse a non-loopback place-call target unless set
# Realtime (speech-to-speech) brain - Azure OpenAI Realtime:
realtime:
backend: azure # azure | openai
azure_endpoint: https://<your-azure-resource>.cognitiveservices.azure.com
azure_deployment: gpt-realtime
azure_api_version: 2025-04-01-preview
voice: cedar
api_key: ${AZURE_FOUNDRY_API_KEY}
vad_threshold: 0.5
prefix_padding_ms: 300
silence_duration_ms: 500
languages: [] # e.g. [en, fr, de, ar]; empty = auto-detect and mirror
Public OpenAI instead of Azure: set
backend: openai,model: gpt-realtime,api_key: ${OPENAI_API_KEY}, and drop theazure_*keys. Streaming (STT→agent→TTS) instead of realtime: omit therealtime:block and runhermes msteams-call serve --handler streaming(needsffmpegon PATH).
~/.hermes/.env, the secrets referenced above (plus Teams chat-plane creds if you
also run hermes gateway run):
# Voice bridge
TEAMS_CALL_SHARED_SECRET=<same value you set in StandIn>
AZURE_FOUNDRY_API_KEY=<azure-openai-key> # or OPENAI_API_KEY for public OpenAI
# Teams chat plane (platforms/teams) - only if you run the gateway:
TEAMS_CLIENT_ID=<bot-app-id>
TEAMS_CLIENT_SECRET=<bot-app-secret>
TEAMS_TENANT_ID=<azure-ad-tenant-id>
secret must match the secret StandIn gave you or the HMAC
handshake fails. Full key reference (every option, defaults, env vars, streaming
mode, the wire protocol): the
Configuration Reference
and Wire Protocol
docs pages. Contributor architecture notes live in
DESIGN.md; the module-level guide is in
src/hermes_msteams_bridge/README.md.
Upgrade / uninstall
Upgrade:
uv pip install --upgrade hermes-msteams-bridge
Uninstall (it then disappears from hermes plugins list):
uv pip uninstall hermes-msteams-bridge
How it loads
Hermes discovers pip plugins via the hermes_agent.plugins entry-point group. This
package exposes:
[project.entry-points."hermes_agent.plugins"]
msteams_call = "hermes_msteams_bridge"
Hermes imports hermes_msteams_bridge and calls its register(ctx), registering the
teams-call CLI, the status tool, and the session hook. Entry-point plugins are
opt-in, so msteams_call must be in plugins.enabled (add it in config.yaml;
hermes plugins enable does not see pip-installed plugins).
Requirements
- A working Hermes Agent install (the host; not a PyPI package).
- Python ≥ 3.10 and
aiohttp;ffmpegon PATH for streaming-mode TTS decode. - StandIn (standin.komaa.com), the hosted media bridge that joins the Teams call and connects to this plugin over the HMAC WebSocket.
Relationship to the bundled plugin
This is the same code as the original in-tree plugin, repackaged for pip
distribution so you don't have to fork Hermes. Install it on vanilla Hermes; don't
also keep a bundled msteams_call (same name → the entry-point would shadow it).
- Voice/CVI works fully on vanilla Hermes.
- Meeting minutes post to the chat with the Word
.docxattached as a native file card (the same Bot Framework attachment contract the Hermes Teams adapter uses; needs the chat plane'sTEAMS_CLIENT_ID/SECRET/TENANT_ID), degrading to text when creds are absent; a Word-openable copy is always kept under the Hermes workspace.
License
MIT - see LICENSE. Copyright (c) 2026 Komaa DigiTech. This is an independent plugin; it is not affiliated with or endorsed by Nous Research. "Hermes" is a project of Nous Research. Docs at https://docs.komaa.com/
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hermes_msteams_bridge-0.5.0.tar.gz.
File metadata
- Download URL: hermes_msteams_bridge-0.5.0.tar.gz
- Upload date:
- Size: 172.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
de50252458f5d3953d07ec1fd004d9d4dcf23ceed3f8095ee3b123dce01a0003
|
|
| MD5 |
b8cd63ad149f7265ef5ba9c79a91b7d5
|
|
| BLAKE2b-256 |
c3f86db143d56b28da5c701390c51d9e4744982332761b570e4a0ba5b54835e1
|
Provenance
The following attestation bundles were made for hermes_msteams_bridge-0.5.0.tar.gz:
Publisher:
pypi-publish.yml on komaa-com/hermes-msteams-bridge
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hermes_msteams_bridge-0.5.0.tar.gz -
Subject digest:
de50252458f5d3953d07ec1fd004d9d4dcf23ceed3f8095ee3b123dce01a0003 - Sigstore transparency entry: 2407766186
- Sigstore integration time:
-
Permalink:
komaa-com/hermes-msteams-bridge@65ef9bc6333273196083a635fab487f6f4f0b19b -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/komaa-com
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-publish.yml@65ef9bc6333273196083a635fab487f6f4f0b19b -
Trigger Event:
push
-
Statement type:
File details
Details for the file hermes_msteams_bridge-0.5.0-py3-none-any.whl.
File metadata
- Download URL: hermes_msteams_bridge-0.5.0-py3-none-any.whl
- Upload date:
- Size: 141.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
35c2c98753e115cb75a42bf277ccc380e412868dad66c1c447ca3775f5217ffe
|
|
| MD5 |
b443c895a5f6da6c492ed1ad3d32375d
|
|
| BLAKE2b-256 |
4cc1bca07f4fea9bcb730a814881dffcfeef9a45b31c1952b45a86a7102aa37d
|
Provenance
The following attestation bundles were made for hermes_msteams_bridge-0.5.0-py3-none-any.whl:
Publisher:
pypi-publish.yml on komaa-com/hermes-msteams-bridge
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hermes_msteams_bridge-0.5.0-py3-none-any.whl -
Subject digest:
35c2c98753e115cb75a42bf277ccc380e412868dad66c1c447ca3775f5217ffe - Sigstore transparency entry: 2407766600
- Sigstore integration time:
-
Permalink:
komaa-com/hermes-msteams-bridge@65ef9bc6333273196083a635fab487f6f4f0b19b -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/komaa-com
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi-publish.yml@65ef9bc6333273196083a635fab487f6f4f0b19b -
Trigger Event:
push
-
Statement type: