Workflows in Microsoft Agent Framework- Executors, Edges and Events

Search for a command to run...

No comments yet. Be the first to comment.
In these blogs, I have explored the intricacies and technical nuances of the underlying architecture, implementation details, authentication flows, and real-world integration scenarios.
My previous article focused on introduction of workflows in MAF along with a basic overview of Executors, Edges and Events. This article will explore different workflow orchestration patterns availabl
Lets take a scenario where you have an MAF (Microsoft Agent Framework) AIAgent that has tool calls to multiple AIFunctions. You definitely wouldn't want an AIAgent to blindly invoke every available to

Imagine a scenario where you have an AI agent that needs to access Azure services, such as Azure Storage. Simply granting the agent open and unrestricted access to Azure resources is not an option. Do

The tittle sounds confusing right ? So how to access Azure services with federated credentials that are tied with User Managed Identities ? Even I was skeptical if it is even possible because its more

My earlier article on securing Azure Storage in ASP.NET core Minimal API's extensively leveraged RBAC access control and through Microsoft Entra ID for user sign-in through OpenID Connect (OIDC) to au

In this article, we'll take a deep dive into securing an ASP.NET Core Minimal API with Microsoft Entra ID and using it to access Azure ADLS Gen2 storage implementing Microsoft Entra ID for user sign-i

