.NET: feat: Implement return-to-previous routing in handoff workflow#4356
.NET: feat: Implement return-to-previous routing in handoff workflow#4356
Conversation
- Also obsoletes HandoffsWorkflowBuilder => HandoffWorkflowBuilder (no "s")
There was a problem hiding this comment.
Pull request overview
This PR implements return-to-previous routing in handoff workflows, allowing subsequent user turns to route directly back to the specialist agent that handled the previous turn, rather than always routing through the initial coordinator agent. The PR also deprecates HandoffsWorkflowBuilder in favor of HandoffWorkflowBuilder (without the 's').
Changes:
- Added
EnableReturnToPrevious()method to configure return-to-previous routing behavior - Introduced
HandoffsCurrentAgentTrackerto track the active agent across turns - Modified
HandoffStateto includeCurrentAgentIdfor routing decisions - Deprecated
HandoffsWorkflowBuilderand introducedHandoffWorkflowBuilderas the preferred name
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| HandoffsWorkflowBuilder.cs | Adds class hierarchy with obsolete HandoffsWorkflowBuilder and new HandoffWorkflowBuilder; implements EnableReturnToPrevious() method and dynamic routing logic |
| HandoffsCurrentAgentTracker.cs | New simple tracker class to maintain current agent ID across workflow turns |
| HandoffState.cs | Adds optional CurrentAgentId parameter to track active agent |
| HandoffsStartExecutor.cs | Updated to accept tracker and pass current agent ID to HandoffState |
| HandoffsEndExecutor.cs | Updated to accept tracker and update current agent ID at workflow completion |
| HandoffAgentExecutor.cs | Determines current agent ID based on handoff target and stores mapping |
| AgentWorkflowBuilderTests.cs | Comprehensive tests covering default behavior, enabled return-to-previous, and edge cases |
Comments suppressed due to low confidence (1)
dotnet/src/Microsoft.Agents.AI.Workflows/HandoffsWorkflowBuilder.cs:39
- The constructor parameter name in the XML documentation incorrectly refers to
HandoffsWorkflowBuilder(with 's'), but this is now in the base classHandoffWorkflowBuilderCore. The documentation should be updated to refer to the correct class name or remain generic.
/// <summary>
/// Initializes a new instance of the <see cref="HandoffsWorkflowBuilder"/> class with no handoff relationships.
/// </summary>
/// <param name="initialAgent">The first agent to be invoked (prior to any handoff).</param>
internal HandoffsWorkflowBuilder(AIAgent initialAgent)
| HandoffsCurrentAgentTracker? tracker = this._returnToPrevious ? new() : null; | ||
| HandoffsStartExecutor start = new(tracker); | ||
| HandoffsEndExecutor end = new(tracker); |
There was a problem hiding this comment.
The HandoffsCurrentAgentTracker is created once per workflow and will be shared if the same workflow instance is reused across multiple sessions. This means the last active agent from one session could become the starting agent for a different session. This is likely fine for the common use case (one workflow instance per conversation session), but could cause unexpected behavior if workflows are reused. Consider documenting that workflows built with EnableReturnToPrevious should not be reused across different conversation sessions, or making the tracker session-scoped.
| namespace Microsoft.Agents.AI.Workflows; | ||
|
|
||
| /// <inheritdoc/> | ||
| [Obsolete("Perfer HandoffWorkflowBuilder (no 's') instead, which has the same API but the preferred name. This will be removed if a future release before GA.")] |
There was a problem hiding this comment.
Spelling error: "Perfer" should be "Prefer" in the obsolescence message.
| [Obsolete("Perfer HandoffWorkflowBuilder (no 's') instead, which has the same API but the preferred name. This will be removed if a future release before GA.")] | |
| [Obsolete("Prefer HandoffWorkflowBuilder (no 's') instead, which has the same API but the preferred name. This will be removed in a future release before GA.")] |
| namespace Microsoft.Agents.AI.Workflows; | ||
|
|
||
| /// <inheritdoc/> | ||
| [Obsolete("Perfer HandoffWorkflowBuilder (no 's') instead, which has the same API but the preferred name. This will be removed if a future release before GA.")] |
There was a problem hiding this comment.
Grammatical error: "This will be removed if a future release" should be "This will be removed in a future release".
| [Obsolete("Perfer HandoffWorkflowBuilder (no 's') instead, which has the same API but the preferred name. This will be removed if a future release before GA.")] | |
| [Obsolete("Prefer HandoffWorkflowBuilder (no 's') instead, which has the same API but the preferred name. This will be removed in a future release before GA.")] |
| public HandoffsWorkflowBuilder WithHandoffInstructions(string? instructions) | ||
| { | ||
| this.HandoffInstructions = instructions ?? DefaultHandoffInstructions; | ||
| return this; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Sets the behavior for filtering <see cref="FunctionCallContent"/> and <see cref="ChatRole.Tool"/> contents from | ||
| /// <see cref="ChatMessage"/>s flowing through the handoff workflow. Defaults to <see cref="HandoffToolCallFilteringBehavior.HandoffOnly"/>. | ||
| /// </summary> | ||
| /// <param name="behavior">The filtering behavior to apply.</param> | ||
| public HandoffsWorkflowBuilder WithToolCallFilteringBehavior(HandoffToolCallFilteringBehavior behavior) | ||
| { | ||
| this._toolCallFilteringBehavior = behavior; | ||
| return this; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Configures the workflow so that subsequent user turns route directly back to the specialist agent | ||
| /// that handled the previous turn, rather than always routing through the initial (coordinator) agent. | ||
| /// </summary> | ||
| /// <returns>The updated <see cref="HandoffsWorkflowBuilder"/> instance.</returns> | ||
| public HandoffsWorkflowBuilder EnableReturnToPrevious() | ||
| { | ||
| this._returnToPrevious = true; | ||
| return this; | ||
| } |
There was a problem hiding this comment.
The return types of methods in HandoffWorkflowBuilderCore should be updated to support both derived classes properly. Currently, all methods return HandoffsWorkflowBuilder (the obsolete class with 's'), which means when using the new HandoffWorkflowBuilder (without 's'), method chaining will return the obsolete type. Consider using protected methods that return this and having both derived classes provide public methods with covariant return types, or use a generic type parameter to achieve proper type polymorphism across the inheritance hierarchy.
Motivation and Context
It is often desirable to return control to the user in a handoff orchestration session without losing the context of which Agent is active (and returning to the original starting agent).
Description
Contribution Checklist