Skip to content

Configuration Reference

Every setting the plugin reads, with its config.yaml key, environment variable, default, and meaning. All values match the code in config.py and realtime/openai_client.py.

Values are resolved in priority order:

  1. The plugins.entries.teams_voice.config block in config.yaml.
  2. Environment variables (typically in ~/.hermes/.env).
  3. Safe defaults.

The recommended pattern: keep secrets in .env and reference them from config.yaml with ${VAR} (Hermes’s loader expands them). The plugin ships no config of its own. Secrets are never logged.

plugins:
enabled:
- teams_voice
entries:
teams_voice:
config:
shared_secret: ${TEAMS_VOICE_SHARED_SECRET}
host: 127.0.0.1
port: 8443
# ...bridge keys below...
realtime:
# ...realtime keys below...
config.yaml keyEnv varDefaultMeaning
shared_secretTEAMS_VOICE_SHARED_SECRET"" (unset)HMAC secret shared with StandIn. Required - with no secret the bridge won’t start. Must equal the value paired in StandIn.
hostTEAMS_VOICE_HOST127.0.0.1Bind address for the local WebSocket server. Non-loopback binds are warned about (they expose the secret).
portTEAMS_VOICE_PORT8443Bind port. StandIn dials ws://host:port/voice/msteams/stream/{callId}.
path(config only)/voice/msteams/streamURL path prefix StandIn connects to. Rarely changed.
hmac_window_msTEAMS_VOICE_HMAC_WINDOW_MS60000Clock-skew / replay window for the HMAC handshake, in milliseconds (±60 s).
max_call_duration_sTEAMS_VOICE_MAX_CALL_DURATION_S0.0Hard wall-clock cap on a single call, in seconds. 0 = unlimited. A wedged/never-ending call is torn down once exceeded.
require_recording_statusTEAMS_VOICE_REQUIRE_RECORDING_STATUStrueGate all media processing until Teams recording is active. Recommended on for compliance.
worker_base_urlTEAMS_VOICE_WORKER_BASE_URLhttp://127.0.0.1:9440Loopback HTTP endpoint StandIn exposes for outbound “call me back”. See Outbound Calls.
allow_remote_workerTEAMS_VOICE_ALLOW_REMOTE_WORKERfalsePermit an outbound place-call to a non-loopback worker_base_url. Off by default (SSRF guard - the secret would be sent to that host).
tenant_idTEAMS_VOICE_TENANT_ID (falls back to TEAMS_TENANT_ID)""Default Azure AD tenant for outbound calls.
allowlistTEAMS_VOICE_ALLOWLIST (falls back to TEAMS_ALLOWED_USERS)() (empty)Comma-separated caller AAD object ids allowed to call. Empty = deny ALL inbound callers unless allow_all is set.
allow_allTEAMS_VOICE_ALLOW_ALLfalseExplicit opt-in to accept any inbound caller when the allowlist is empty. Deny-by-default otherwise.
allowlist_allow_namesTEAMS_VOICE_ALLOWLIST_ALLOW_NAMESfalseAlso match the allowlist against caller display names (weaker / spoofable). Off by default.
session_scopeTEAMS_VOICE_SESSION_SCOPEper-callAgent memory continuity: per-call (fresh each call), per-thread (keyed by Teams thread), or per-aad (keyed by caller AAD id).
wake_phrasesTEAMS_VOICE_WAKE_PHRASESassistant, hermesGroup-call wake phrases - in a meeting the agent speaks only when addressed by one of these.
meeting_recapTEAMS_VOICE_MEETING_RECAPfalsePost end-of-call meeting minutes to the Teams chat.
share_point_site_idTEAMS_SHAREPOINT_SITE_ID""SharePoint (OneDrive) site id host,siteGuid,webGuid to attach the minutes .docx to the chat as a file card. Needs the bot app’s Graph Sites.ReadWrite.All. Empty = text-only minutes.
max_vision_per_minuteTEAMS_VOICE_MAX_VISION_PER_MINUTE30Per-call vision spend cap across look_at_screen + ambient push. 0 = unlimited.

Internal defaults (not currently config-driven)

Section titled “Internal defaults (not currently config-driven)”

These have sensible fixed defaults and are not exposed as config keys today:

