Skip to main content

Microsoft Agent Framework–Building a multi-agent workflow with DevUI in .NET

Yesterday, I created a minimal .NET project with DevUI and registered a couple of standalone agents. That gets you surprisingly far for interactive testing. But real business scenarios quickly outgrow a single agent: you need data flowing through multiple specialized steps, decisions being made along the way, and a clear picture of the whole pipeline.

That's what workflows are for. In this post, we'll build a content review pipeline as a concrete example — a Writer agent drafts a response, a Reviewer agent critiques it, and a deterministic formatting step finalizes the output. All of it visualized in DevUI.

Agents vs Workflows — the key distinction

The Agent Framework docs put it cleanly: an agent is LLM-driven and dynamic — it decides which tools to call and in what order, based on the conversation. A workflow is a predefined graph of operations, some of which may be AI agents, but the topology is explicit and deterministic. You decide exactly what runs after what.

When should you choose a workflow over a single agent with tools? A few signals:

  • You need multiple agents with different system prompts to collaborate in a fixed order
  • You have deterministic steps (validation, formatting, API calls) interleaved with LLM steps
  • You need checkpointing, fault tolerance, or human-in-the-loop approval between steps
  • You want type-safe message contracts between pipeline stages

Remark: The key message is to remain pragmatic: if you can write a regular function to handle the task, do that. Use an agent when you need LLM reasoning. Use a workflow when you need explicit control over a multi-step process.

Core Workflow concepts

Three concepts make up the mental model:

Executors are the processing nodes. Each executor receives typed messages, does work (LLM call, API call, data transform), and emits typed output messages. The recommended way to define one is a partial class that inherits from Executor, with handler methods marked [MessageHandler]. The partial keyword enables compile-time source generation for handler registration.

Edges define the connections between executors — which executor's output feeds which other executor's input. They can be simple direct connections, or conditional branches that route based on message content.

WorkflowBuilder ties executors and edges into a directed graph. Once built, you execute it with InProcessExecution.RunStreamingAsync() or RunAsync().

What we're building

A content review pipeline with three stages:

1. Writer executor (AI agent) — takes a topic prompt and drafts a short paragraph

2. Reviewer executor (AI agent) — receives the draft and returns concise actionable feedback

3. Formatter executor (deterministic) — wraps the final output in a structured response envelope

The data flow looks like this:

Step 1: Install extra packages

On top of the packages from the previous post, you need the workflows package:


        dotnet add package Microsoft.Agents.AI.Hosting --prerelease
	dotnet add package Microsoft.Agents.AI.DevUI --prerelease
        dotnet add package Microsoft.Agents.AI.Workflows --prerelease
        dotnet add package OllamaSharp

Step 2: Define the Executors

WriterExecutor.cs

The Writer executor is a thin wrapper around a ChatClientAgent. It receives a string (the user's topic), runs the agent, and returns the draft as a string that gets forwarded to the next executor automatically.

ReviewerExecutor.cs

The Reviewer receives the draft string and returns a string containing its feedback. Same pattern, different agent instructions.

FormatterExecutor.cs

The Formatter is a purely deterministic executor — no AI, just C# code. It calls context.YieldOutputAsync() to emit the final result back to the workflow caller.

Step 3: Wire it up in Program.cs

Now the workflow is assembled in Program.cs alongside the DevUI registration. The key difference from the single-agent setup is that instead of calling builder.AddAIAgent(), we construct agents manually so we can pass them into executor instances, then hand the workflow to the hosting layer.

Step 3 — Run and Explore in DevUI

dotnet run

In the DevUI sidebar you'll now see ContentReviewPipeline listed as a workflow entry rather than an agent. Click on Configure and run, type a topic — "the benefits of electric vehicles" works well — and hit Run workflow.

Unfortunately, that didn't work and resulted in an error. It turns out that using mixed workflows in C# doesn't work yet in DevUI. 

However, an agent only workflow does work. So, let's update our example and change it into an agent only workflow:

Restart the application, click on Configure and run again, type a topic — "the benefits of electric vehicles" — and hit Run workflow.

You'll see each executor fire in sequence. Because agents are wrapped as executors in the workflow, the reasoning trail shows the Writer's output, then the Reviewer's feedback, the full chain of thought, step by step.

Tip: DevUI's workflow visualization is one of its strongest features. Unlike the single-agent view, you can see the executor graph on the left and watch messages propagate through it in real time, which makes it easy to spot where in the pipeline things are going wrong.

Calling the workflow programmatically

If you want to call the workflow directly from code (outside of DevUI), you can use the RunAsync method:


Conclusion

Switching from a single agent to a workflow in the Agent Framework is mostly additive. You keep the same setup from part one and add executor classes plus a WorkflowBuilder call. 

Unfortunately, the DevUI backend part for ASP.NET Core can’t handle all type of workflows yet, so we’ll have to wait for an update. When that happens, I’ll write a follow-up post. Pinky promise!

Meanwhile, you can explore my working end-to-end example here: wullemsb/DevUIExample at workflows

More information

Microsoft Agent Framework Workflows | Microsoft Learn

Microsoft Agent Framework Workflows - Executors | Microsoft Learn

wullemsb/DevUIExample at workflows


Popular posts from this blog

Azure DevOps/ GitHub emoji

I’m really bad at remembering emoji’s. So here is cheat sheet with all emoji’s that can be used in tools that support the github emoji markdown markup: All credits go to rcaviers who created this list.

Podman– Command execution failed with exit code 125

After updating WSL on one of the developer machines, Podman failed to work. When we took a look through Podman Desktop, we noticed that Podman had stopped running and returned the following error message: Error: Command execution failed with exit code 125 Here are the steps we tried to fix the issue: We started by running podman info to get some extra details on what could be wrong: >podman info OS: windows/amd64 provider: wsl version: 5.3.1 Cannot connect to Podman. Please verify your connection to the Linux system using `podman system connection list`, or try `podman machine init` and `podman machine start` to manage a new Linux VM Error: unable to connect to Podman socket: failed to connect: dial tcp 127.0.0.1:2655: connectex: No connection could be made because the target machine actively refused it. That makes sense as the podman VM was not running. Let’s check the VM: >podman machine list NAME         ...

Cleaner switch expressions with pattern matching in C#

Ever find yourself mapping multiple string values to the same result? Being a C# developer for a long time, I sometimes forget that the C# has evolved so I still dare to chain case labels or reach for a dictionary. Of course with pattern matching this is no longer necessary. With pattern matching, you can express things inline, declaratively, and with zero repetition. A small example I was working on a small script that should invoke different actions depending on the environment. As our developers were using different variations for the same environment e.g.  "tst" alongside "test" , "prd" alongside "prod" .  We asked to streamline this a long time ago, but as these things happen, we still see variations in the wild. This brought me to the following code that is a perfect example for pattern matching: The or keyword here is a logical pattern combinator , not a boolean operator. It matches if either of the specified pattern...