little-king-41579
02/18/2026, 3:56 PMtall-airline-16756
02/18/2026, 3:56 PMtall-airline-16756
02/18/2026, 3:57 PMweb_participant2 joins the room. I’m happy to help you implement that.
Quick summary of the approach:
• The agent itself must stop/unpublish its audio track when it detects web_participant2 has joined. This requires adding a handler in the agent code for the room’s participant-join event and unpublishing (or muting/stopping) the agent’s audio track when the joining participant’s identity matches web_participant2.
Before I give a concrete snippet tailored to your environment, a couple quick clarifying questions:
1. Which agent SDK are you using: Node.js or Python (or another)?
2. Is the agent running as a LiveKit Agent (using the agent runtime) or as a regular client participant?
3. Do you want the audio permanently unpublished while web_participant2 is present, or should it be re-published automatically after they leave?
If you’re using Node.js or Python, here are concise examples you can adapt.
Node.js (conceptual)
1. Listen for the participant-connected event on the room.
2. If the joining participant’s identity === 'web_participant2', call unpublishTrack() (or `stop()`/`mute()` depending on how you manage the track) for the agent’s audio.
Python (conceptual)
1. Register a participant_connected handler on the room.
2. When identity is 'web_participant2', find the agent’s published audio track and call unpublish_track() (or stop/mute it).
Implementation notes and options:
• Unpublish vs mute/stop:
◦ Unpublishing removes the track publication from the room; other participants will see the publication removed.
◦ Muting or stopping the local audio track keeps the publication but stops audio data; other participants still see the publication (useful if you want to re-enable quickly).
• Re-enable behavior:
◦ If you want audio to resume automatically after web_participant2 leaves, listen for the participant-left event and re-publish (or unmute) the audio when they depart.
• Identity matching:
◦ Use the participant’s identity field (exact match to web_participant2) to trigger the action.
If you tell me:
• which SDK (Node.js or Python),
• whether you prefer unpublish or mute behavior,
• and whether you want automatic re-enable on leave,
I’ll provide a ready-to-use code snippet tailored to your setup.
Sources: Agent session | LiveKit Documentation | client-sdk-flutter | livekit/livekit_composite | DeepWikilittle-king-41579
02/18/2026, 3:58 PMtall-airline-16756
02/18/2026, 3:58 PMweb_participant2 joins, plus options for mute vs unpublish and how to automatically re-enable audio when they leave.
Summary of the approach
• Add handlers on the agent’s room for participant-connected and participant-disconnected events.
• When a participant with identity web_participant2 connects, either unpublish the agent’s audio track or mute/stop it.
• When that participant disconnects, re-publish or unmute the audio if you want audio to resume automatically.
Python example (agent runtime, conceptual)
• This uses the room event handlers and the agent’s local participant to control its audio track.
# Example: agent entrypoint using LiveKit Python SDK / agent runtime
@server.rtc_session()
async def agent_entrypoint(ctx: JobContext):
# Helper: find local audio publication or track
def get_local_audio_publication():
for pub in ctx.room.local_participant.publications:
if pub.kind == "audio":
return pub
return None
async def stop_audio_by_unpublish():
pub = get_local_audio_publication()
if pub and pub.track:
# Unpublish the audio track so others no longer receive a publication
await ctx.room.local_participant.unpublish_track(pub.track)
<http://ctx.logger.info|ctx.logger.info>("Agent audio unpublished (web_participant2 joined)")
async def mute_local_audio():
pub = get_local_audio_publication()
if pub and pub.track:
# Stop or mute the track depending on your track API
# If using track.stop(): stops capture; if mute API available, use that.
try:
await pub.track.stop()
except Exception:
# Fallback: set enabled/disabled if supported
if hasattr(pub.track, "enabled"):
pub.track.enabled = False
<http://ctx.logger.info|ctx.logger.info>("Agent audio muted/stopped (web_participant2 joined)")
async def restore_audio_by_publish():
# Re-publish logic depends on how you originally created the track
# Example: recreate an audio track and publish it
audio_track = await create_local_audio_track_somehow()
await ctx.room.local_participant.publish_track(audio_track)
<http://ctx.logger.info|ctx.logger.info>("Agent audio re-published (web_participant2 left)")
async def unmute_local_audio():
pub = get_local_audio_publication()
if pub and pub.track:
try:
# If track.stop() was used earlier, you might need to recreate the track.
if hasattr(pub.track, "enabled"):
pub.track.enabled = True
except Exception:
pass
<http://ctx.logger.info|ctx.logger.info>("Agent audio unmuted (web_participant2 left)")
@ctx.room.on("participant_connected")
async def on_participant_connected(participant: RemoteParticipant):
if participant.identity == "web_participant2":
# Choose one of these actions depending on desired behavior:
# 1) Unpublish (removes the publication)
await stop_audio_by_unpublish()
# OR 2) Mute/stop the track but keep publication
# await mute_local_audio()
@ctx.room.on("participant_disconnected")
async def on_participant_disconnected(participant: RemoteParticipant):
if participant.identity == "web_participant2":
# If you want audio to come back when they leave, restore or unmute:
# await restore_audio_by_publish()
# OR
# await unmute_local_audio()
pass
# Connect the agent to the room (example flags; adapt to your setup)
await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_VIDEO)
# Keep your agent work running...
Notes and implementation details
• Unpublish vs mute:
◦ Unpublish removes the track publication for others to see. Use when you want the publication gone.
◦ Muting/stopping keeps the publication but stops sending audio data. Use when you want to resume quickly and keep signal that an audio track exists.
• Re-creating audio tracks:
◦ If you use track.stop(), many SDKs require creating a new local track to re-publish. Muting via track.enabled = False (if supported) lets you toggle without re-creating.
• Finding the audio publication:
◦ The snippet uses local_participant.publications to locate the audio publication. If your SDK exposes a helper like get_track_publication(Track.Kind.Audio), you can use that.
• Threading / lifecycle:
◦ Ensure the handlers are registered before connecting or immediately after connecting so you won’t miss the join event.
• Identities:
◦ Match exact participant.identity string web_participant2. If identities vary, use whatever identifier your app sets.
If you’d like, I can:
• Provide a version that explicitly recreates and re-publishes the audio track after stop,
• Show code that mutes/unmutes using a specific track API if you share how you create the local audio track,
• Or adapt this to a concrete AgentSession or background task pattern you’re using.
Which behavior do you prefer — Unpublish or Mute/Stop? Also, how are you creating the agent’s local audio track now (so I can give the exact publish/unpublish code)?little-king-41579
02/18/2026, 4:24 PMtall-airline-16756
02/18/2026, 4:25 PMpublications, but the LiveKit Python SDK exposes the agent’s published tracks via published_tracks (a dict of track SID → LocalTrackPublication), not publications.
Below is a corrected, minimal Python example that:
• Listens for participants joining,
• Unpublishes the agent’s audio when web_participant2 joins,
• Optionally re-publishes (or unmutes) when they leave.
Adjust the reconnect/publish logic to match how you create your local audio track.
import asyncio
from livekit import rtc # adjust import to your setup
from livekit.rtc import TrackKind
@server.rtc_session()
async def agent_entrypoint(ctx: JobContext):
async def unpublish_audio():
# published_tracks is a dict of sid -> LocalTrackPublication
for pub in ctx.room.local_participant.published_tracks.values():
# publication.track may be None if already unpublished
if pub.track and pub.track.kind == TrackKind.KIND_AUDIO:
await ctx.room.local_participant.unpublish_track(pub.track)
<http://ctx.logger.info|ctx.logger.info>("Agent audio unpublished (web_participant2 joined)")
return
async def republish_audio(create_audio_track_fn):
# create_audio_track_fn: your function to create a new LocalAudioTrack
audio_track = await create_audio_track_fn()
await ctx.room.local_participant.publish_track(audio_track)
<http://ctx.logger.info|ctx.logger.info>("Agent audio re-published (web_participant2 left)")
@ctx.room.on("participant_connected")
async def on_participant_connected(participant: rtc.RemoteParticipant):
if participant.identity == "web_participant2":
# Choose action: unpublish or mute. Example: unpublish
await unpublish_audio()
# If you prefer mute instead of unpublish, implement muting here
# (muting may require toggling a property on the track or stopping capture)
@ctx.room.on("participant_disconnected")
async def on_participant_disconnected(participant: rtc.RemoteParticipant):
if participant.identity == "web_participant2":
# If you want audio to resume automatically, republish or unmute here.
# Example placeholder: await republish_audio(create_local_audio_track)
pass
await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_VIDEO)
# keep agent running...
Important notes:
• Use ctx.room.local_participant.published_tracks (not .publications).
• unpublish_track() expects a LocalTrack object (use pub.track from published_tracks).
• If you call track.stop() to mute, you may need to recreate the track to republish later. Muting by toggling track.enabled (if available) avoids re-creation.
• Ensure handlers are registered before or immediately after connect() so you don't miss the join event.
If you share:
• the LiveKit Python SDK version you’re using, and
• how you currently create the agent’s local audio track (e.g., a helper function name),
I’ll give a ready-to-paste snippet that includes the exact republish workflow or a mute/unmute implementation. Which behavior do you prefer — Unpublish or Mute?
Sources: client-sdk-unity | livekit/livekit_composite | DeepWiki | Building an Automated IVR Menu Caller | LiveKit Documentation | server-sdk-js | livekit/livekit_composite | DeepWikilittle-king-41579
02/18/2026, 4:29 PMtall-airline-16756
02/18/2026, 4:30 PMLocalParticipant in your SDK version. Let’s fix that and give you a working pattern.
What to use
• The correct property on LocalParticipant to inspect published tracks is track_publications (a mapping of track SID → TrackPublication).
• Each publication may have a .track (the LocalTrack) and a .kind (audio/video). Use those to find the audio publication and call unpublish_track() with the LocalTrack object.
Minimal working pattern
1. Register the participant-connected handler.
2. When participant.identity == "web_participant2", iterate ctx.room.local_participant.track_publications.values() to find the audio publication.
3. Call await ctx.room.local_participant.unpublish_track(pub.track).
Example (adapt to your imports / agent runtime):
@server.rtc_session()
async def agent_entrypoint(ctx: JobContext):
async def unpublish_audio():
for pub in ctx.room.local_participant.track_publications.values():
# pub.track can be None if already unpublished
if pub.track and pub.kind == rtc.TrackKind.KIND_AUDIO:
await ctx.room.local_participant.unpublish_track(pub.track)
<http://ctx.logger.info|ctx.logger.info>("Agent audio unpublished (web_participant2 joined)")
return
@ctx.room.on("participant_connected")
async def on_participant_connected(participant: rtc.RemoteParticipant):
if participant.identity == "web_participant2":
await unpublish_audio()
@ctx.room.on("participant_disconnected")
async def on_participant_disconnected(participant: rtc.RemoteParticipant):
if participant.identity == "web_participant2":
# If you want audio to resume automatically, implement republish/unmute here.
pass
await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_VIDEO)
# keep agent running...
Notes and choices
• Unpublish vs mute/stop:
◦ Unpublish removes the publication; other participants no longer see it.
◦ Stopping/muting the track (e.g., track.stop() or toggling track.enabled) keeps the publication but stops audio data. If you call track.stop(), you may need to recreate the LocalTrack to republish later.
• If you prefer mute/unmute instead of unpublish, tell me how you create the audio track (function name or code) and I’ll show the exact mute/unmute flow.
• If you still get AttributeError, please share:
◦ the LiveKit Python SDK version you’re using, and
◦ the exact code you use to create/publish the local audio track.
If you want, I can prepare a copy-pasteable snippet that also handles automatic re-publishing when web_participant2 leaves — tell me whether you want unpublish or mute behavior and the SDK version.
Sources: livekit/livekit_composite | DeepWikilittle-king-41579
02/18/2026, 4:44 PMtall-airline-16756
02/18/2026, 4:44 PMunpublish_track(). In this LiveKit Python SDK, unpublish_track() expects either a track SID string or the LocalTrackPublication object — not the LocalTrack instance (i.e., not pub.track).
What to change
• Find the audio publication via ctx.room.local_participant.track_publications.
• Call unpublish_track(pub) or unpublish_track(pub.track_sid) — do NOT pass pub.track.
Working example to replace your current unpublish logic:
async def unpublish_audio(ctx):
lp = ctx.room.local_participant
for pub in lp.track_publications.values():
# pub.track can be None if already unpublished
if pub.kind == rtc.TrackKind.KIND_AUDIO:
# Correct: pass the publication or the SID, not pub.track
await lp.unpublish_track(pub) # OR: await lp.unpublish_track(pub.track_sid)
<http://ctx.logger.info|ctx.logger.info>(f"Unpublished audio track: {pub.track_sid}")
return
<http://ctx.logger.info|ctx.logger.info>("No audio track found to unpublish")
How to wire it into the join handler:
@ctx.room.on("participant_connected")
async def on_participant_connected(participant: rtc.RemoteParticipant):
if participant.identity == "web_participant2":
await unpublish_audio(ctx)
Additional notes
• If you previously used track.stop() to mute, you may need to recreate the local track to re-publish later. Unpublishing removes the publication; muting keeps the publication but stops audio data.
• If you want automatic resume when web_participant2 leaves, add a participant_disconnected handler that re-publishes (or unmutes) using your track-creation routine.
• If you still get errors, please share:
◦ The LiveKit Python SDK version (pip show livekit), and
◦ The code you use to create/publish the local audio track.
I can then provide a copy‑paste snippet that includes safe re-publish logic or a mute/unmute flow tailored to your SDK version. Which behavior do you prefer — unpublish or mute?
Sources: client-sdk-android | livekit/livekit_composite | DeepWiki | client-sdk-unity | livekit/livekit_composite | DeepWiki | An open source stack for real-time multimodal AIlittle-king-41579
02/18/2026, 4:46 PMtall-airline-16756
02/18/2026, 4:46 PMunpublish_track() with the publication or the publication's SID (not the LocalTrack object).
Below is a concise, ready-to-use example tailored to your SDK versions that:
• Listens for web_participant2 joining,
• Unpublishes the agent's audio by passing the LocalTrackPublication (or its track_sid),
• Optionally re-publishes when they leave (two approaches shown: unpublish/recreate or mute/unmute).
Paste this into your agent entrypoint (adapt imports and your audio-track creation function):
import asyncio
from livekit import rtc
from livekit.rtc import TrackKind, AutoSubscribe
# Example: replace with how you create a LocalAudioTrack in your app
async def create_local_audio_track():
# Example placeholder — replace with your actual creation code
# e.g., return await rtc.create_local_audio_track(...) or your helper
raise NotImplementedError("Provide your audio track creation code here")
@server.rtc_session()
async def agent_entrypoint(ctx: JobContext):
async def unpublish_audio():
lp = ctx.room.local_participant
for pub in lp.track_publications.values():
# pub.track can be None if already unpublished
if pub.kind == TrackKind.KIND_AUDIO:
# Correct: pass the publication or the SID (not pub.track)
await lp.unpublish_track(pub) # OR: await lp.unpublish_track(pub.track_sid)
<http://ctx.logger.info|ctx.logger.info>(f"Unpublished audio track: {pub.track_sid}")
return
<http://ctx.logger.info|ctx.logger.info>("No audio track found to unpublish")
async def republish_audio():
lp = ctx.room.local_participant
# Recreate and publish a new local audio track (depends on your creation function)
audio_track = await create_local_audio_track()
await lp.publish_track(audio_track)
<http://ctx.logger.info|ctx.logger.info>("Re-published agent audio")
# Alternative: mute/unmute approach (keeps publication visible)
async def mute_audio_keep_publication():
lp = ctx.room.local_participant
for pub in lp.track_publications.values():
if pub.kind == TrackKind.KIND_AUDIO and pub.track:
# If the track has an 'enabled' flag, toggle it; otherwise stop() and recreate later
if hasattr(pub.track, "enabled"):
pub.track.enabled = False
<http://ctx.logger.info|ctx.logger.info>("Muted audio (kept publication)")
else:
await pub.track.stop()
<http://ctx.logger.info|ctx.logger.info>("Stopped audio track (must recreate to republish)")
return
async def unmute_audio_keep_publication():
lp = ctx.room.local_participant
for pub in lp.track_publications.values():
if pub.kind == TrackKind.KIND_AUDIO and pub.track:
if hasattr(pub.track, "enabled"):
pub.track.enabled = True
<http://ctx.logger.info|ctx.logger.info>("Unmuted audio")
else:
<http://ctx.logger.info|ctx.logger.info>("Track was stopped; recreate to republish")
return
@ctx.room.on("participant_connected")
async def on_participant_connected(participant: rtc.RemoteParticipant):
if participant.identity == "web_participant2":
# Choose one action:
await unpublish_audio() # removes publication
# OR: await mute_audio_keep_publication() # keep publication but stop audio
@ctx.room.on("participant_disconnected")
async def on_participant_disconnected(participant: rtc.RemoteParticipant):
if participant.identity == "web_participant2":
# If you used unpublish and want to resume:
# await republish_audio()
# If you used mute:
# await unmute_audio_keep_publication()
pass
await ctx.connect(auto_subscribe=AutoSubscribe.AUDIO_VIDEO)
# Keep the agent running (your existing loop/work)
Checklist to avoid the previous errors
• Use lp.track_publications to iterate publications.
• Pass a publication (pub) or pub.track_sid into unpublish_track() — do not pass pub.track.
• If you call track.stop(), expect to recreate the track to republish later.
• If you prefer quick toggle without recreating, use pub.track.enabled = False/True if that attribute exists for your LocalTrack.
Next steps
• Try the code above (replace create_local_audio_track() with your actual audio-track creation).
• Tell me which behavior you want: fully remove the publication (unpublish) or keep the publication but mute (mute/stop). I can then provide a copy-paste-ready create_local_audio_track() example for your environment or a full mute/unmute flow.