FieldDefaultMeaning
max_connections64Global concurrent-connection cap (DoS guard).
max_connections_per_ip8Per-IP concurrent-connection cap.
pre_start_timeout_s10.0A connection that doesn’t send session.start within this window is reaped.

These live under plugins.entries.teams_voice.config.realtime (or the matching env vars) and configure the OpenAI/Azure Realtime speech-to-speech engine. Only used by --handler realtime.

config.yaml key (under realtime:)Env varDefaultMeaning
backendTEAMS_VOICE_REALTIME_BACKEND(auto - see below)openai or azure.
api_keyTEAMS_VOICE_REALTIME_API_KEY(see fallbacks)Provider key. OpenAI falls back to OPENAI_API_KEY; Azure falls back to AZURE_OPENAI_API_KEY then AZURE_FOUNDRY_API_KEY.
modelTEAMS_VOICE_REALTIME_MODELgpt-realtimeOpenAI realtime model. (On Azure the deployment name is used as the model.)
azure_endpointTEAMS_VOICE_AZURE_ENDPOINT""Azure OpenAI resource endpoint. Setting this auto-selects the Azure backend.
azure_deploymentTEAMS_VOICE_AZURE_DEPLOYMENT""Azure realtime deployment name (e.g. gpt-realtime).
azure_api_versionTEAMS_VOICE_AZURE_API_VERSION2024-10-01-previewAzure realtime API version.
urlTEAMS_VOICE_REALTIME_URL""Explicit Realtime WebSocket URL override. An *.azure.com URL auto-selects Azure.
voiceTEAMS_VOICE_REALTIME_VOICEalloyRealtime voice name (e.g. cedar).
instructionsTEAMS_VOICE_REALTIME_INSTRUCTIONS(built-in prompt)System prompt for the voice assistant. The default keeps replies brief and delegates real work to the agent.
vad_thresholdTEAMS_VOICE_VAD_THRESHOLD0.5Server-VAD activation threshold.
prefix_padding_msTEAMS_VOICE_PREFIX_PADDING_MS300Audio kept before detected speech start, in ms.
silence_duration_msTEAMS_VOICE_SILENCE_DURATION_MS500Trailing silence that ends a turn, in ms.
input_transcribe_modelTEAMS_VOICE_INPUT_TRANSCRIBE_MODELwhisper-1Model that transcribes caller audio (for wake words / verbal interrupts). Set to none / off / disabled (or empty) to turn off - VAD barge-in still works.
bilingualTEAMS_VOICE_BILINGUALfalsePin the model to detect/mirror the caller’s language (Arabic/English) and translate on request.

Azure is chosen when any of these is true; otherwise OpenAI (bearer auth):

  • backend: azure (or TEAMS_VOICE_REALTIME_BACKEND=azure), or
  • an azure_endpoint is set, or
  • the explicit url contains azure.com.

On Azure, the base URL is built as wss://<endpoint>/openai/realtime?api-version=<ver>&deployment=<deployment> and the api-key header is used instead of bearer auth.

realtime:
backend: openai
model: gpt-realtime
voice: alloy
api_key: ${OPENAI_API_KEY}
vad_threshold: 0.5
prefix_padding_ms: 300
silence_duration_ms: 500
bilingual: false
realtime:
backend: azure
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}

You can run entirely from environment variables (no config.yaml block):

Terminal window
TEAMS_VOICE_SHARED_SECRET=... # must equal the value paired in StandIn
TEAMS_VOICE_HOST=127.0.0.1
TEAMS_VOICE_PORT=8443
TEAMS_VOICE_SESSION_SCOPE=per-thread
TEAMS_VOICE_WAKE_PHRASES=assistant,hermes
# Realtime (Azure):
TEAMS_VOICE_REALTIME_BACKEND=azure
TEAMS_VOICE_AZURE_ENDPOINT=https://<your-azure-resource>.cognitiveservices.azure.com
TEAMS_VOICE_AZURE_DEPLOYMENT=gpt-realtime
TEAMS_VOICE_AZURE_API_VERSION=2025-04-01-preview
TEAMS_VOICE_REALTIME_VOICE=cedar
AZURE_FOUNDRY_API_KEY=...

config.yaml wins wherever both a key and its env var are set.