Sequential workflows in Microsoft Agent Framework

8 min read
Learn how to orchestrate AI agents in a step-by-step pipeline using Microsoft Agent Framework's SequentialBuilder

A single AI agent is powerful and can do impressive things. But real-world applications often need multiple specialists working together, with each agent handling a specific task before passing results to the next. As with humans, in the agentic world we also need to break complex problems into manageable pieces and have specialized agents handle each task. We need one agent to review another’s work and handle complex tool invocation.

Trying to cram all of this into one agent leads to:

  • Confused outputs (the agent doesn’t know which “hat” to wear)
  • Context overload (too many tools and instructions)
  • Difficult debugging (hard to know what went wrong)

Microsoft Agent Framework (MAF) makes this orchestration simple with its workflow patterns. Workflows in MAF connect multiple agents (and other executors) into a graph that defines how data flows between them. Think of it as the “plumbing” that connects your agents together. In this article, we’ll focus on the most fundamental pattern: Sequential Workflows.

Sequential Workflows

The Sequential Pattern is the simplest and most intuitive workflow pattern. Agents process data sequentially, each building on the previous agent’s output.

In a sequential flow, each agent receives the accumulated conversation history (including all previous messages), processes the input, generates a response, appends it to the conversation, and passes the updated conversation to the next agent. These workflows are perfect for content pipelines (research → write → edit → publish), data transformation chains (extract → transform → load), review processes (draft → review → approve), and step-by-step analysis (collect data → analyze → summarize).

Building a Sequential Workflow: Step by Step

Let’s build a content creation pipeline that demonstrates the sequential pattern. We’ll start simple and gradually add complexity.

First, install the required packages:

1
2
$ pip install agent-framework-azure-ai --pre
$ pip install python-dotenv azure-identity

Set up your environment variables (create a .env file):

1
2
AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME="gpt-4o"

Let’s start by importing everything we need:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from agent_framework import Role, WorkflowRunState
from agent_framework.orchestrations import SequentialBuilder

from agent_framework.openai import OpenAIChatClient
from azure.identity import DefaultAzureCredential
import asyncio
import os
from dotenv import load_dotenv

load_dotenv()

SequentialBuilder in the MAF package agent_framework is used to build a sequential workflow. We will discuss other imports as we progress further.

We need a chat client for the workflow.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
model = (
  os.getenv("AZURE_OPENAI_CHAT_MODEL")
  or os.getenv("AZURE_OPENAI_MODEL")
  or os.getenv("AZURE_OPENAI_CHAT_DEPLOYMENT_NAME")
  or os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME")
)
chat_client = OpenAIChatClient(
  model=model,
  credential=DefaultAzureCredential(),
)

OpenAIChatClient resolves the deployed model name from one of the standard environment variables and authenticates via DefaultAzureCredential, which tries multiple authentication methods in order:

  • Environment variables
  • Managed Identity
  • Azure CLI (az login)

Now let’s create three specialized agents for our content pipeline:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
researcher = chat_client.as_agent(
    name="Researcher",
    instructions="Research the given topic and provide key facts and statistics.",
)

writer = chat_client.as_agent(
    name="Writer", 
    instructions="Take the research and write an engaging article draft.",
)

editor = chat_client.as_agent(
    name="Editor",
    instructions="Review the draft for clarity, grammar, and engagement. Provide the final version.",
)

A sequential workflow is created using the SequentialBuilder().

1
2
3
4
5
workflow = (
    SequentialBuilder()
    .participants([researcher, writer, editor])
    .build()
)

That’s it! Just two method calls:

  • .participants([...])- List your agents in the order they should execute.
  • .build()- Construct the workflow.

Under the hood, this creates:

  • An executor for each agent
  • Edges connecting them in sequence
  • A start point (first agent) and an end point (last agent)

Running the Workflow with Streaming

MAF supports real-time event streaming during workflow execution. This gives you visibility into what’s happening:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
async def main():        
    # Run the workflow with streaming events
    print("Running sequential workflow: Researcher → Writer → Editor")
    print("=" * 60)
    
    output_data = None
    
    async for event in workflow.run("Write about the future of AI agents", stream=True):
        if event.type == "executor_invoked":
            print(f"⚡ Starting: {event.executor_id}")
        elif event.type == "executor_completed":
            print(f"✓ Completed: {event.executor_id}")
        elif event.type == "status":
            if event.state == WorkflowRunState.IDLE:
                print("\n✅ Workflow completed!")
        elif event.type == "output":
            output_data = event.data

Workflow events come through as WorkflowEvent instances, discriminated by the event.type string. Let’s understand each one:

Event type When it fires What it contains
"executor_invoked" When an agent starts processing event.executor_id — the agent’s name
"executor_completed" When an agent finishes event.executor_id
"status" When the workflow state changes event.stateSTARTED, IN_PROGRESS, IDLE, FAILED, etc.
"output" When the workflow produces output event.data — the final result, plus event.executor_id

The final output contains the complete conversation history.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    # Display the final conversation
    if output_data:
        print("\n" + "=" * 60)
        print("FINAL CONVERSATION")
        print("=" * 60)
        for i, msg in enumerate(output_data, start=1):
            name = msg.author_name or ("assistant" if msg.role == Role.ASSISTANT else "user")
            print(f"\n{'-' * 60}")
            print(f"{i:02d} [{name}]")
            print(f"{'-' * 60}")
            print(msg.text)

