A tiny ASP.NET Core Web API that shows how to wire up three agents with the Microsoft Agent Framework and run them as a sequential workflow. You send a topic, the agents pass their work down the line, and you get a polished article back.
The whole sample is one Program.cs file. The goal is to make multi-agent
orchestration feel as ordinary as wiring up DI for a controller.
POST /api/content { "topic": "..." }
│
▼
┌────────────┐ ┌──────────┐ ┌────────┐
│ Researcher │ → │ Writer │ → │ Editor │ → polished article
└────────────┘ └──────────┘ └────────┘
Three small agents, each with a different role:
| Agent | Instructions (short version) |
|---|---|
| Researcher | List 5 concise factual bullet points on the topic. |
| Writer | Turn the bullets into a short ~200-word article. |
| Editor | Tighten grammar, sharpen the opening, return final. |
The framework chains them so the output of one becomes the input of the next — you don't manage any of that plumbing yourself.
- .NET 10 SDK (or newer)
- An OpenAI API key — get one at https://platform.openai.com/api-keys
- Optional: an HTTP client (curl, the bundled
ContentPipelineApi.httpfile, or Postman)
The sample uses OpenAI for simplicity. The framework works just as well with Azure OpenAI, GitHub Models, Anthropic, or any other
IChatClientprovider — see the Swap the model provider section below.
git clone https://github.com/ludmal/microsoft-agent-framework-sample.git
cd microsoft-agent-framework-samplePick one of the following.
Option A — environment variable (recommended for local runs):
# PowerShell
$env:OPENAI_API_KEY = "sk-..."# bash / zsh
export OPENAI_API_KEY="sk-..."Option B — .NET user-secrets (keeps the key out of source):
cd ContentPipelineApi
dotnet user-secrets init
dotnet user-secrets set "OpenAI:ApiKey" "sk-..."Option C — appsettings.Development.json:
{
"OpenAI": {
"ApiKey": "sk-...",
"Model": "gpt-4o-mini"
}
}Default model is
gpt-4o-mini. Override withOpenAI:Modelif you want a different one (e.g.gpt-4o).
cd ContentPipelineApi
dotnet runYou should see something like:
Now listening on: http://localhost:5027
Application started. Press Ctrl+C to shut down.
Using curl:
curl -X POST http://localhost:5027/api/content `
-H "Content-Type: application/json" `
-d '{ "topic": "Why .NET is a great choice for AI apps" }'Or just open ContentPipelineApi.http in VS Code / Visual Studio / Rider and
click Send Request.
{
"topic": "Why .NET is a great choice for AI apps",
"article": "Final polished article here...",
"steps": [
{ "agent": "Researcher", "output": "- Point 1\n- Point 2 ..." },
{ "agent": "Writer", "output": "Draft article ..." },
{ "agent": "Editor", "output": "Final polished article ..." }
]
}article is what the editor produced. steps shows every agent's contribution
so you can see how the work flowed through the pipeline.
The whole orchestration lives in Program.cs. Here's the meaningful bit:
var researcher = new ChatClientAgent(chat, instructions: "...", name: "Researcher");
var writer = new ChatClientAgent(chat, instructions: "...", name: "Writer");
var editor = new ChatClientAgent(chat, instructions: "...", name: "Editor");
Workflow pipeline = AgentWorkflowBuilder.BuildSequential(researcher, writer, editor);That's it. BuildSequential is the orchestration. To execute, we feed an
initial user message in and read the final messages out:
await using var run = await InProcessExecution.RunStreamingAsync(pipeline, messages);
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
await foreach (var evt in run.WatchStreamAsync())
{
if (evt is WorkflowOutputEvent output)
{
finalMessages = output.As<List<ChatMessage>>() ?? [];
break;
}
}No queues, no message bus, no hand-rolled prompt chaining. The framework owns the wiring; you own the agent roles.
- Add a fourth agent. Drop a
FactCheckerbetween Writer and Editor:AgentWorkflowBuilder.BuildSequential(researcher, factChecker, writer, editor);
- Run agents in parallel. Swap
BuildSequentialforBuildConcurrentand the framework fans the same input out to every agent. - Add tools. Each
ChatClientAgentcan take function tools (e.g. a web search or a DB lookup) — the agent will call them automatically when it needs data.
The sample uses OpenAI but agents only depend on IChatClient. To target Azure
OpenAI, replace the DI registration:
builder.Services.AddSingleton<IChatClient>(_ =>
new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetChatClient(deploymentName)
.AsIChatClient());Everything else stays the same.
microsoft-agent-framework-sample/
├── ContentPipelineApi/
│ ├── Program.cs ← all the agent + endpoint code
│ ├── ContentPipelineApi.csproj
│ ├── ContentPipelineApi.http ← example requests
│ ├── appsettings.json
│ └── appsettings.Development.json
├── BLOG.md ← longer write-up of the framework
├── MicrosoftAgentFrameworkSample.slnx
└── README.md
- Microsoft Agent Framework — https://github.com/microsoft/agent-framework
Microsoft.Agents.AIon NuGet — https://www.nuget.org/packages/Microsoft.Agents.AI- .NET Blog announcement — https://devblogs.microsoft.com/dotnet/introducing-microsoft-agent-framework-preview/
- Pinned to
Microsoft.Agents.AI1.6.1 andMicrosoft.Agents.AI.Workflows1.6.1 (the latest stable at time of writing). The orchestration API on preview/older versions differs slightly. - Built and tested against .NET 10 on Windows. Should run on .NET 8 with
minor adjustments to the
TargetFrameworkin the csproj. - The Researcher → Writer → Editor split is illustrative — I chose it because it's the smallest example that makes "agents talking to agents" obvious. The same pattern fits research / triage / customer-support / data pipelines.
- I have not verified runtime behaviour end-to-end against a live OpenAI key in this environment — the project builds cleanly and follows the patterns from the official samples, but please try a real call before sharing the sample.