plain-agent-69288
03/18/2026, 11:15 PMtall-airline-16756
03/18/2026, 11:15 PMtall-airline-16756
03/18/2026, 11:16 PMAgent from a tool should add an AgentHandoff to the chat context and trigger the new agent’s on_enter() after the handoff.
• The system supports parallel_tool_calls so an LLM can invoke multiple tools in one generation.
• There is no documented behavior for the mixed case where one parallel tool triggers a handoff while another produces a reply in the same generation.
To narrow this down and give an actionable suggestion, I need a little more info. Could you share the following?
1. The minimal steps to reproduce (a short sequence you can run locally or a short transcript).
2. The agent and tool definitions (the code for the two tools involved — especially the tool that returns the Agent and the one that returns the reply). Redact any secrets.
3. The LLM and LiveKit agents/framework versions you’re using.
4. The value of parallel_tool_calls and any related runtime configuration.
5. Any logs around the time of the event (tool return values, the created AgentHandoff entry if visible, and whether an error or exception appears in logs).
While you gather that, here are two practical mitigation steps you can try immediately:
• Disable parallel tool calls for the affected flow (set parallel_tool_calls to false) so tools run sequentially. This ensures the handoff tool runs alone and the handoff completes before any subsequent reply is handled.
• Alternatively, ensure the tool that returns the Agent does not set reply_required=True or returns the Agent as the only output in that generation; then have the reply-producing tool run in a separate generation after the new agent is active.
If you prefer, once you paste the minimal repro or the two tool snippets I can:
• Review them for any issues that could prevent on_enter() from being invoked.
• Suggest a small code change to sequence the handoff and reply cleanly (for example, explicitly pushing the AgentHandoff and deferring replies until after the handoff).
Thanks — once you share the repro details I’ll walk through it with you and suggest the next fix.
Sources: Agents and handoffs | LiveKit Documentation | Function tool definition | LiveKit Documentation | Anthropic Claude LLM plugin guide | LiveKit Documentation | xAI LLM plugin guide | LiveKit Documentation | DeepSeek LLM | LiveKit Documentation, with interactive elementsplain-agent-69288
03/18/2026, 11:45 PMon_enter() is never called on the new agent, and the conversation gets stuck in a loop.
This is the same class of bug as #4691, but on the pipeline path (STT → LLM → TTS) rather than the Gemini Realtime path.
### Observed Trace Data
Cycle 1 — agent_turn (162024.163 → 162025.031)
| Span | Time | Key attributes |
|------|------|---------------|
| llm_node | 24.164–24.827 | model=gemini-2.5-flash, output_tokens=33, response.text="", response.function_calls=2 calls: validate_and_save_field(isPlanTerminated=false) + switch_assistant(get-info-assistant-product-coverage) |
| function_tool | 24.820–25.031 | name=validate_and_save_field, output="Field isPlanTerminated saved successfully with value False", is_error=false |
| function_tool | 24.822–24.824 | name=switch_assistant, output="", is_error=false |
| tts_node | 24.184–24.834 | input_text="" (nothing to speak) |
Cycle 2 — follow-up agent_turn (162025.033 → 162025.470)
| Span | Time | Key attributes |
|------|------|---------------|
| llm_node | 25.033–25.446 | model=gemini-2.5-flash, output_tokens=0, response.text="", response.function_calls=[] |
| tts_node | 25.055–25.470 | input_text="" (nothing to speak) |
→ Agent is silent. on_enter() never fires for the new agent. User speaks again → Cycle 1 repeats identically.
Full trace spans available on request.
Did a brief root cause analysis, it might be related to how parallel tool calls are handled in the agent_activity
### Root Cause
In agent_activity.py around line 2232-2269, after parallel tool execution:
1. The SDK detects agent_task from the handoff tool and calls self._session.update_agent(new_agent_task) (line 2238-2239) — this creates an async _update_activity_task
2. But fnc_executed_ev._reply_required is True (set by the other tool's string return value), so a _pipeline_reply_task is also created (line 2243-2269) that sends tool results back to the LLM on the old agent
3. The follow-up LLM call completes before the handoff task can drain the old agent — the LLM returns an empty response (0 output tokens) since the old agent has nothing meaningful to say
4. The old agent goes back to "listening". When the user speaks again, the old agent starts a new turn, making the same tool calls (handoff + save), creating the same race — an infinite loop
5. on_enter() is never called on the new agent
The key issue is at line 2243: when _handoff_required is True, the follow-up _pipeline_reply_task should not be created, since the old agent is about to be replaced.
python
# agent_activity.py lines 2237-2269 (simplified)
draining = self.scheduling_paused
if fnc_executed_ev._handoff_required and new_agent_task and not ignore_task_switch:
self._session.update_agent(new_agent_task) # async task
draining = True
tool_messages = new_calls + new_fnc_outputs
if fnc_executed_ev._reply_required: # ← True from the non-handoff tool's string result
# This creates a follow-up LLM call on the OLD agent,
# which races with and prevents the handoff from completing
chat_ctx.items.extend(tool_messages)
tool_response_task = self._create_speech_task(
self._pipeline_reply_task(
speech_handle=speech_handle,
chat_ctx=chat_ctx,
tools=tools,
model_settings=ModelSettings(
tool_choice="none" if draining or ... else "auto",
),
...
),
...
)
### Expected Behavior
When a tool returns an Agent (handoff), the handoff should complete regardless of what other parallel tools returned. on_exit() should be called on the old agent and on_enter() should be called on the new agent.
### Reproduction Steps
bash
The bug triggers when the LLM returns parallel tool calls where one returns an Agent and another returns a string. This happens naturally in production when models batch related operations (e.g., "save this field AND switch to the next assistant"). It can be hard to reproduce deterministically since it depends on LLM behavior.
from livekit.agents import Agent, RunContext, function_tool, AgentSession
class PrimaryAgent(Agent):
def __init__(self):
super().__init__(
instructions=(
"You are a helpful assistant. "
"When the user says 'switch', you MUST call BOTH save_data and "
"switch_to_secondary at the same time in the same response."
),
)
async def on_enter(self):
print("PrimaryAgent on_enter called")
await self.session.generate_reply()
@function_tool()
async def save_data(self, context: RunContext, value: str) -> str:
"""Save a piece of data. Always call this before switching agents."""
print(f"save_data called with: {value}")
return f"Saved: {value}" # <-- returns string, sets reply_required=True
@function_tool()
async def switch_to_secondary(self, context: RunContext) -> Agent:
"""Switch to the secondary agent to continue the conversation."""
print("switch_to_secondary called")
return SecondaryAgent() # <-- returns Agent, triggers handoff
class SecondaryAgent(Agent):
def __init__(self):
super().__init__(instructions="You are a secondary assistant. Say hello.")
async def on_enter(self):
# THIS NEVER FIRES when save_data runs in the same generation
print("SecondaryAgent on_enter called")
await self.session.generate_reply()
To reproduce:
* Start the agent with uv run python main.py console
* Say "switch" — the LLM should call both save_data and switch_to_secondary in the same generation
* Observe that SecondaryAgent on_enter called never prints
* The agent goes silent. Subsequent messages keep triggering the same two tool calls in a loop
**Key condition:** The bug only triggers when the LLM batches the handoff tool with another tool that returns a non-None value in the **same generation**. If switch_to_secondary is the only tool called (or all other tools return None), the handoff works correctly.
### Operating System
Linux
### Models Used
Deepgram Nova-3, Gemini 2.5 Flash (via vertex), Elevenlabs TTS
### Package Versions
bash
livekit-agent: 1.4.3tall-airline-16756
03/18/2026, 11:45 PM