I’m debugging an issue in a Python voice agent tha...
# ask-ai
f
I’m debugging an issue in a Python voice agent that uses the LiveKit Agents SDK with an avatar participant. I’m hoping to clarify expected SDK behavior so I can harden my application logic correctly. Context I’m running a survey moderator agent that: • asks questions via TTS/Anam avatar playback • waits for participant responses via STT • sometimes interrupts or clears playback during retries / shutdown In one run, the avatar participant disconnected mid-session. After that, during shutdown / interruption, the agent logged: •
speech not done in time after interruption, cancelling the speech arbitrarily
•
failed to perform clear buffer rpc
•
livekit.rtc.rpc.RpcError: Connection timeout
•
didn't receive playback finished event after clear buffer, marking playout as done arbitrarily
I also noticed that some
track_unmuted
/
track_muted
callbacks appeared to receive an object without
.identity
, which looked like a
RemoteTrackPublication
. Questions 1. What exactly does
wait_for_playout()
guarantee in the Python Agents/avatar stack? Does it mean: • audio has been fully heard by the remote participant, or • audio frames have only been queued / handed off locally? 2. Under what conditions does the avatar stack issue a
clear buffer rpc
? If the avatar participant has already disconnected, is
RpcError: Connection timeout
expected in that case? 3. If the avatar participant disconnects mid-session, what is the recommended application-side behavior during interruption / shutdown? Should app code: skip buffer clear attempts, skip
interrupt()
paths that depend on avatar connectivity, • or simply treat
clear buffer rpc
timeout as benign? 4. Is there a supported way to detect whether avatar/audio output is still audibly playing for the remote participant, beyond
wait_for_playout()
? I’m trying to prevent an idle watchdog from re-prompting while users can still hear the question. 5. For Python room event handlers like
track_unmuted
and
track_muted
, what are the exact callback argument types? Can the second callback argument ever be a publication-like object instead of a participant object? If so, what is the correct supported way to recover the speaker identity? 6. Are there known issues or best practices for: • avatar disconnects during active playout •
speech not done in time after interruption
•
clear buffer rpc
timeout during shutdown / participant disconnect Goal I want to make my agent robust in these scenarios: • avatar disconnects should not cause noisy or blocking teardown • playback cleanup should degrade gracefully if the avatar is already gone • watchdog timers should not fire while TTS is still effectively playing • track event handling should not assume the wrong callback object type If helpful, I can also share a minimal log excerpt or a reduced repro sequence.
✅ 1
t
Thanks for reaching out, Ganesh. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — this is a great, detailed report and the questions are exactly the right ones to harden your agent. I’ll address each question directly, highlight recommended app-side behavior, and finish with short practical guidance you can apply immediately. If you’d like, share a minimal log excerpt or a reduced repro next and I’ll point to the exact lines to watch. Quick acknowledgement • I’m answering your questions about the Python Agents/avatar stack: what
wait_for_playout()
actually guarantees, when
clear buffer
RPCs are sent and why they can time out, how to handle avatar disconnects during shutdown/interrupt, detecting remote audible playback, and the
track_muted
/
track_unmuted
callback argument types. 1) What does wait_for_playout() guarantee? • wait_for_playout() does NOT guarantee the remote participant has actually heard the audio end-to-end. • It guarantees that the agent has finished handing off audio frames into the agent/avatar playback pipeline (i.e., locally queued/processed for playback). • End-to-end audible confirmation requires a playback-finished callback from the avatar server/renderer; if that callback cannot be received (for example because the avatar disconnected), wait_for_playout() cannot confirm remote rendering. How this affects you: • Treat wait_for_playout() as a “local enqueue and local-completion” signal, not a proof of remote audible playback. 2) When does the avatar stack issue a clear buffer RPC, and is Connection timeout expected if the avatar disconnected? • The agent sends a
clear buffer
RPC when you interrupt or cancel playback (to discard any queued avatar frames and reset avatar state). • If the avatar participant (the renderer/server side) has disconnected, the RPC cannot be delivered and a
RpcError: Connection timeout
is expected. • That timeout indicates the avatar endpoint is unreachable over the room connection — this is normal if the participant already left. 3) Recommended application behavior when avatar disconnects mid-session • Treat
clear buffer
RPC timeouts as benign and non-fatal: ◦ Do not block shutdown or interruption flow waiting for RPC success. ◦ Catch RpcError/Connection timeout and continue cleanup. ◦ Log at debug or info level rather than failing the agent flow. • Practical pattern: a. Wrap RPC calls in try/except and handle RpcError as recoverable. b. Cancel/cleanup local speech handles and pending state regardless of RPC outcome. c. If you rely on graceful drain, consider
session.shutdown(drain=True)
semantics (or your equivalent) but do not let RPC timeouts stall the whole shutdown. • Alternatively, if you have strong reasons to avoid issuing clear buffer after disconnect, you can check participant presence before calling clear buffer; but that check can race (participant may disconnect between check and RPC). Safer is “attempt RPC, handle timeout as benign.” 4) Is there a supported way to detect whether audio is still audibly playing remotely (beyond wait_for_playout())? • There is no guaranteed SDK API that confirms “remote user is still hearing audio” once the audio has been handed off. • The next-best signals: ◦ The avatar server’s playback-finished RPC/callback — rely on it when available. ◦ Use wait_for_playout() to avoid immediate re-prompting (best available local signal). ◦ Guard watchdog timers by: start watchdog only after a known idle period following wait_for_playout(), or reset watchdog when you get explicit playback-finished callbacks. • Practical approach for your watchdog: ◦ Do not trigger re-prompting until wait_for_playout() completes. ◦ Add a small safety window (e.g., a configurable short delay) after wait_for_playout() before the watchdog can reprompt to account for network/playout jitter. ◦ If the avatar disconnects and you don’t get a playback-finished callback, treat the clear-buffer timeout as benign and allow watchdog to proceed after a conservative timeout. 5) track_unmuted / track_muted callback argument types — can the second argument ever be a publication-like object? • The event signature is: (publication, participant). ◦ First argument: TrackPublication (e.g., RemoteTrackPublication). ◦ Second argument: Participant instance representing the publisher. • The second argument should be a Participant object and you should be able to read
participant.identity
. • If you observe a publication-like object in the participant slot: ◦ That indicates an SDK/event-dispatching mismatch or a bug or version inconsistency in the SDK stack or wrappers. ◦ Actionable safeguards: ▪︎ Add a defensive type check before accessing
.identity
. ▪︎ If the second arg is a RemoteTrackPublication, retrieve the identity from the publication’s publisher information if available, or fall back to participant lookup by publication.sid or other metadata. Example defensive pattern:
Copy code
def on_track_muted(publication, maybe_participant):
    if hasattr(maybe_participant, "identity"):
        publisher_id = maybe_participant.identity
    elif hasattr(publication, "publisher_identity"):
        publisher_id = publication.publisher_identity
    else:
        publisher_id = "<unknown>"
• If you can reproduce this mismatch reliably, share a minimal log/repro — it’s worth verifying you’re on compatible SDK versions and that no wrapper is changing arguments. 6) Known issues and best practices (summary) • Avatar disconnects during active playout: ◦ Expected in unreliable networks; handle gracefully. ◦ Best practice: agent remains functional; clear buffer rpc failures are benign. • `speech not done in time after interruption`: ◦ This is a fallback safety path when play-out doesn’t complete in a timeout after interruption. ◦ It’s a defensive behavior: the agent marks playout done to avoid deadlocks. ◦ Use wait_for_playout() patterns and avoid awaiting long-running speech handles without timeouts. •
clear buffer rpc
timeout during shutdown / participant disconnect: ◦ Expected and benign if participant left. ◦ Treat as non-blocking; continue shutdown. Concrete recommendations to harden your agent • Wrap avatar RPCs in try/except and treat RpcError/Connection timeout as recoverable. • Use wait_for_playout() to gate re-prompts; add a small configurable safety window after it completes. • Do local cleanup (cancel speech handles, clear local buffers) even if RPCs fail. • Log these RPC timeouts at debug/info level so they don’t look like critical errors. • Add defensive type checking in track event handlers and recover identity from publication metadata if needed. • If you need exact audible confirmation for users, build a protocol-level acknowledgement (e.g., avatar server sends explicit playback-finished event that you treat as authoritative) — otherwise rely on local handoff + playback-finished callbacks when present. Next steps / offer • If you want, paste a minimal log excerpt showing the exact sequence (timestamps around the disconnect + RPC attempts). I can point to which lines to suppress or which checks to add. • If you can reproduce the track callback identity mismatch, share a tiny repro and I’ll show how to adapt the handler safely and what to inspect in SDK versions. If you’d like, I can convert the recommendations into a small code-template for your shutdown/interruption path (with try/except handling, watchdog gating, and defensively-typed track handlers). Which would help more: a log review, a code-template, or both? Sources: client-sdk-js | livekit/livekit_composite | DeepWiki | Agent speech and audio | LiveKit Documentation | Bringing AI avatars to voice agents | LiveKit | How Playback built a global stadium for watching sports together | LiveKit | Forwarding to the frontend | LiveKit Documentation, with interactive elements