Governors and Privacy
Two governors
Section titled “Two governors”A call can be cut off from two places:
- StandIn-side - StandIn enforces the caller’s tier limits and, at cutoff, asks the bridge to wind the call down. The bridge forwards the goodbye request to the agent on
msteams.goodbye. - Bridge-side (
MAX_CALL_MINUTES) - an independent hard cap you set on the bridge. LiveKit doesn’t know about your billing, so this is your own backstop against a call that runs forever.
Both funnel through the same goodbye path; the first to fire wins, and a duplicate goodbye is ignored.
Separately, STALE_CALL_REAPER_SECONDS (default 120) ends a call whose agent never joined the room at all. That is not a governor - there is nobody to say goodbye - so it just tears the call down with session.end reason no-answer. See Troubleshooting.
How the goodbye works
Section titled “How the goodbye works”There is no bridge-side TTS on the room transport - the bridge cannot synthesize speech into the room. So the governor’s goodbye is a msteams.goodbye data message carrying the text; your agent speaks it. The sequence on MAX_CALL_MINUTES:
- The bridge arms a hard deadline (
GOODBYE_GRACE_MS+ headroom) so nothing can wedge the call past its limit. - It sends
msteams.goodbyeto the agent. - It waits
GOODBYE_GRACE_MSfor the agent to speak, then ends the call.
Because the bridge can’t know your agent’s real speech duration, GOODBYE_GRACE_MS (default 8 s) is the budget. If the agent’s current turn outlasts the grace, the goodbye can be cut - so have your msteams.goodbye handler interrupt the current turn and speak the line with interruptions disabled (see Agents and Dispatch).
Privacy and data handling
Section titled “Privacy and data handling”- No recording, no persistence. This bridge stores nothing on disk. Audio is relayed frame-by-frame between the worker socket and the room and never written out.
- Video is opt-in and gated. Caller screen-share/camera frames are ignored entirely unless
AMBIENT_VISION=true. When it is on,REQUIRE_RECORDING_STATUS(defaulttrue) means a frame is not even held in memory before Teams reports recording active, so nothing captured beforehand can surface later; the newest frame per source is kept only until it is superseded, and everything is dropped at teardown. - Transcripts are read, never kept. The group-call gate reads what your agent publishes on LiveKit’s
lk.transcriptiontopic purely to test it against the wake phrases. No transcript text is stored, forwarded, or logged. - Caller identity is minimal and defaulted. The agent receives
caller_name,tenant_id,call_direction, anduser_id(AAD id) - anduser_idis included only when Teams provides one, so it is never a shared placeholder. Nullable fields default to safe strings, never null. - Metrics carry no call content.
GET /metricsexposes counters (calls, durations, rejects, relay/drop counts) only - never audio, text, or identities. - The room is deleted at teardown (
LIVEKIT_DELETE_ROOM_ON_END=true), so the agent job ends immediately and no room lingers with call context.
Hardening summary
Section titled “Hardening summary”The transport carries the same protections as the sibling bridges: replay-proof single-use HMAC upgrade, connection caps checked before crypto, payload caps, a pre-start timeout, dead-peer detection, duplicate-call 409, a pre-auth crash guard, and a graceful SIGTERM drain. See Architecture for how each fits into the call flow.