#7276·agents

AvatarSession.aclose() doesn't unbind the audio output start() installed, so a failed avatar start silently mutes the agent

Author: joearasinCreated Sep 14, 2026Updated Sep 15, 2026
Labelsbug

Bug Description

AvatarSession.start() points the agent session's audio output at the avatar's datastream. AvatarSession.aclose() never puts it back. That asymmetry is invisible on the happy path, but if the avatar fails to start, the agent is then silent (and doesn't degrade to just audio).

Expected Behavior

AvatarSession.aclose() sets audio output back to what it was before.

Reproduction Steps

bash
import asyncio
from livekit import rtc
from livekit.agents.voice import AgentSession
from livekit.agents.voice.avatar import AvatarSession, DataStreamAudioOutput


class FakeAvatarSession(AvatarSession):
    """Mimics what an avatar plugin's start() does."""

    @property
    def avatar_identity(self) -> str:
        return "avatar-worker"

    async def start(self, agent_session, room):
        await super().start(agent_session, room)
        agent_session.output.replace_audio_tail(
            DataStreamAudioOutput(room=room, destination_identity=self.avatar_identity)
        )


async def main():
    session = AgentSession()
    room = rtc.Room()
    print("before start:       ", session.output.audio)

    avatar = FakeAvatarSession()
    await avatar.start(session, room=room)
    print("after plugin start: ", type(session.output.audio).__name__)

    # the avatar never joins: wait_for_join(timeout=...) raises, the app gives up
    await avatar.aclose()
    print("after aclose:       ", type(session.output.audio).__name__)


asyncio.run(main())


Output:


before start:        None
after plugin start:  DataStreamAudioOutput
after aclose:        DataStreamAudioOutput

Operating System

All

Models Used

No response

Package Versions

bash
livekit-agents==1.8.0

Session/Room/Call IDs

No response

Proposed Solution

Have AvatarSession remember what it replaced in start() and restore it in aclose() — symmetric with how it already unregisters its own event handlers and cancels its own join task. (Or provide a parameter to close to do this)

Additional Context

No response

Screenshots and Recordings

No response