feat: automatic back-handoff to orchestrating agents#3584
Conversation
When an agent is reached via a handoff, it can now automatically hand back to the originating agent upon completion. Controlled by the auto_handoff_back parameter on the Handoff/handoff() config. This enables orchestration patterns where a main agent delegates to specialist agents and expects results back without needing explicit circular handoff configurations. Changes: - Add auto_handoff_back: bool = False to Handoff class - Pass through auto_handoff_back in handoff() factory function - Extend NextStepHandoff to carry auto_handoff_back and originating_agent - Track handoff chain in both streaming and non-streaming run loops - Auto-handoff back when child agent produces final output - Add comprehensive tests for the feature Closes openai#847 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 792258f496
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| input_type: type[THandoffInput] | None = None, | ||
| input_filter: Callable[[HandoffInputData], HandoffInputData] | None = None, | ||
| nest_handoff_history: bool | None = None, | ||
| auto_handoff_back: bool = False, |
There was a problem hiding this comment.
Preserve positional is_enabled binding
This inserts auto_handoff_back before the existing is_enabled positional slot in the runtime implementation, so existing callers that passed is_enabled positionally now bind that value to auto_handoff_back and leave is_enabled at its default True. For example, a previously disabled handoff using positional arguments is silently re-enabled, which breaks the public API positional-compatibility contract; the new optional parameter needs to be appended after is_enabled or handled with a compatibility shim.
Useful? React with 👍 / 👎.
| turn_session_items.append(back_msg) | ||
| streamed_result.new_items.append(back_msg) |
There was a problem hiding this comment.
Feed the back-handoff result to streamed parent turns
In the streaming path, this synthetic child-result message is appended only to new_items/session items, but the next streamed turn is built from streamed_result._model_input_items (set earlier to pre_step_items + new_step_items). When Runner.run_streamed() auto-hands back, the parent agent is invoked without the specialist's final output in its model input, so the advertised orchestration pattern fails for streaming runs.
Useful? React with 👍 / 👎.
| if next_step.auto_handoff_back and next_step.originating_agent is not None: | ||
| handoff_chain.append(next_step.originating_agent) |
There was a problem hiding this comment.
Persist the auto-handoff stack across resume
This stores the originating agent only in a local handoff_chain, but interruption results persist and resume from RunState, which does not include that local stack. If the child agent pauses for approval or another interruption after an auto handoff, resuming the run loses the parent agent, and the child's eventual final output is returned as the run result instead of handing control back.
Useful? React with 👍 / 👎.
Implements automatic back-handoff support as requested in #847.
When an agent is reached via a handoff, it can now automatically hand back to the originating agent upon completion by setting
auto_handoff_back=Trueon the handoff configuration.Problem
Currently, handoffs are one-way. Once an agent hands off control to another agent, it will never regain control without explicit circular handoff configurations. This makes orchestration patterns (where a main agent delegates to specialists and expects results back) tedious to implement.
Solution
This PR adds an
auto_handoff_back: bool = Falseparameter to theHandoffclass andhandoff()factory function. When enabled:Changes
src/agents/handoffs/__init__.py: Addauto_handoff_backfield toHandoffclass andhandoff()factorysrc/agents/run_internal/run_steps.py: ExtendNextStepHandoffto carry auto-handoff-back metadatasrc/agents/run_internal/turn_resolution.py: Pass auto-handoff-back info throughexecute_handoffs()src/agents/run.py: Track handoff chain and handle auto back-handoff in non-streaming pathsrc/agents/run_internal/run_loop.py: Track handoff chain and handle auto back-handoff in streaming pathtests/test_auto_handoff_back.py: Comprehensive tests for auto back-handoff behaviorUsage
Closes #847