Skip to main content

Command Palette

Search for a command to run...

Workflows in Microsoft Agent Framework- Executors, Edges and Events

Published
6 min read
Workflows in Microsoft Agent Framework- Executors, Edges and Events
S
From Synapse Analytics, Power BI, Spark, Microsoft Fabric,ASP.NET Core and recently Agentic AI on .NET I try to explore, learn and share all aspects of Microsoft Data Stack in this blog.

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

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

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