Skip to content

send_dtmf_events publishes DTMF to the job's room, not the executing session's room (wrong leg inside WarmTransferTask) #6357

Description

@jonahlipsky

Environment: livekit-agents 1.6.4 (Python)

Description

livekit.agents.beta.tools.send_dtmf_events resolves its publish target via the job context:

job_ctx = get_job_context()
await job_ctx.room.local_participant.publish_dtmf(code=code, digit=event.value)

The job's room is always the original caller room. But the natural use of this tool with WarmTransferTaskWarmTransferTask(tools=[send_dtmf_events], ...) so the consult agent can navigate an IVR/phone tree on the human-agent leg — executes the tool on the consult session, which runs in a separate {caller_room}-human-agent room.

Result on a live call: the DTMF was relayed out through the caller room's SIP participant, so the caller on hold heard carrier-regenerated beeps over the hold music, and the IVR on the human-agent leg never received the digits (the consult agent then fell back to speaking at the menu, which does nothing).

Steps to reproduce

  1. Agent on a SIP call with a caller; trigger a WarmTransferTask whose sip_call_to answers with a DTMF menu ("press 1 for sales…").
  2. Pass tools=[send_dtmf_events] (plus instructions to use it) to the task.
  3. The consult agent calls the tool. Observe: digits arrive on the caller leg; the IVR never advances.

Expected behavior

DTMF should be relayed to the SIP participant(s) in the room of the session executing the tool.

Suggested fix

Resolve the room from the RunContext instead of the job context:

room = context.session.room_io.room
await room.local_participant.publish_dtmf(code=code, digit=event.value)

This is behavior-preserving for the primary ivr_detection=True case (session room == job room) and correct inside WarmTransferTask. We're running exactly this as a local replacement tool and confirmed on live calls that the menu advances.

Related observation

WarmTransferTask._dial_human_agent builds the consult AgentSession copying only vad/llm/stt/tts/turn_detection, so session-level ivr_detection (and its injected tools) never reaches the leg that actually encounters phone trees — passing tools=[send_dtmf_events] explicitly is currently the only public way to get DTMF there, which is how this surfaces. A ? in the docs or an ivr_detection-style option on the task might be worth considering.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions