Skip to content

Conversation

@lokitoth
Copy link
Member

@lokitoth lokitoth commented Feb 10, 2026

Motivation and Context

The initial implementation of the InProcess runtime relied on exact matches on handled types to determine routing. This was a result of the need to be able to go from a TypeId to a runtime Type after a checkpoint serialization/deserialization cycle, which loses type information. With the changes to provide a Source Generator rather than Reflection, and a more expressive ProtocolBuilder API to be able to define the types of sent messages and yielded outputs, we can switch the deserialization logic to rely on the source executor's information,

Description

Implements Polymorphic routing (closes #782, closes #3116)

  • [BREAKING] Changes ConfigureRoutes/ConfigureXYZTypes to ConfigureProtocol (closes .NET: Reconcile Executor Configure and ConfigureXYZ methods #3242)
  • [BREAKING] Adds check for outgoing messages and outputs to match registered types
  • Temporarily updates ReflectingExecutor (which is Obsolete) to properly register outgoing types
  • Update FunctionExecutor to register outgoing types and listen to [SendsMessage] and [YieldsOutput] attributes
  • TODO:
    • Add support for attributes-based outgoing types registration to Basic Executors
    • Update Declarative Workflows for missing type registrations (if any)

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? If yes, add "[BREAKING]" prefix to the title of the PR.

@lokitoth lokitoth added .NET workflows Related to Workflows in agent-framework labels Feb 10, 2026
@lokitoth lokitoth moved this to In Progress in Agent Framework Feb 10, 2026
@lokitoth lokitoth force-pushed the dev/dotnet_workflow/polymorphic_routing branch from a0a5997 to 8e3d6d6 Compare February 10, 2026 09:54
@lokitoth lokitoth force-pushed the dev/dotnet_workflow/polymorphic_routing branch from 8e3d6d6 to 8e48bef Compare February 10, 2026 10:06
@lokitoth lokitoth force-pushed the dev/dotnet_workflow/polymorphic_routing branch from 8e48bef to 786d27e Compare February 10, 2026 10:29
@lokitoth lokitoth force-pushed the dev/dotnet_workflow/polymorphic_routing branch from 786d27e to d95b528 Compare February 10, 2026 10:41
@lokitoth lokitoth marked this pull request as ready for review February 10, 2026 10:53
Copilot AI review requested due to automatic review settings February 10, 2026 10:53
@lokitoth lokitoth moved this from In Progress to In Review in Agent Framework Feb 10, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements polymorphic routing and a new protocol-configuration model for .NET workflows by replacing ConfigureRoutes/ConfigureXYZTypes with a unified ConfigureProtocol + ProtocolBuilder pipeline, and updating runners, executors, generators, tests, and samples accordingly.

Changes:

  • Introduces ProtocolBuilder/extended ProtocolDescriptor and updates executors to declare routes + sent/yielded types via ConfigureProtocol.
  • Updates in-proc execution and edge chasing to support polymorphic routing across checkpoint serialization boundaries.
  • Updates source generator and test/samples to use partial executors and new protocol APIs.

Reviewed changes

Copilot reviewed 76 out of 76 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowVisualizerTests.cs Update test executors to ConfigureProtocol.
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowBuilderSmokeTests.cs Update smoke tests to ConfigureProtocol.
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestingExecutor.cs Switch to attribute-based handlers.
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/TestRunContext.cs Use AttachRequestContext API.
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/14_Subworkflow_SharedState.cs Convert sample executors to generator model.
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/09_Subworkflow_ExternalRequest.cs Add protocol declarations + error surfacing.
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/08_Subworkflow_Simple.cs Add protocol declarations + error surfacing.
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/03_Simple_Workflow_Loop.cs Replace reflecting executors with new handlers.
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/02_Simple_Workflow_Condition.cs Fail fast on WorkflowErrorEvent; update executors.
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/01a_Simple_Workflow_Sequential.cs Add illustrative (commented) workflow builder notes.
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/01_Simple_Workflow_Sequential.cs Update sequential sample executors/protocol usage.
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/RepresentationTests.cs Update protocol description path.
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Microsoft.Agents.AI.Workflows.UnitTests.csproj Add generator project as analyzer.
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/InProcessStateTests.cs Make test class partial for generator.
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/ForwardMessageExecutor.cs Declare sent types via ProtocolBuilder.
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/EdgeRunnerTests.cs Add CancellationToken to edge chase calls.
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/DynamicRequestPortTests.cs Use AttachRequestContext API.
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/DynamicPortsExecutor.cs Migrate port handler setup to ConfigureProtocol.
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AgentWorkflowBuilderTests.cs Fail fast on WorkflowErrorEvent.
dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/SyntaxTreeFluentExtensions.cs Add fluent syntax-tree assertions.
dotnet/tests/Microsoft.Agents.AI.Workflows.Generators.UnitTests/ExecutorRouteGeneratorTests.cs Update generator tests for new output format.
dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/ObjectModel/WorkflowActionExecutorTest.cs Register sent types for declarative executor.
dotnet/tests/Microsoft.Agents.AI.DevUI.UnitTests/DevUIIntegrationTests.cs Update test executor to ConfigureProtocol.
dotnet/src/Microsoft.Agents.AI.Workflows/Workflow.cs Describe protocol incl. output executor yields.
dotnet/src/Microsoft.Agents.AI.Workflows/SubworkflowBinding.cs Pass workflow protocol into host executor.
dotnet/src/Microsoft.Agents.AI.Workflows/StatefulExecutor.cs Add protocol-based routing/type declarations.
dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/WorkflowHostExecutor.cs Forward protocol types for subworkflow host.
dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/RequestInfoExecutor.cs Declare outgoing request/response protocol types.
dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/OutputMessagesExecutor.cs Declare yielded chat output type.
dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/HandoffsStartExecutor.cs Update options + declare sent types.
dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/HandoffsEndExecutor.cs Declare yielded messages type.
dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/HandoffAgentExecutor.cs Switch to typed executor return path.
dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/GroupChatHost.cs Migrate host to ChatProtocolExecutor model.
dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/ConcurrentEndExecutor.cs Declare yielded chat output type.
dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIContentExternalHandler.cs Refactor to work with ProtocolBuilder.
dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs Use ProtocolBuilder for external handlers.
dotnet/src/Microsoft.Agents.AI.Workflows/RouteBuilder.cs Expose output types for protocol building.
dotnet/src/Microsoft.Agents.AI.Workflows/Reflection/RouteBuilderExtensions.cs Expose handler discovery helper.
dotnet/src/Microsoft.Agents.AI.Workflows/Reflection/ReflectingExecutor.cs Update obsolete reflection executor protocol setup.
dotnet/src/Microsoft.Agents.AI.Workflows/ProtocolDescriptor.cs Extend descriptor with Sends/Yields.
dotnet/src/Microsoft.Agents.AI.Workflows/ProtocolBuilder.cs New unified protocol configuration builder.
dotnet/src/Microsoft.Agents.AI.Workflows/InProc/InProcessRunnerContext.cs Enforce declared send/yield types; pass cancellation.
dotnet/src/Microsoft.Agents.AI.Workflows/InProc/InProcessRunner.cs Translate message types via source protocol.
dotnet/src/Microsoft.Agents.AI.Workflows/FunctionExecutor.cs Add outgoing type registration + attributes.
dotnet/src/Microsoft.Agents.AI.Workflows/Executor.cs New protocol model + delayed request context.
dotnet/src/Microsoft.Agents.AI.Workflows/Execution/ResponseEdgeRunner.cs Add cancellation + runtime-type-based delivery.
dotnet/src/Microsoft.Agents.AI.Workflows/Execution/MessageRouter.cs Implement polymorphic handler matching logic.
dotnet/src/Microsoft.Agents.AI.Workflows/Execution/FanOutEdgeRunner.cs Add cancellation + runtime-type-based target filtering.
dotnet/src/Microsoft.Agents.AI.Workflows/Execution/FanInEdgeState.cs Group released messages by source.
dotnet/src/Microsoft.Agents.AI.Workflows/Execution/FanInEdgeRunner.cs Map portable types via source protocol translator.
dotnet/src/Microsoft.Agents.AI.Workflows/Execution/EdgeRunner.cs Add cancellation + runtime type helper methods.
dotnet/src/Microsoft.Agents.AI.Workflows/Execution/EdgeMap.cs Thread cancellation through edge preparation.
dotnet/src/Microsoft.Agents.AI.Workflows/Execution/DirectEdgeRunner.cs Use runtime type for polymorphic delivery.
dotnet/src/Microsoft.Agents.AI.Workflows/Checkpointing/PortableMessageEnvelope.cs Persist message source identity.
dotnet/src/Microsoft.Agents.AI.Workflows/ChatProtocolExecutor.cs Move chat routing config to protocol builder.
dotnet/src/Microsoft.Agents.AI.Workflows/ChatProtocol.cs Update chat protocol detection criteria.
dotnet/src/Microsoft.Agents.AI.Workflows/ChatForwardingExecutor.cs Update forwarding executor protocol declarations.
dotnet/src/Microsoft.Agents.AI.Workflows/Attributes/YieldsOutputAttribute.cs Allow method-level output declaration.
dotnet/src/Microsoft.Agents.AI.Workflows/Attributes/SendsMessageAttribute.cs Allow method-level send declaration.
dotnet/src/Microsoft.Agents.AI.Workflows.Generators/Models/MethodAnalysisResult.cs Rename configure flag to protocol variant.
dotnet/src/Microsoft.Agents.AI.Workflows.Generators/Models/ExecutorInfo.cs Split generation conditions for Sends/Yields.
dotnet/src/Microsoft.Agents.AI.Workflows.Generators/Generation/SourceBuilder.cs Generate ConfigureProtocol fluent chains.
dotnet/src/Microsoft.Agents.AI.Workflows.Generators/ExecutorRouteGenerator.cs Combine IO-only analysis results.
dotnet/src/Microsoft.Agents.AI.Workflows.Generators/Diagnostics/DiagnosticDescriptors.cs Rename diagnostic for ConfigureProtocol.
dotnet/src/Microsoft.Agents.AI.Workflows.Generators/Analysis/SemanticAnalyzer.cs Detect ConfigureProtocol and base chaining.
dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Kit/RootExecutor.cs Register ActionExecutorResult as sent type.
dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Kit/ActionExecutor.cs Register ActionExecutorResult as sent type.
dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/DelegateActionExecutor.cs Register ActionExecutorResult as sent type.
dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/DeclarativeWorkflowExecutor.cs Register ActionExecutorResult as sent type.
dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/DeclarativeActionExecutor.cs Register ActionExecutorResult as sent type.
dotnet/samples/GettingStarted/Workflows/_Foundational/08_WriterCriticWorkflow/Program.cs Make executor partial + use [MessageHandler].
dotnet/samples/GettingStarted/Workflows/_Foundational/08_WriterCriticWorkflow/08_WriterCriticWorkflow.csproj Add generator as analyzer reference.
dotnet/samples/GettingStarted/Workflows/Observability/WorkflowAsAnAgent/WorkflowHelper.cs Make helper/executors partial + annotate handlers.
dotnet/samples/GettingStarted/Workflows/Observability/WorkflowAsAnAgent/WorkflowAsAnAgentObservability.csproj Add generator as analyzer reference.
dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/Program.cs Make executor partial + annotate handlers.
dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/CustomAgentExecutors.csproj Add generator as analyzer reference.
Comments suppressed due to low confidence (2)

dotnet/src/Microsoft.Agents.AI.Workflows.Generators/Analysis/SemanticAnalyzer.cs:413

        foreach (var member in classSymbol.GetMembers("ConfigureProtocol"))
        {
            if (member is IMethodSymbol method && !method.IsAbstract &&
                SymbolEqualityComparer.Default.Equals(method.ContainingType, classSymbol))
            {
                return true;
            }
        }

dotnet/src/Microsoft.Agents.AI.Workflows.Generators/Analysis/SemanticAnalyzer.cs:440

            foreach (var member in baseType.GetMembers("ConfigureProtocol"))
            {
                if (member is IMethodSymbol method && !method.IsAbstract)
                {
                    return true;
                }
            }

@lokitoth lokitoth force-pushed the dev/dotnet_workflow/polymorphic_routing branch from d95b528 to 7e90cbc Compare February 10, 2026 11:45
@lokitoth lokitoth force-pushed the dev/dotnet_workflow/polymorphic_routing branch from 7e90cbc to 2c1f7e5 Compare February 10, 2026 11:54
@lokitoth lokitoth force-pushed the dev/dotnet_workflow/polymorphic_routing branch from 2c1f7e5 to 9d2bdde Compare February 10, 2026 13:12
@lokitoth lokitoth force-pushed the dev/dotnet_workflow/polymorphic_routing branch from 9d2bdde to 1fcd88d Compare February 10, 2026 16:15
@lokitoth lokitoth force-pushed the dev/dotnet_workflow/polymorphic_routing branch from 1fcd88d to 3f6fc32 Compare February 10, 2026 18:51
@lokitoth lokitoth force-pushed the dev/dotnet_workflow/polymorphic_routing branch from 3f6fc32 to ab18459 Compare February 11, 2026 15:04
* Adds annotations to Declarative workflow executors
* Implicit filter in collection loops
* Remove debug / usused / superfluous code
* Fix ProtocolBuilder implicit output registrations
* Fix logic error in ExecuteRouteGeneratorTests.ClassWithManualConfigureProtocol_DoesNotGenerate
@lokitoth lokitoth force-pushed the dev/dotnet_workflow/polymorphic_routing branch from ab18459 to 5258a47 Compare February 11, 2026 15:32
Copy link
Member

@markwallace-microsoft markwallace-microsoft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some feedback from GitHub Copilot CLI

Logic Error Review — PR #3792 (Polymorphic Routing)


🔴 1. FanInEdgeRunner uses wrong TypeId for polymorphic type resolution

File: Execution/FanInEdgeRunner.csMapRuntimeTypes local function

The inline MapRuntimeTypes uses envelope.MessageType (the declared base type) for type translation, but after checkpoint serialization envelope.MessageType contains the declared base type, not the runtime derived type. All other edge runners (DirectEdgeRunner, FanOutEdgeRunner, ResponseEdgeRunner) correctly delegate to GetMessageRuntimeTypeAsync, which checks if envelope.Message is PortableValue and uses portableValue.TypeId to get the actual runtime type.

This means polymorphic routing through FanIn edges will use the declared base type instead of the derived type, potentially causing messages to be dropped or misrouted when the handler hierarchy expects the most-derived type for walking up.

Suggested fix: Refactor MapRuntimeTypes to use the same PortableValue-aware logic as EdgeRunner.GetMessageRuntimeTypeAsync, or call that method directly.


🔴 2. Source generator no longer registers handler return types as yielded outputs

File: SourceGen/Generation/SourceBuilder.csGenerateConfigureYieldTypes method

This method now only iterates handler.YieldTypes (from [YieldsOutput] attributes) but no longer registers implicit output types from handler return values (e.g. int from ValueTask<int>). The old code explicitly registered handler.OutputTypeName when handler.HasOutput was true.

Evidence this is unintentional:

  • The method comment still says "Types come from [YieldsOutput] attributes and handler return types (ValueTask<T>)"
  • HasHandlerWithYieldTypes (in ExecutorInfo.cs) still checks handler.HasOutput, indicating the intent is to include these types

Suggested fix: After the YieldTypes iteration, also register handler.OutputTypeName for handlers where handler.HasOutput is true.


🟡 3. ReflectingExecutor crashes if subclass has no IMessageHandler interfaces

File: Reflection/ReflectingExecutor.csConfigureProtocol

Aggregate() is called without a seed value on a sequence derived from messageHandlers. If a ReflectingExecutor subclass implements no IMessageHandler<T> interfaces, the sequence is empty and Aggregate throws InvalidOperationException: Sequence contains no elements. While current subclasses all have handlers and the class is marked [Obsolete], this is still a latent runtime crash for any new subclass.

Suggested fix: Guard with an empty check before calling Aggregate, or use the overload with a seed value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

.NET workflows Related to Workflows in agent-framework

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

.NET: Reconcile Executor Configure and ConfigureXYZ methods Support for polymorphic routing .NET: Route messages are not handled polymorphically

2 participants