if __name__ == "__main__":
    asyncio.run(main())

For a SequentialBuilder workflow, event.data on the output event contains a list of message objects with:

  • role: USER or ASSISTANT
  • author_name: The agent’s name (for assistant messages)
  • text: The message content

Here is the complete example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
from agent_framework import Role, WorkflowRunState
from agent_framework.orchestrations import SequentialBuilder
from agent_framework.openai import OpenAIChatClient
from azure.identity import DefaultAzureCredential
import asyncio
import os
from typing import Any

from dotenv import load_dotenv

load_dotenv()

# Create chat client
model = (
    os.getenv("AZURE_OPENAI_CHAT_MODEL")
    or os.getenv("AZURE_OPENAI_MODEL")
    or os.getenv("AZURE_OPENAI_CHAT_DEPLOYMENT_NAME")
    or os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME")
)
chat_client = OpenAIChatClient(
    model=model,
    credential=DefaultAzureCredential(),
)

# Create specialized agents
researcher = chat_client.as_agent(
    name="Researcher",
    instructions="Research the given topic and provide key facts and statistics.",
)

writer = chat_client.as_agent(
    name="Writer", 
    instructions="Take the research and write an engaging article draft.",
)

editor = chat_client.as_agent(
    name="Editor",
    instructions="Review the draft for clarity, grammar, and engagement. Provide the final version.",
)

async def main():        
    # Build sequential workflow
    workflow = (
        SequentialBuilder()
        .participants([researcher, writer, editor])
        .build()
    )

    # Run the workflow with streaming events
    print("Running sequential workflow: Researcher → Writer → Editor")
    print("=" * 60)
    
    output_data = None
    
    async for event in workflow.run("Write about the future of AI agents", stream=True):
        if event.type == "executor_invoked":
            print(f"⚡ Starting: {event.executor_id}")
        elif event.type == "executor_completed":
            print(f"✓ Completed: {event.executor_id}")
        elif event.type == "status":
            if event.state == WorkflowRunState.IDLE:
                print("\n✅ Workflow completed!")
        elif event.type == "output":
            output_data = event.data

    # Display the final conversation
    if output_data:
        print("\n" + "=" * 60)
        print("FINAL CONVERSATION")
        print("=" * 60)
        for i, msg in enumerate(output_data, start=1):
            name = msg.author_name or ("assistant" if msg.role == Role.ASSISTANT else "user")
            print(f"\n{'-' * 60}")
            print(f"{i:02d} [{name}]")
            print(f"{'-' * 60}")
            print(msg.text)

if __name__ == "__main__":
    asyncio.run(main())

When you run this workflow, you’ll see something like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Running sequential workflow: Researcher → Writer → Editor
============================================================
⚡ Starting: Researcher
✓ Completed: Researcher
⚡ Starting: Writer
✓ Completed: Writer
⚡ Starting: Editor
✓ Completed: Editor

✅ Workflow completed!

============================================================
FINAL CONVERSATION
============================================================

------------------------------------------------------------
01 [user]
------------------------------------------------------------
Write about the future of AI agents

------------------------------------------------------------
02 [Researcher]
------------------------------------------------------------
# Key Facts and Statistics on AI Agents

## Market Growth
- The AI agent market is projected to reach $47.1 billion by 2030
- Autonomous AI systems are expected to handle 85% of customer interactions by 2027
...

------------------------------------------------------------
03 [Writer]
------------------------------------------------------------
# The Future of AI Agents: A New Era of Intelligent Automation

The rise of AI agents represents one of the most transformative shifts...
[Engaging article draft based on the research]

------------------------------------------------------------
04 [Editor]
------------------------------------------------------------
# The Future of AI Agents: Reshaping How We Work and Live
....

Sequential workflows in MAF provide a simple yet powerful way to orchestrate multiple AI agents. With just a few lines of code, you can create sophisticated pipelines where:

  • Each agent specializes in one task
  • Data flows naturally from one agent to the next
  • You have full visibility through event streaming
  • The framework handles all the orchestration complexity

The SequentialBuilder makes this pattern incredibly easy to implement. You just define your agents, list them in order, and let MAF handle the rest.

ℹ️
Updated 27th April 2026 for breaking API changes. Microsoft Agent Framework’s Python package was reorganized in version 1.2.0. Several names and patterns referenced here have changed: AzureOpenAIChatClient (in agent_framework.azure) is now OpenAIChatClient (in agent_framework.openai), with an explicit model= parameter and either an azure_endpoint= argument or an AZURE_OPENAI_ENDPOINT environment variable; chat clients use chat_client.as_agent(...) rather than chat_client.create_agent(...); SequentialBuilder moved from agent_framework to agent_framework.orchestrations; workflow.run_stream(...) is now workflow.run(input, stream=True); the per-event classes (ExecutorInvokedEvent, WorkflowOutputEvent, etc.) were collapsed into a single WorkflowEvent type discriminated by event.type (a string). All code samples in this article have been updated. See the client comparison article for the current client surface.

Share this article

Comments