Realtime: agent_end names the post-handoff agent instead of the agent whose turn ended

Author: kosesenaCreated Sep 17, 2026Updated Sep 17, 2026

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 58a6d2c9 (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.

python
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.

Source: openai/openai-agents-python