Before we dive into the details of the topic of this article, lets first understand why we need workflows in Agentic AI systems. Most people might argue that when agents can make decisions, make tool calls and orchestrate then what is the need for workflows ?
And honestly, that is a fair question. In many simple scenarios an agent can perform the same operations that a workflow can. An agent can classify input, decide what action to take next, invoke functions and even coordinate multiple steps . So it might look that workflows are an unnecessary overhead.
Now, think about this for a moment, why do we need ETL when Stored Procedures can perform data transformations in database systems ?
The reason is that the idea behind ETL tools was not merely to perform data transformations when Stored Procedures could already do that. Apart from data transformations the ETL systems provide: orchestration, monitoring, retries, scheduling, scalability, checkpointing, hierarchical execution so on an so forth, features that lack in Stored Procedures.
The same principle applies to workflows in Agentic AI. If you do understand the importance of ETL over Stored Procedures or Entity Framework over Raw SQL for that matter, then its not difficult to understand and visualize the importance of Workflows over Agents in Agentic AI system.
And in no ways I am proposing the usage of workflows for every Agentic AI use case. Many small to medium Agentic systems can survive perfectly fine with Agents alone, similar to how small to medium Data Engineering systems can perfectly work well without ETL's.
But as systems become larger, stateful, distributed, and operationally complex, workflows start to provide real value beyond simple if/else orchestration logic.
Agents : Agents are primarily LLM driven with ability to reason on an given context and are non deterministic.
WorkFlows : Workflows are predefined sequence of operations with the ability to handle complex sequential or parallel business operations with dynamic execution path. They may or may not include agents. Workflows are type safe and support parallel, sequential and handoff execution mode.
This article is focused on three major aspects of a Workflow :
Executors
Edges
Events
In MAF a workflow revolves around an Executor. Think of it as an execution processing engine of the workflow.
Executors derive from the Executor base class and use the [MessageHandler] or [YieldsOutput(typeof(T))] attribute to declare handler methods. The class must be marked partial to enable source generation.
using Microsoft.Agents.AI.Workflows;
partial class ReverseExecutor : Executor<string, string>
{
private ReverseExecutor() : base("ReverseExecutor") { }
[MessageHandler]
public override ValueTask<string> HandleAsync(
string message,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
return ValueTask.FromResult(message.Reverse());
}
}
The Executor implementation should inherit from the Executor base class of type or <TInput,TOutput>.
The class should be partial and MessageHandler or YieldsOutput attribute should be used to declare the handler method. These two are explained later in the article.
Lambda functions can be used to create executors with **BindAsExecutor()**and is syntactically more simpler.
Func<string, string> reverseFunc = s => string.Concat(s.Reverse());
var reverse = reverseFunc.BindAsExecutor("ReverseExecutor");
IWorkflowContext
With IWorkflowContext its possible to interact with workflow during execution. It provides the following two methods :
SendMessageAsync
using Microsoft.Agents.AI.Workflows;
partial class ReverseExecutor : Executor<string, string>
{
private ReverseExecutor() : base("ReverseExecutor") { }
[MessageHandler]
public override ValueTask<string> HandleAsync(
string message,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
return context.SendMessageAsync(message.Reverse());
}
}
YieldOutputAsync
using Microsoft.Agents.AI.Workflows;
partial class ReverseExecutor : Executor<string, string>
{
private ReverseExecutor() : base("ReverseExecutor") { }
[YieldsOutput(typeof(string))]
public override ValueTask<string> HandleAsync(
string message,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
return context.YieldOutputAsync(message.Reverse());
}
}
The difference between SendMessageAsync and YieldOutputAsync is that, SendMessageAsync sends message to all the connected executors in a workflow while YieldOutputAsync sends the output only to the caller. With YieldOutputAsync, the other executors in the workflow are unaware of the output.
WorkflowBuilder
API for constructing workflows
var builder = new WorkflowBuilder(startExecutor);
builder.AddEdge(executorA, executorB);
builder.WithOutputFrom(endExecutor);
var workflow = builder.Build();
Now that we understand Executors, its important to understand how do they connect with each other. This is primarily done through Edges.
Simple/Direct
This is the most simplest form of an edge. It connects two executors without any conditions.
var workflow = new WorkflowBuilder(Executor1)
.AddEdge(Executor1, Executor2)
.AddEdge(Executor2, Executor3)
.Build();
Conditional
Assume a use case, where a given number is to be classified into even or odd and if odd, then the number should be forwarded through another executor lets say sendDetailsExecutor .
var workflow = new WorkflowBuilder(numberAnalyzer)
.AddEdge(numberAnalyzer,EvenNo_HandlerExecutor, condition: GetCondition(expectedResult: true))
.AddEdge(numberAnalyzer,OddNo_HandlerExecutor, condition: GetCondition(expectedResult: true))
.WithOutputFrom(OddNo_HandlerExecutor,sendDetailsExecutor)
.Build();
Switch-Case
A major drawback of Conditional Edges is that there is no straightforward way to handle a third case for example : a non numeric or special character from the example above.
This is where the Switch-Case Edges are useful
WorkflowBuilder builder = new(numberAnalyzer);
builder.AddSwitch(numberAnalyzer, switchBuilder =>
switchBuilder
.AddCase(GetCondition(Number.Even),EvenNo_HandlerExecutor)
.AddCase(GetCondition(Number.Odd),OddNo_HandlerExecutor)
.WithDefault(Uncertain_HandlerExecutor))
.AddEdge(EvenNo_HandlerExecutor,Handle_EvenNoExecutor)
.WithOutputFrom(OddNo_HandlerExecutor,Handle_EvenNoExecutor,Uncertain_HandlerExecutor)
var workflow = builder.Build();
I will soon publish a detailed example in my next article on Switch-Case Edges
Fan-out
Fan-out Edges sends messages to multiple executors in parallel
var workflow = new WorkflowBuilder(Master)
.AddFanOutEdge(Master, new[] { workerA, workerB, workerC }).Build();
Fan-in
Send messages to multiple executors in parallel
var workflow = new WorkflowBuilder(Master)
.AddFanOutEdge(Master,new[] { workerA, workerB, workerC })
.AddFanInBarrierEdge(new[] { workerA, workerB, workerC }, Master).Build();
Events provide details for each step during execution and through streaming can be consumed real time
// Workflow lifecycle events
WorkflowStartedEvent // Workflow execution begins
WorkflowOutputEvent // Workflow outputs data
WorkflowErrorEvent // Workflow encounters an error
WorkflowWarningEvent // Workflow encountered a warning
// Executor events
ExecutorInvokedEvent // Executor starts processing
ExecutorCompletedEvent // Executor finishes processing
ExecutorFailedEvent // Executor encounters an error
AgentResponseEvent // An agent run produces output
AgentResponseUpdateEvent // An agent run produces a streaming update
// Superstep events
SuperStepStartedEvent // Superstep begins
SuperStepCompletedEvent // Superstep completes
// Request events
RequestInfoEvent // A request is issued
Consume Events
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
{
if (evt is SuperStepCompletedEvent superStepCompleted)
{
Console.WriteLine($"superStepCompleted{evt.Data}");
}
if (evt is ExecutorCompletedEvent executorCompleted)
{
Console.WriteLine($"{executorCompleted.ExecutorId}: {executorCompleted.Data}");
}
if (evt is AgentResponseUpdateEvent executorComplete)
{
Console.WriteLine($"{executorComplete.ExecutorId}: {executorComplete.Data}");
}
if (evt is WorkflowOutputEvent outputEvent)
{
Console.WriteLine($"{outputEvent}");
}
}
In this article I have barely scratched the surface of Workflows in Microsoft Agent Framework. The details are so extensive that its almost impossible to cover them in a single article. Overtime, I will post a series of articles covering different aspects of workflows with extensive examples.
So, stay tuned !!!