-
Notifications
You must be signed in to change notification settings - Fork 1.1k
.NET: [BREAKING] Implement Polymorphic Routing #3792
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
a0a5997 to
8e3d6d6
Compare
8e3d6d6 to
8e48bef
Compare
8e48bef to
786d27e
Compare
786d27e to
d95b528
Compare
There was a problem hiding this 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/extendedProtocolDescriptorand updates executors to declare routes + sent/yielded types viaConfigureProtocol. - 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
- This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.
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
- This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.
foreach (var member in baseType.GetMembers("ConfigureProtocol"))
{
if (member is IMethodSymbol method && !method.IsAbstract)
{
return true;
}
}
dotnet/src/Microsoft.Agents.AI.Workflows/Execution/MessageRouter.cs
Outdated
Show resolved
Hide resolved
dotnet/src/Microsoft.Agents.AI.Workflows/Execution/MessageRouter.cs
Outdated
Show resolved
Hide resolved
dotnet/src/Microsoft.Agents.AI.Workflows.Generators/Generation/SourceBuilder.cs
Outdated
Show resolved
Hide resolved
dotnet/src/Microsoft.Agents.AI.Workflows.Generators/Generation/SourceBuilder.cs
Outdated
Show resolved
Hide resolved
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/08_Subworkflow_Simple.cs
Show resolved
Hide resolved
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/Sample/09_Subworkflow_ExternalRequest.cs
Show resolved
Hide resolved
dotnet/src/Microsoft.Agents.AI.Workflows.Generators/Generation/SourceBuilder.cs
Show resolved
Hide resolved
d95b528 to
7e90cbc
Compare
7e90cbc to
2c1f7e5
Compare
2c1f7e5 to
9d2bdde
Compare
9d2bdde to
1fcd88d
Compare
1fcd88d to
3f6fc32
Compare
3f6fc32 to
ab18459
Compare
* 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
ab18459 to
5258a47
Compare
markwallace-microsoft
left a comment
There was a problem hiding this 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.cs — MapRuntimeTypes 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.cs — GenerateConfigureYieldTypes 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(inExecutorInfo.cs) still checkshandler.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.cs — ConfigureProtocol
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.
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
TypeIdto a runtimeTypeafter a checkpoint serialization/deserialization cycle, which loses type information. With the changes to provide a Source Generator rather than Reflection, and a more expressiveProtocolBuilderAPI 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)
ConfigureRoutes/ConfigureXYZTypestoConfigureProtocol(closes .NET: Reconcile Executor Configure and ConfigureXYZ methods #3242)[SendsMessage]and[YieldsOutput]attributesContribution Checklist