Workflows in Microsoft Agent Framework- Executors, Edges and Events

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.
Difference between Agents and Workflows
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
Executors
In MAF a workflow revolves around an Executor. Think of it as an execution processing engine of the workflow.
Executor structure
Executors derive from the
Executorbase class and use the[MessageHandler]or[YieldsOutput(typeof(T))]attribute to declare handler methods. The class must be markedpartialto 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 <TInput> 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.
You can also use lambda functions to create executors withBindAsExecutor()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();
Edges
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
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}");
}
}
Conclusion
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 !!!



