Quickstart: Microsoft Agent Framework Multi-Agent Communication
Build two agents using Microsoft Agent Framework that communicate through Dapr pub/sub. One agent performs code review and publishes findings, the other subscribes and generates fix suggestions. This pattern enables decoupled, scalable multi-agent architectures.
What You'll Build
- Reviewer Agent: Analyzes code and publishes review findings to a topic
- Fixer Agent: Subscribes to findings and generates code fix suggestions
Both agents run as separate ASP.NET Core services, communicating asynchronously through Dapr's pub/sub building block.
Why Async Agent Communication?
Asynchronous pub/sub messaging makes your multi-agent systems production-ready:
- Resiliency — If an agent crashes or restarts, messages wait in the broker until it recovers. No lost work.
- Independent scaling — Scale reviewer agents and fixer agents separately based on load.
- Loose coupling — Agents only know about topics, not each other. Add, remove, or replace agents without breaking the system.
- Guaranteed delivery — The message broker handles retries, dead-letter queues, and at-least-once delivery.
Bring Your Own Broker
This quickstart uses Redis, but Dapr supports 40+ pub/sub brokers including Kafka, RabbitMQ, AWS SNS/SQS, Azure Service Bus, and GCP Pub/Sub. Switch brokers by changing a YAML file — your agent code stays the same.
Prerequisites
Before you begin, ensure you have:
- Dapr CLI installed and initialized (
dapr init) - .NET 8 SDK
- An OpenAI API key
Setup
Create Project Structure
Create a new solution with two projects:
mkdir microsoft-agents-pubsub && cd microsoft-agents-pubsub
mkdir -p components
dotnet new sln -n AgentsPubSub
dotnet new webapi -n ReviewerAgent -o src/ReviewerAgent
dotnet new webapi -n FixerAgent -o src/FixerAgent
dotnet sln add src/ReviewerAgent
dotnet sln add src/FixerAgent
Your project will have this structure:
microsoft-agents-pubsub/
├── components/
│ └── pubsub.yaml
├── src/
│ ├── ReviewerAgent/
│ └── FixerAgent/
└── AgentsPubSub.sln
Configure Dapr Pub/Sub Component
Create components/pubsub.yaml:
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: pubsub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
This uses Redis as the message broker. Dapr initializes Redis automatically with dapr init.
Install Dependencies
Add the required packages to both projects:
cd src/ReviewerAgent
dotnet add package Microsoft.Agents.AI --prerelease
dotnet add package Dapr.AspNetCore
cd ../FixerAgent
dotnet add package Microsoft.Agents.AI --prerelease
dotnet add package Dapr.AspNetCore
Set Your OpenAI API Key
export OPENAI_API_KEY="your-api-key-here"
How It Works
┌─────────────────┐ Dapr Pub/Sub ┌─────────────────┐
│ Reviewer Agent │ ───────────────────▶ │ Fixer Agent │
│ (MS Agent │ "code-review-findings" │ (MS Agent │
│ Framework) │ │ Framework) │
└─────────────────┘ └─────────────────┘
│ │
▼ ▼
Reviews code Subscribes to topic
Publishes findings Receives findings
Generates fixes
- Reviewer Agent receives code via HTTP, uses Microsoft Agent Framework to analyze it, and publishes review findings
- Dapr handles message routing through the configured pub/sub component (Redis)
- Fixer Agent subscribes to the topic, receives findings, and uses Microsoft Agent Framework to generate fix suggestions
Build the Agents
Create the Reviewer Agent
Replace src/ReviewerAgent/Program.cs:
using Dapr.Client;
using Microsoft.Agents.AI;
using OpenAI;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDaprClient();
var app = builder.Build();
// Create the Microsoft Agent Framework reviewer
var openAiClient = new OpenAIClient(Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
var reviewer = openAiClient
.GetOpenAIResponseClient("gpt-4o-mini")
.CreateAIAgent(
name: "CodeReviewer",
instructions: """
You are an expert code reviewer.
Analyze code for bugs, security issues, and best practices.
Provide findings as a JSON array with 'issue' and 'severity' fields.
Severity should be: 'critical', 'warning', or 'suggestion'.
"""
);
app.MapPost("/review", async (ReviewRequest request, DaprClient daprClient) =>
{
var code = request.Code ?? "print('hello world')";
// Run the agent to review the code
var result = await reviewer.RunAsync(
$"Review this code and provide findings:\n\n```\n{code}\n```"
);
var findings = result.ToString();
// Publish findings via Dapr pub/sub
await daprClient.PublishEventAsync(
"pubsub",
"code-review-findings",
new CodeReviewFindings(code, findings)
);
Console.WriteLine("Published code review findings");
return Results.Ok(new
{
Status = "published",
Message = "Review findings published to fixer agent"
});
});
app.MapGet("/health", () => Results.Ok(new { Status = "healthy" }));
app.Run();
record ReviewRequest(string? Code);
record CodeReviewFindings(string Code, string Findings);
Create the Fixer Agent
Replace src/FixerAgent/Program.cs:
using Dapr;
using Microsoft.Agents.AI;
using OpenAI;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDaprClient();
var app = builder.Build();
// Store generated fixes
var fixes = new List<FixResult>();
// Create the Microsoft Agent Framework fixer
var openAiClient = new OpenAIClient(Environment.GetEnvironmentVariable("OPENAI_API_KEY"));
var fixer = openAiClient
.GetOpenAIResponseClient("gpt-4o-mini")
.CreateAIAgent(
name: "CodeFixer",
instructions: """
You are an expert software engineer.
Given code and review findings, provide specific fixes.
Show the corrected code and explain each change.
"""
);
// Subscribe to code review findings
app.MapPost("/receive-findings", [Topic("pubsub", "code-review-findings")] async (CodeReviewFindings findings) =>
{
Console.WriteLine(new string('=', 50));
Console.WriteLine("Received code review findings");
Console.WriteLine(new string('=', 50));
// Run the agent to generate fixes
var result = await fixer.RunAsync(
$"""
Fix the following code based on review findings:
Original code:
```
{findings.Code}
```
Review findings:
{findings.Findings}
Provide the fixed code and explain your changes.
"""
);
var fix = new FixResult(
findings.Code,
findings.Findings,
result.ToString()
);
fixes.Add(fix);
Console.WriteLine(new string('=', 50));
Console.WriteLine("Generated fix suggestions");
Console.WriteLine(new string('=', 50));
return Results.Ok();
});
app.MapGet("/fixes", () => Results.Ok(fixes));
app.MapGet("/health", () => Results.Ok(new { Status = "healthy" }));
app.Run();
record CodeReviewFindings(string Code, string Findings);
record FixResult(string OriginalCode, string Findings, string SuggestedFixes);
Configure the Ports
Update src/ReviewerAgent/Properties/launchSettings.json:
{
"profiles": {
"http": {
"commandName": "Project",
"applicationUrl": "http://localhost:5001"
}
}
}
Update src/FixerAgent/Properties/launchSettings.json:
{
"profiles": {
"http": {
"commandName": "Project",
"applicationUrl": "http://localhost:5002"
}
}
}
Run the Agents
Start the Fixer Agent
Open a terminal and run:
dapr run --app-id fixer-agent \
--app-port 5002 \
--dapr-http-port 3501 \
--resources-path ./components \
-- dotnet run --project src/FixerAgent
This agent subscribes to the code-review-findings topic and waits for messages.
Start the Reviewer Agent
Open a second terminal and run:
dapr run --app-id reviewer-agent \
--app-port 5001 \
--dapr-http-port 3500 \
--resources-path ./components \
-- dotnet run --project src/ReviewerAgent
Trigger Code Review
Open a third terminal and send code for review:
curl -X POST http://localhost:5001/review \
-H "Content-Type: application/json" \
-d '{
"code": "public string GetUser(string id) {\n var query = \"SELECT * FROM users WHERE id = \" + id;\n return db.Execute(query);\n}"
}'
Watch the terminal outputs:
- Reviewer Agent: Analyzes the code and publishes findings (should catch the SQL injection!)
- Fixer Agent: Receives findings via pub/sub and generates fix suggestions
View Generated Fixes
Retrieve all fixes from the Fixer Agent:
curl http://localhost:5002/fixes
Key Concepts
| Concept | Description |
|---|---|
| Pub/Sub Decoupling | Agents don't need to know each other's addresses. They communicate through topics. |
| Async Communication | Reviewer agent publishes and returns immediately. Fixer agent processes asynchronously. |
| Scalability | Multiple fixer agents can subscribe to the same topic for parallel processing. |
| Reliability | Dapr handles message delivery, retries, and dead-letter queues. |
Next Steps
- Add more agents for a complete code review pipeline (security scanner, performance analyzer)
- Use Dapr state management to persist agent memory
- Deploy to Kubernetes with Catalyst
- Explore Dapr workflows for orchestrated agent coordination
Clean Up
Stop both Dapr applications:
dapr stop --app-id reviewer-agent
dapr stop --app-id fixer-agent