Skip to content

Conversation

@alliscode
Copy link
Member

@alliscode alliscode commented Jan 8, 2026

This pull request introduces a new Roslyn source generator project for the Microsoft Agent Framework Workflows, enabling compile-time route configuration for executor classes using the [MessageHandler] attribute. It includes the generator implementation, diagnostics, build configuration, and integration into the solution and dependency management.


Follow-up PRs will update samples and docs to use this new pattern and obsolete the ReflectingExecutor

New Source Generator Project:

  • Added new project Microsoft.Agents.AI.Workflows.Generators targeting netstandard2.0, with all necessary build and NuGet packaging settings, and Roslyn dependencies for source generator development. [1] [2]

Source Generator Implementation:

  • Implemented the ExecutorRouteGenerator class, a Roslyn incremental source generator that scans for [MessageHandler] methods and generates partial classes to configure workflow routes at compile time.
  • Added the SourceBuilder utility to generate the actual C# code for route configuration, including overrides for ConfigureRoutes, ConfigureSentTypes, and ConfigureYieldTypes as needed.

Diagnostics and Analysis:

  • Introduced DiagnosticDescriptors to define and register custom diagnostics for common usage errors (e.g., missing parameters, invalid return types, non-partial classes, etc.) in handler methods and executor classes.

Solution and Dependency Integration:

  • Registered the new generator project and its corresponding unit test project in the solution file (agent-framework-dotnet.slnx). [1] [2]
  • Updated Directory.Packages.props to include Microsoft.CodeAnalysis.Analyzers as a dependency for code analysis support.### Motivation and Context

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.

@markwallace-microsoft markwallace-microsoft added documentation Improvements or additions to documentation .NET workflows Related to Workflows in agent-framework labels Jan 8, 2026
@github-actions github-actions bot changed the title Initial working version with tests. .NET: Initial working version with tests. Jan 8, 2026
@alliscode alliscode changed the title .NET: Initial working version with tests. .NET: Draft- Executor source gen for workflows Jan 8, 2026
@alliscode alliscode marked this pull request as ready for review January 12, 2026 17:57
/// </summary>
public static readonly DiagnosticDescriptor MissingWorkflowContext = Register(new(
id: "MAFGENWF001",
title: "Handler missing IWorkflowContext parameter",
Copy link
Member

Choose a reason for hiding this comment

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

We don't need to localize these strings, right?

public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Step 1: Use ForAttributeWithMetadataName to efficiently find methods with [MessageHandler] attribute. For each method found, build a MethodAnalysisResult.
var methodAnalysisResults = context.SyntaxProvider
Copy link
Member

Choose a reason for hiding this comment

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

Rooting the analysis on the methods annotated by [MessageHandler] is likely simpler than starting with extends(Executor), but it has the drawback that if we only want to use SourceGeneration for the Send/Yield types, and manually create the routes, we ignore that Executor class;

We should also do a bit of perf work here, after we settle on the API surface (attributes, what gets generated / not).

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed that we should update this to handle Send/Yield types even with manual routing, but I don't think we'd want to root on extends(Executor). Rooting on an attribute with ForAttributeWithMetadataName claims to be at least 99x faster.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've updated things to handle this case while still rooting on ForAttributeWithMetadataName .

sb.AppendLine($"{indent}partial class {info.ClassName}{info.GenericParameters}");
sb.AppendLine($"{indent}{{");

var memberIndent = indent + " ";
Copy link
Member

Choose a reason for hiding this comment

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

We may want to consider using the AnalyzerConfigOptionsProvider mechanism to get the indent style / spacecount from .editorconfig, so that the generated code matches the general project style.

Copy link
Member Author

Choose a reason for hiding this comment

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

Interesting idea. By default, these generated "files" are not written to actual files and even in VS/Code you have to look in the package dependency to see the virtual file. I'm not sure I see the value in trying to match spacecount etc.

{
var builder = ImmutableArray.CreateBuilder<string>();

foreach (var attr in classSymbol.GetAttributes())
Copy link
Member

Choose a reason for hiding this comment

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

Does this always get iterated in the same order?

Since we are creating immutable arrays that are presumably used as some form of comparator somewhere, do we want/need to ensure this is permutation invariant?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good call out. They are consistent (source order) for a given file, but the partial classes may break that. I've added explicit sort to this and other places that have this issue.

AppendHandlerGenericArgs(sb, handler);
sb.Append($"(this.{handler.MethodName})");

if (isLast)
Copy link
Member

Choose a reason for hiding this comment

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

If you pull this out of the loop you can avoid the conditional and just do sb.AppendLine(";");.

Copy link
Member Author

Choose a reason for hiding this comment

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

Pulled it out and added a line to clean up the extra newline

@alliscode alliscode requested a review from a team as a code owner January 16, 2026 20:34
@markwallace-microsoft markwallace-microsoft added python lab Agent Framework Lab labels Jan 16, 2026
@github-actions github-actions bot changed the title .NET: Executor source gen for workflow executor routing Python: .NET: Executor source gen for workflow executor routing Jan 16, 2026

foreach (var namedArg in attr.NamedArguments)
{
if (namedArg.Key == "Yield" && !namedArg.Value.IsNull)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Should this be a case sensitive comparison just to be safe?

{
var sb = new StringBuilder();

if (!string.IsNullOrEmpty(info.Namespace))
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Probably better to use IsNullOrWhitespace or is whitespace a valid namespace?

sb.AppendLine();

// Namespace
if (!string.IsNullOrEmpty(info.Namespace))
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment for this.

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

Labels

documentation Improvements or additions to documentation lab Agent Framework Lab .NET python workflows Related to Workflows in agent-framework

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants