# 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.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/2ad03cdb-ec6d-402e-b480-4e41a616d8d0.png align="center")

**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.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/075dcfd2-4473-48c5-a010-77834dc2e500.png align="center")

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 **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.

```csharp
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.

Lambda functions can be used to create executors with **BindAsExecutor()**and is syntactically more simpler.

```csharp
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**

```csharp
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**

```csharp
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

```csharp
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.

```csharp
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** .

```csharp
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

```csharp
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](https://www.azureguru.net/deep-dive-into-workflow-execution-in-microsoft-agent-framework) on **Switch-Case Edges**

***Fan-out***

**Fan-out Edges** sends messages to multiple executors in parallel

```csharp
var workflow = new WorkflowBuilder(Master)
.AddFanOutEdge(Master, new[] { workerA, workerB, workerC }).Build();
```

***Fan-in***

Send messages to multiple executors in parallel

```csharp
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

```csharp
// 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
```

Reference : [https://learn.microsoft.com/en-us/agent-framework/workflows/events?pivots=programming-language-csharp#built-in-event-types](https://learn.microsoft.com/en-us/agent-framework/workflows/events?pivots=programming-language-csharp#built-in-event-types)

**Consume Events**

```csharp

 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 !!!
