Please read this first
- Have you read the docs? Yes.
- Have you searched for related issues? Yes. No open or closed issue covers which agent
agent_end names after an in-response handoff.
Describe the bug
When a Realtime response contains a handoff tool call, the handoff completes before the model's turn_ended event and moves _current_agent to the target agent. The turn_ended branch in RealtimeSession then emits RealtimeAgentEndEvent(agent=self._current_agent), so the caller sees:
agent_start(A) → handoff(A → B) → agent_end(B) → agent_start(B) → agent_end(B)
RealtimeAgentEndEvent.agent is documented as "The agent that ended", but A never ends and B ends without having started. Any consumer that pairs agent_start / agent_end per agent (turn markers in a UI, per-agent timing, per-agent usage attribution read on agent_end) attributes A's turn to B.
The session already knows which agent produced the turn: _active_output_response_agent is captured at turn_started and is what the output guardrails use for the same reason. It is cleared one line before the agent_end event is built.
Debug information
- Agents SDK version:
main at 58a6d2c (also present in v0.22.2)
- Python version: 3.12.13
- Operating system: macOS 15
- Model and model provider: any Realtime model; reproduced with
ScriptedRealtimeModel from agents.realtime.testing
- Does the issue reproduce with the latest Agents SDK release? Yes
- Does the issue occur consistently or intermittently? Consistently, whenever a handoff runs inside a response
Repro steps
Self-contained, no network.
import asyncio
from agents.realtime.agent import RealtimeAgent
from agents.realtime.events import RealtimeAgentEndEvent, RealtimeAgentStartEvent, RealtimeHandoffEvent
from agents.realtime.model_events import (
RealtimeModelToolCallEvent,
RealtimeModelTurnEndedEvent,
RealtimeModelTurnStartedEvent,
)
from agents.realtime.session import RealtimeSession
from agents.realtime.testing import ScriptedRealtimeModel
async def main():
agent_b = RealtimeAgent(name="b")
agent_a = RealtimeAgent(name="a", handoffs=[agent_b])
model = ScriptedRealtimeModel(strict=False)
session = RealtimeSession(model, agent_a, None, run_config={"async_tool_calls": False})
await session.enter()
await model.emit(RealtimeModelTurnStartedEvent(response_id="response_1"))
await model.emit(RealtimeModelToolCallEvent(name="transfer_to_b", call_id="call_1", arguments="{}"))
await model.emit(RealtimeModelTurnEndedEvent(response_id="response_1"))
while not session._event_queue.empty():
event = session._event_queue.get_nowait()
if isinstance(event, RealtimeAgentStartEvent):
print("agent_start", event.agent.name)
elif isinstance(event, RealtimeHandoffEvent):
print("handoff", event.from_agent.name, "->", event.to_agent.name)
elif isinstance(event, RealtimeAgentEndEvent):
print("agent_end", event.agent.name)
await session.close()
asyncio.run(main())
Output on main:
agent_start a
handoff a -> b
agent_end b
Expected behavior
agent_start a
handoff a -> b
agent_end a
The turn that ended was produced by A; the handoff only decides who speaks next. I have a one-line fix with a regression test ready and will open a PR referencing this issue.
Please read this first
agent_endnames after an in-response handoff.Describe the bug
When a Realtime response contains a handoff tool call, the handoff completes before the model's
turn_endedevent and moves_current_agentto the target agent. Theturn_endedbranch inRealtimeSessionthen emitsRealtimeAgentEndEvent(agent=self._current_agent), so the caller sees:RealtimeAgentEndEvent.agentis documented as "The agent that ended", but A never ends and B ends without having started. Any consumer that pairsagent_start/agent_endper agent (turn markers in a UI, per-agent timing, per-agent usage attribution read onagent_end) attributes A's turn to B.The session already knows which agent produced the turn:
_active_output_response_agentis captured atturn_startedand is what the output guardrails use for the same reason. It is cleared one line before theagent_endevent is built.Debug information
mainat 58a6d2c (also present in v0.22.2)ScriptedRealtimeModelfromagents.realtime.testingRepro steps
Self-contained, no network.
Output on
main:Expected behavior
The turn that ended was produced by A; the handoff only decides who speaks next. I have a one-line fix with a regression test ready and will open a PR referencing this issue.