Thanks — this is a clear report and a helpful set of questions. I’ll respond to each question directly and then give a short recommendation you can apply immediately to remove the timing-dependent behavior.
Acknowledgement
• I’m responding to the five questions about how Participant linking works for Python Agents (AgentSession / RoomIO), whether the default is timing/order dependent for SIP callers, best practice for telephony agents, how to explicitly bind a participant in current Python Agents, and whether you should migrate from RoomInputOptions to RoomOptions.
Answers
1) Default binding behavior (when you do NOT set participant_identity)
• The AgentSession / RoomIO will link to the “first participant” it chooses in the room by default. Agents auto-subscribe to eligible participant kinds (standard RTC and SIP participants), and if you do not explicitly set a participant identity the agent will use whatever participant the session links to (effectively the first participant it establishes for input).
2) Is that default behavior timing/order dependent for SIP calls?
• Yes. Because the session links to whichever participant is selected when the RoomIO is created/started, race conditions between the SIP caller joining and the agent starting can make the linked participant change between calls. That timing/order dependency can produce intermittent cases where the agent is not linked to the SIP caller — resulting in no user conversation_item_added, no user final transcript, and 0 STT/LLM calls even though TTS runs (agent greetings/inactivity still played). This matches the intermittent failures you described.
3) Is explicit participant binding recommended for telephony agents?
• Yes. Explicitly binding the AgentSession / RoomIO input to the SIP participant identity is the recommended best practice for telephony/one-to-one caller scenarios. This avoids race conditions in multi-participant rooms and ensures the agent always processes the intended caller’s audio.
4) Preferred current method in Python Agents to bind explicitly
• Use RoomOptions(participant_identity=...) when starting the session — this is the recommended and straightforward approach for new code. Alternative options:
◦ RoomOptions(participant_identity="sip-participant-identity") passed to AgentSession.start(...) — recommended.
◦ Construct RoomIO with the participant identity explicitly (RoomIO(session, room=..., participant="...")) and start it before or as part of session start — acceptable for custom flows.
◦ RoomIO.set_participant(...) can be used to change the linked participant dynamically at runtime if you need to switch targets after start.
• Practical note: The simplest and most robust pattern is to determine the SIP participant identity (from room.remote_participants or your SIP signaling) and pass it as
participant_identity
in RoomOptions when calling session.start.
5) Is RoomInputOptions deprecated / should you migrate to RoomOptions?
• RoomOptions is the current unified approach and is used in current examples. RoomInputOptions is not the preferred interface for new Python Agent code; migrate to RoomOptions to manage participant binding and other input/output configuration to avoid unexpected differences or confusion.
How this applies to your symptom (why you saw stt_calls=0 / llm_calls=0)
• If the agent is linked to the wrong participant (or to no participant input) the STT/audio path for the SIP caller will not be processed even though TTS works (TTS is independent outbound). Explicitly binding the session to the SIP participant eliminates the race that leads to intermittent “no user transcript” cases.
Suggested next steps (concise)
1. When you discover the SIP participant in room.remote_participants, capture its participant.identity immediately.
2. Start the agent with `RoomOptions(participant_identity=
that-identity)`:
a. Example pattern:
▪︎ Build room_options = RoomOptions(participant_identity="sip-identity")
▪︎ await session.start(agent=my_agent, room=ctx.room, room_options=room_options)
1. If you need to support callers who join after the agent starts, either:
◦ Start the agent only after the SIP participant is present and you’ve set RoomOptions, or
◦ Use RoomIO.set_participant(...) as soon as you detect the SIP participant to rebind the input dynamically.
If you’d like, tell me:
• How you currently detect the SIP participant (what field you use for identity), and
• Whether the agent must be started before the SIP join or can wait until after — I can suggest the exact code pattern to integrate RoomOptions or a dynamic set_participant flow for your current startup sequence. Sources:
Agent session | LiveKit Documentation |
sip | livekit/livekit_composite | DeepWiki |
How to set a fixed participant for agent audio processing | LiveKit, with interactive elements