Skip to content

Enable unified streaming of all agents in a run (agent-as-tools, handoffs) recursively with streamAgentTools param #705

@rainbow-f1zz

Description

@rainbow-f1zz

Please read this first

Feature request

** Add a way to stream events from all agents involved in a run (main agent, agents used as tools, and agents reached via handoffs), through a single stream returned from Runner.run. This would be controlled by a new streamAgentTools?: boolean flag on StreamRunOptions when stream: true.

Concretely, when calling:

const runner = new Runner({ model: 'gpt-4.1-mini' });

const result = await runner.run(agentA, 'Solve task by using agentB as a tool', {
  stream: true,
  streamAgentTools: true,
});

and agentA uses other agents as tools (e.g. agentB.asTool(), possibly nested and/or via handoffs), then:

  • All RunStreamEvents from agent A and any downstream agents (used as tools or reached via handoffs, recursively) are surfaced in the top-level StreamedRunResult.
  • Each raw_model_stream_event is annotated with the originating agent name (e.g. event.agentName) so UIs can distinguish which agent produced which text or event.
  • Internally, the runner maintains a context-scoped stream attached to RunContext:
    • When streamAgentTools: true is set for the top-level run, a “context scope stream” is created on the context and all subsequent streamed results in that context copy their events into it.
    • When an agent is invoked via agent.asTool(...) within an active context-scope stream, that tool run automatically executes in streaming mode (stream: true) but with streamAgentTools: false to avoid recursive re-wiring; its events are still mirrored into the shared context-scope stream.

Motivation

Today, when building an app where:

  • A “main” agent orchestrates the conversation, and
  • It uses multiple specialized agents as tools (and possibly handoffs to other agents),

the top-level stream only includes the main agent’s events. End users see that the main agent has called a tool (e.g. via tool_called / tool_output in run item events) but get no live updates from the tool agent itself. If the tool agent is slow or multi-step, this makes the UI feel stuck or unresponsive.

There are workarounds (e.g. writing custom updates in hooks, or wiring up separate streams per tool agent), but they require significant additional code and don’t compose nicely with nested agent-as-tools and handoffs. A simple, opt-in toggle like streamAgentTools: true makes it much easier to:

  • Build rich, multi-agent UIs where users can see progress from all participating agents in real time.
  • Support deep nesting (agent → agent-as-tool → handoff → further agent-as-tool, etc.) without writing custom streaming plumbing.
  • Maintain a single, unified streaming contract (StreamedRunResult) that works with existing API surface and event types (raw_model_stream_event, run_item_stream_event, agent_updated_stream_event).

Example usage

const weatherAgent = new Agent({ name: 'Weather Agent', tools: [getWeatherTool] });

const newsAgent = new Agent({
  name: 'News Agent',
  instructions: 'You are a news agent that can tell the news for a given city.',
  tools: [getLocalNewsTool],
});

const personalAgent = new Agent({
  name: 'Personal Agent',
  instructions:
    'You are a personal agent that prepares a user for the day. ' +
    'You can use the news agent to get the news for the day, and the weather agent to get the weather for the day.',
  tools: [
    newsAgent.asTool({ toolName: 'news_agent', toolDescription: 'Get the local news for today' }),
    weatherAgent.asTool({ toolName: 'weather_agent', toolDescription: 'Get the weather for today' }),
  ],
});

const runner = new Runner({
  model: 'gpt-4.1-mini',
  tracingDisabled: true,
});

const streamedRunResult = await runner.run(
  personalAgent,
  "What's up in Beijing today?",
  {
    stream: true,
    // Enable recursive streaming of all agent-as-tool and handoff events
    streamAgentTools: true,
  },
);

// Events now include contributions from `Personal Agent`, `News Agent`, and `Weather Agent`.
for await (const event of streamedRunResult) {
  // UI can render progress for all agents, using event.agentName where applicable.
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions