Run the Example
Three example projects show a full working setup, all in this repository: a minimal bridge embedding (examples/basic-bridge) and two ready-to-run agents (examples/voice-agent and examples/avatar-agent). This page walks through them so you understand every moving part.
What a working setup needs
Section titled “What a working setup needs”A LiveKit call has three processes, two of them yours:
- Your agent worker - registers with your LiveKit project under an
agent_nameand waits for dispatch. - This bridge - creates a room per Teams call, dispatches the agent into it, relays audio.
- StandIn (hosted) - joins the Teams call and dials the bridge.
1. Run the example voice agent
Section titled “1. Run the example voice agent”The example agents are plain LiveKit agents - nothing Teams-specific. The pipeline is picked from what your environment offers: Azure speech-to-speech realtime, Azure STT/LLM/TTS, or plain OpenAI (see the example’s README).
git clone https://github.com/komaa-com/livekit-msteams-bridge-pycd livekit-msteams-bridge-py/examples/voice-agentcp .env.example .env # LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET + one model stackuv syncuv run python -m livekit.agents download-filesuv run worker.py dev(Plain pip works too: pip install -r requirements.txt && python worker.py dev.)
The worker registers as standin-agent and waits - it will not join anything until the bridge creates a room and dispatches it.
2. Run the bridge example
Section titled “2. Run the bridge example”pip install livekit-msteams-bridgegit clone https://github.com/komaa-com/livekit-msteams-bridge-pycd livekit-msteams-bridge-py/examples/basic-bridgecp .env.example .env # same LiveKit project + LIVEKIT_AGENT_NAME=standin-agent + BRIDGE_SECRETpython main.pyIt prints the WebSocket URL to give StandIn:
Point your StandIn identity's agent WebSocket URL at ws://<this-host>:9442/msteams/callingThe main.py is the recommended embedding shape in ~25 lines: load_dotenv(), load_config() (fails loud on any misconfiguration), await start_server(cfg), and a graceful await server.close() on Ctrl-C / SIGTERM that ends live calls with a spoken-protocol session.end rather than a hard drop.
3. Connect StandIn and call
Section titled “3. Connect StandIn and call”- Expose port 9442 with a tunnel (
tailscale funnel --bg --set-path /msteams/calling http://127.0.0.1:9442/msteams/calling, which serves the bridge atwss://<your-tailnet-host>/msteams/callingwith no port;cloudflared tunnel --url http://localhost:9442; orngrok http 9442). - In your StandIn dashboard, set the identity’s Agent voice URL to the
wss://.../msteams/callingform and make sure the shared secret equalsBRIDGE_SECRET. - Call your Teams bot (or join the sandbox meeting). The bridge creates the room, dispatches
standin-agent, and the agent answers.
4. Swap in the avatar agent
Section titled “4. Swap in the avatar agent”examples/avatar-agent is the same pipeline plus a lip-synced bitHuman avatar. Two extra variables in that agent’s .env (BITHUMAN_API_SECRET, BITHUMAN_MODEL_PATH), then:
cd ../avatar-agent && uv sync && uv run worker.py devStop the voice worker first: both register as standin-agent, and explicit dispatch resolves a single name. The caller hears the avatar’s audio; the avatar’s video stays in the room (the Teams tile is rendered by StandIn’s own animated avatar - see Agents and Dispatch).
What the example agents demonstrate
Section titled “What the example agents demonstrate”Each example shows the three integration points your own agent can use:
agent_nameinWorkerOptions- the dispatch contract withLIVEKIT_AGENT_NAME.ctx.job.metadata- per-call caller context (caller_name,tenant_id,call_direction,user_idwhen known) for greetings and personalization.msteams.context/msteams.goodbye/msteams.visiondata topics - call context, the governor’s goodbye handler (interrupt the current turn, speak the line), and the opt-in ambient-vision byte stream.
Details and copy-paste handlers: Agents and Dispatch.
From example to your own service
Section titled “From example to your own service”- Keep your own agent worker exactly as it is for WebRTC users - just give it an
agent_name. - Embed the bridge (
await start_server(load_config())) or run the stock CLI. - Set the governor variables (
MAX_CALL_MINUTES,GOODBYE_TEXT) before production. - For tests, inject a fake room with the
connect_roomargument - see Library API.