proud-lawyer-39815
05/08/2026, 4:22 PMtall-airline-16756
05/08/2026, 4:22 PMtall-airline-16756
05/08/2026, 4:22 PMis_agent_handoff() or contains_agent_handoff() to verify a handoff event.
• Example (Python):
result.expect.next_event().is_agent_handoff(new_agent_type=MyAgent)
This checks the next event is a handoff to the specified agent type and validates the transition and context preservation.
If you want, tell me which agents or a snippet of your test and I can suggest the exact assertion to use.
Sources: Test framework | LiveKit Documentation | Agents and handoffs | LiveKit Documentation, with interactive elementsproud-lawyer-39815
05/08/2026, 4:24 PMtall-airline-16756
05/08/2026, 4:24 PMself.session.update_agent(agent=my_next_agent) after hydrate_chat_ctx() — ensure that update_agent is actually awaited or executed before the session ends. If update happens after the session finishes producing events, no handoff event will be emitted.
2. Event consumption: The recorded events show:
◦ AgentHandoff (old_agent=None -> StartingAgent)
◦ ChatMessage (assistant: "Remain silent")
That means the handoff to my_next_agent never happened (or happened but after capture ended). Try using contains_agent_handoff() which searches remaining events instead of strict next-event ordering:
• Replace the failing assertion with result.expect.contains_agent_handoff(new_agent_type=my_next_agent.__class__).
1. Confirm update call and side effects:
◦ Verify self.session.update_agent(agent=my_next_agent) runs (add a log or a short sleep after it to ensure event loop scheduling).
◦ Ensure hydrate_chat_ctx() does not block or swallow the update call.
1. If your expected sequence must be strict (handoff → message → handoff), ensure the first is_agent_handoff(new_agent_type=starting_agent.__class__) is actually correct — the first handoff shows old_agent=None → StartingAgent, so that assertion is fine. For the final handoff, confirm my_next_agent.__class__ matches the agent instance passed.
If you want, share:
• the full StartingAgent.on_enter code (exact indentation/awaits),
• whether update_agent is async or sync,
and I'll suggest the precise assertion or minimal code change.
, with interactive elementsproud-lawyer-39815
05/08/2026, 4:39 PMtall-airline-16756
05/08/2026, 4:40 PMupdate_agent(...) is actually executed (and awaited if it’s async) inside on_enter. If update_agent is async, call await self.session.update_agent(...).
• Keep capture running until the handoff occurs. Use contains_agent_handoff(...) instead of strict next_event() when ordering/timing may vary:
◦ result.expect.contains_agent_handoff(new_agent_type=my_next_agent.__class__)
• If you need strict ordering, wait for the specific event with the test framework’s event assertions rather than sleeping. For example, poll result.events or use result.expect.next_event() repeatedly until you see the handoff (the framework’s assertions already provide that pattern).
• Add lightweight diagnostics (logging or a short await after update_agent) only to confirm the call executes during tests — not long sleeps.
If you share whether update_agent is async or paste the exact on_enter code, I’ll suggest the minimal change to ensure the handoff event is emitted.
, with interactive elementsproud-lawyer-39815
05/08/2026, 4:44 PMtall-airline-16756
05/08/2026, 4:44 PMupdate_agent returns but doesn't trigger a handoff.
Quick answers and resources:
• There isn’t a single GitHub test that directly asserts update_agent, but the test framework supports handoff assertions (is_agent_handoff() / contains_agent_handoff()) you can use.
• Example repos with agent-switching logic you can copy patterns from:
◦ Medical Office Triage: https://github.com/livekit-examples/python-agents-examples/tree/main/complex-agents/medical_office_triage
◦ Front-desk example: https://github.com/livekit/agents/blob/main/examples/frontdesk
◦ Drive-thru example: https://github.com/livekit/agents/blob/main/examples/drive-thru
Suggested next steps to test locally (no arbitrary sleep):
1. Replace strict ordering with a contains check:
◦ result.expect.contains_agent_handoff(new_agent_type=my_next_agent.__class__)
1. Verify update_agent is awaited if it’s async:
◦ If update_agent is async: await self.session.update_agent(agent=my_next_agent)
1. Add a short diagnostic await or log right after update to confirm it runs (not a long sleep).
2. If you want a small test snippet to assert handoff order, I can craft one matching your agents.
If you’d like, paste the exact on_enter method (including awaits) and whether update_agent is async — I’ll suggest the minimal code/test change.
Sources: Agents and handoffs | LiveKit Documentation | Test framework | LiveKit Documentation | The Handoff Pattern for Voice Agents That Replaces IVR Menus | LiveKit, with interactive elementsproud-lawyer-39815
05/08/2026, 4:48 PMtall-airline-16756
05/08/2026, 4:48 PMon_enter runs. That usually means the handoff call completed without keeping the session alive long enough for the new agent to run. Here are concise, actionable fixes and a small test pattern to try.
Quick checklist (try in this order):
• Ensure you await update_agent if it’s async:
◦ If update_agent is async: await self.session.update_agent(agent=my_next_agent)
• Keep the session alive until the new agent starts:
◦ Return control to the session only after the handoff is requested (i.e., don’t let on_enter finish immediately if the session should continue).
◦ If your framework offers a wait-for-handoff primitive, use it (e.g., await self.session.wait_for_handoff()), otherwise add a short await that yields control (not a long sleep) after update_agent to let the event loop process the handoff.
• Prefer tool-based handoffs if appropriate: tools that return a new Agent instance automatically trigger a handoff and avoid lifecycle races.
• Use contains_agent_handoff(...) in tests when exact ordering/timing is flaky.
Minimal test pattern (pseudo-Python) to assert handoff without sleeps:
result = await agent_session.start(agent=starting_agent, capture_run=True)
# ensure the session captured the initial message
result.expect.next_event().is_message(role="assistant")
# assert that a handoff to the next agent occurred somewhere in the events
result.expect.contains_agent_handoff(new_agent_type=my_next_agent.__class__)
# optionally assert the next agent produced a message
# result.expect.contains_event(lambda e: isinstance(e, ChatMessageEvent) and e.item['role']=='assistant' ...)
If you want, paste the full StartingAgent.on_enter (with awaits and update_agent call) and tell me whether update_agent is async; I’ll suggest the exact minimal code change to ensure the session remains active and the new agent’s on_enter runs.
, with interactive elementsproud-lawyer-39815
05/08/2026, 4:52 PMclass StartingAgent(Agent):
async def on_enter(self) -> None:
await self.session.say(
text="Hello"
) # it needs to generate/say something, otherwise the the session will hang
self.session.update_agent(agent=my_next_agent)
update_agent is sync.tall-airline-16756
05/08/2026, 4:53 PMon_enter and that update_agent is synchronous. The session ends before the new agent runs because on_enter returns immediately after calling the sync update_agent, so the event loop may not schedule the new agent before capture stops.
Try this minimal change to ensure the handoff runs before on_enter finishes:
class StartingAgent(Agent):
async def on_enter(self) -> None:
await self.session.say(text="Hello")
self.session.update_agent(agent=my_next_agent)
# yield control so the session can schedule the new agent's on_enter
await asyncio.sleep(0) # short, non-blocking
Why this helps:
• await asyncio.sleep(0) yields to the event loop so the session can process the handoff and invoke the new agent’s on_enter before the starting agent returns.
If your framework provides a wait-for-handoff primitive (e.g., await self.session.wait_for_handoff()), prefer that instead of sleep(0).
After this change, keep your test assertions as:
• result.expect.next_event().is_message(role="assistant") (for "Hello")
• result.expect.contains_agent_handoff(new_agent_type=my_next_agent.__class__)
• Optionally assert the new agent’s message with contains or further next_event() checks.
Would you like me to craft a short test snippet that includes these assertions?
, with interactive elementsproud-lawyer-39815
05/08/2026, 4:54 PMtall-airline-16756
05/08/2026, 4:55 PMon_enter runs even though update_agent is sync. That means the handoff can occur but the new agent’s lifecycle runs asynchronously outside the current run, so you won't reliably see the new agent's events in the same captured run.
What to try next (minimal, ordered):
1. Verify the handoff happened (in the same run)
◦ Keep your existing flow but assert the handoff occurred:
▪︎ result.expect.contains_agent_handoff(new_agent_type=my_next_agent.__class__)
1. Test the new agent separately (recommended)
◦ After confirming the handoff event, start a new run or session to exercise my_next_agent.on_enter() and assert its messages/events. Example pattern:
i. Run that triggers handoff → assert contains_agent_handoff(...)
ii. Start/run with my_next_agent directly → assert its on_enter behavior
1. If you need end-to-end in one run (race avoidance)
◦ Use a handshake so the session stays alive until the new agent finishes:
▪︎ Make the starting agent await a confirmation after calling update_agent, e.g. set a session flag or await an async primitive the framework provides (preferable).
▪︎ If no primitive exists, yield to the loop after update and wait for a short condition rather than a blind sleep:
self.session.update_agent(agent=my_next_agent)
await asyncio.wait_for(self.session.wait_until_agent_is(my_next_agent.__class__), timeout=1)
(If wait_until_agent_is doesn't exist, implement a small session flag the new agent sets in on_enter that the starter polls with asyncio.wait_for.)
1. Debugging logs (if still failing)
◦ Log type(session.current_agent) before/after update_agent.
◦ Print result.events to verify what the test framework captured.
If you want, paste:
• the test code that starts/runs the session, and
• whether you can add a small session flag or primitive the new agent can set in on_enter,
and I’ll write the exact minimal test snippet or a handshake implementation you can drop into your code.
Sources: Agents and handoffs | LiveKit Documentation | Testing and evaluation | LiveKit Documentation, with interactive elements