DEV Community

Cover image for Multi-Agent Systems: Orchestrating Azure AI Agents with Semantic Kernel
Brian Spann
Brian Spann

Posted on

Multi-Agent Systems: Orchestrating Azure AI Agents with Semantic Kernel

Multi-Agent Systems: Orchestrating Azure AI Agents with Semantic Kernel

A single agent can do a lot. But some tasks are too complex, too nuanced, or require too many different skills for one agent to handle well. That's when you need a team.

In this article, we'll explore multi-agent orchestration—how to build systems where specialized agents collaborate to accomplish what no single agent could do alone. We'll use Semantic Kernel as our orchestration layer, combining it with Azure AI Agent Service to create powerful agentic workflows.

Why Multi-Agent?

Before diving into code, let's understand when multi-agent systems make sense:

The Case for Specialization

Imagine building a content creation system. A single agent doing everything would need to:

  • Research topics thoroughly
  • Write engaging content
  • Edit for grammar and style
  • Fact-check claims
  • Optimize for SEO

That's a lot to pack into one system prompt. The instructions become bloated, conflicting, and hard to tune. Worse, the model context gets cluttered with information that's irrelevant to the current subtask.

Multi-agent systems solve this through division of labor:

┌────────────────────────────────────────────────────────────────┐
│                    Content Creation Pipeline                   │
│                                                                │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐  │
│  │ Research │───▶│  Writer  │───▶│  Editor  │───▶│   SEO    │  │
│  │  Agent   │    │  Agent   │    │  Agent   │    │  Agent   │  │
│  └──────────┘    └──────────┘    └──────────┘    └──────────┘  │
│       │               │               │               │        │
│   Focused on      Focused on      Focused on      Focused on   │
│   gathering       compelling      clarity and     keywords     │
│   accurate        narrative       correctness     and reach    │
│   information                                                  │
└────────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Benefits of Multi-Agent Architectures

  1. Specialization — Each agent does one thing well
  2. Separation of Concerns — Different system prompts, tools, and models
  3. Scalability — Add new agents without rewriting existing ones
  4. Debuggability — Trace issues to specific agents
  5. Flexibility — Swap agents, run in parallel, or reconfigure workflows

Semantic Kernel: The Orchestration Layer

Semantic Kernel is Microsoft's open-source SDK for building AI applications. While Azure AI Agent Service manages individual agents (threads, runs, tools), Semantic Kernel sits above it to coordinate multiple agents working together.

Why Semantic Kernel?

  • Agent Abstractions — Works with Azure AI Agents, OpenAI, and custom implementations
  • Chat Patterns — Built-in support for multi-agent conversations
  • Selection Strategies — Control which agent speaks when
  • Termination Strategies — Know when the task is complete
  • Plugin System — Share capabilities across agents

Installing Semantic Kernel

dotnet add package Microsoft.SemanticKernel --version 1.25.0
dotnet add package Microsoft.SemanticKernel.Agents.Core --version 1.25.0-alpha
dotnet add package Microsoft.SemanticKernel.Agents.AzureAI --version 1.25.0-alpha
Enter fullscreen mode Exit fullscreen mode

Note: Agent packages are in preview. Check NuGet for latest versions.


AgentGroupChat: Multi-Agent Conversations

The core abstraction for multi-agent orchestration is AgentGroupChat. It manages a conversation where multiple agents take turns responding.

Basic Setup

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.Agents.Chat;
using Azure.AI.Projects;
using Azure.Identity;

// Connect to Azure AI Foundry
var projectClient = new AIProjectClient(
    new Uri(Environment.GetEnvironmentVariable("AZURE_AI_FOUNDRY_PROJECT_ENDPOINT")!),
    new DefaultAzureCredential()
);

// Create the underlying Azure AI agents
var researcherDef = await projectClient.CreateAgentAsync(new CreateAgentOptions(
    model: "gpt-4o",
    name: "Researcher",
    instructions: """
        You are a thorough research specialist. Your job is to:
        - Gather comprehensive information on topics
        - Find relevant facts, statistics, and examples
        - Identify key points and supporting evidence
        - Present findings in a clear, organized manner

        Focus on accuracy and completeness. Cite sources when possible.
        When you've gathered sufficient information, summarize your findings.
        """
));

var writerDef = await projectClient.CreateAgentAsync(new CreateAgentOptions(
    model: "gpt-4o",
    name: "Writer",
    instructions: """
        You are a skilled content writer. Your job is to:
        - Take research and transform it into engaging content
        - Write with clarity, flow, and reader engagement
        - Structure content logically with compelling hooks
        - Adapt tone and style to the target audience

        Write complete, polished drafts. Don't leave placeholders.
        """
));

var editorDef = await projectClient.CreateAgentAsync(new CreateAgentOptions(
    model: "gpt-4o",
    name: "Editor",
    instructions: """
        You are a meticulous editor. Your job is to:
        - Review content for grammar, spelling, and punctuation
        - Improve clarity and readability
        - Ensure consistent tone and style
        - Fact-check claims against the research
        - Provide constructive feedback or final approval

        Be specific about changes. When content is ready, say "APPROVED".
        """
));

// Wrap as Semantic Kernel agents
var researcher = new AzureAIAgent(researcherDef.Value, projectClient);
var writer = new AzureAIAgent(writerDef.Value, projectClient);
var editor = new AzureAIAgent(editorDef.Value, projectClient);
Enter fullscreen mode Exit fullscreen mode

Creating the Group Chat

// Create a group chat with all agents
var chat = new AgentGroupChat(researcher, writer, editor)
{
    ExecutionSettings = new AgentGroupChatSettings
    {
        // Agents take turns in order: researcher -> writer -> editor -> researcher...
        SelectionStrategy = new SequentialSelectionStrategy(),

        // Stop after 6 turns or when editor approves
        TerminationStrategy = new AggregateTerminationStrategy(
            new MaxTurnsTerminationStrategy(6),
            new KeywordTerminationStrategy("APPROVED")
        )
    }
};

// Add the initial task
chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, 
    "Create a blog post about the benefits of AI agents in customer service. " +
    "Target audience: business decision-makers. Length: 500-800 words."));

// Run the multi-agent workflow
Console.WriteLine("Starting multi-agent content creation...\n");

await foreach (var message in chat.InvokeAsync())
{
    Console.WriteLine($"[{message.AuthorName}]");
    Console.WriteLine(message.Content);
    Console.WriteLine(new string('-', 60));
}

Console.WriteLine("\n✅ Content creation complete!");
Enter fullscreen mode Exit fullscreen mode

Selection Strategies: Who Speaks Next?

The selection strategy determines which agent responds at each turn. Semantic Kernel provides several built-in options.

Sequential Selection

Agents take turns in a fixed order:

new SequentialSelectionStrategy()
Enter fullscreen mode Exit fullscreen mode

Good for: Pipelines with defined stages (research → write → edit).

Round Robin Selection

Similar to sequential, but cycles continuously:

new RoundRobinSelectionStrategy()
Enter fullscreen mode Exit fullscreen mode

Kernel Function Selection

Let an AI decide which agent should respond next:

var kernel = Kernel.CreateBuilder()
    .AddAzureOpenAIChatCompletion(
        deploymentName: "gpt-4o",
        endpoint: Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!,
        new DefaultAzureCredential())
    .Build();

var selectionFunction = KernelFunctionFactory.CreateFromPrompt(
    """
    Examine the conversation and decide which agent should respond next.

    Agents available:
    - Researcher: Gathers information and facts
    - Writer: Creates content from research
    - Editor: Reviews and approves content

    Rules:
    - If no research exists yet, select Researcher
    - If research exists but no draft, select Writer
    - If a draft exists, select Editor
    - If Editor requests changes, select Writer
    - If Editor approves, the task is complete

    Respond with ONLY the agent name.
    """);

new KernelFunctionSelectionStrategy(selectionFunction, kernel)
{
    ResultParser = result => result.GetValue<string>()?.Trim() ?? "Researcher"
}
Enter fullscreen mode Exit fullscreen mode

Custom Selection Strategy

For complex logic, implement your own:

public class WorkflowSelectionStrategy : SelectionStrategy
{
    private readonly Dictionary<string, string> _transitions = new()
    {
        ["User"] = "Researcher",
        ["Researcher"] = "Writer",
        ["Writer"] = "Editor",
        ["Editor"] = "Writer"  // Editor can request revisions
    };

    public override ValueTask<Agent> SelectAgentAsync(
        IReadOnlyList<Agent> agents,
        IReadOnlyList<ChatMessageContent> history,
        CancellationToken cancellationToken = default)
    {
        var lastMessage = history.LastOrDefault();
        var lastSpeaker = lastMessage?.AuthorName ?? "User";

        // Check for approval
        if (lastSpeaker == "Editor" && 
            lastMessage?.Content?.Contains("APPROVED") == true)
        {
            return ValueTask.FromResult<Agent>(null!); // Signal completion
        }

        // Get next agent based on workflow
        var nextAgentName = _transitions.GetValueOrDefault(lastSpeaker, "Researcher");
        var nextAgent = agents.First(a => a.Name == nextAgentName);

        return ValueTask.FromResult(nextAgent);
    }
}
Enter fullscreen mode Exit fullscreen mode

Termination Strategies: Knowing When to Stop

Without proper termination, agents could chat forever. Termination strategies define when the conversation is complete.

Max Turns

Stop after a fixed number of agent responses:

new MaxTurnsTerminationStrategy(10)
Enter fullscreen mode Exit fullscreen mode

Keyword Detection

Stop when an agent says a specific phrase:

new KeywordTerminationStrategy("APPROVED", "TASK_COMPLETE", "DONE")
{
    // Only trigger when specific agents use the keyword
    Agents = new[] { editor }
}
Enter fullscreen mode Exit fullscreen mode

Kernel Function Termination

Let an AI decide when the task is complete:

var terminationFunction = KernelFunctionFactory.CreateFromPrompt(
    """
    Review the conversation and determine if the task is complete.

    The task is complete when:
    - The Editor has explicitly approved the content
    - OR a final polished piece has been delivered

    Respond with ONLY "true" or "false".
    """);

new KernelFunctionTerminationStrategy(terminationFunction, kernel)
{
    ResultParser = result => result.GetValue<string>()?.Trim().ToLower() == "true"
}
Enter fullscreen mode Exit fullscreen mode

Aggregate Strategies

Combine multiple strategies with AND/OR logic:

// Stop when ANY condition is met
new AggregateTerminationStrategy(
    AggregateTerminationStrategy.AggregationMode.Any,
    new MaxTurnsTerminationStrategy(10),
    new KeywordTerminationStrategy("APPROVED")
)

// Stop when ALL conditions are met (less common)
new AggregateTerminationStrategy(
    AggregateTerminationStrategy.AggregationMode.All,
    new KeywordTerminationStrategy("FACT_CHECKED"),
    new KeywordTerminationStrategy("APPROVED")
)
Enter fullscreen mode Exit fullscreen mode

Complete Example: Research → Write → Edit Pipeline

Here's a production-ready multi-agent content creation system:

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.AzureAI;
using Microsoft.SemanticKernel.Agents.Chat;
using Azure.AI.Projects;
using Azure.Identity;

namespace MultiAgentDemo;

public class ContentCreationPipeline
{
    private readonly AIProjectClient _projectClient;
    private readonly List<Agent> _agents = new();
    private readonly List<string> _agentIds = new();

    public ContentCreationPipeline()
    {
        _projectClient = new AIProjectClient(
            new Uri(Environment.GetEnvironmentVariable("AZURE_AI_FOUNDRY_PROJECT_ENDPOINT")!),
            new DefaultAzureCredential()
        );
    }

    public async Task InitializeAsync()
    {
        Console.WriteLine("Initializing agents...");

        // Research Agent - with Bing search tool
        var researcherDef = await _projectClient.CreateAgentAsync(new CreateAgentOptions(
            model: "gpt-4o",
            name: "Researcher",
            instructions: """
                You are a research specialist. When given a topic:

                1. Identify key aspects that need to be covered
                2. Gather relevant facts, statistics, and examples
                3. Find compelling angles and insights
                4. Organize findings in a clear structure

                Present your research as a structured brief that a writer can use.
                Include specific data points and examples.

                Format your output as:
                ## Research Brief: [Topic]
                ### Key Points
                - ...
                ### Statistics & Data
                - ...
                ### Examples & Case Studies
                - ...
                ### Suggested Angles
                - ...
                """
        ));
        _agentIds.Add(researcherDef.Value.Id);
        _agents.Add(new AzureAIAgent(researcherDef.Value, _projectClient) { Name = "Researcher" });

        // Writer Agent
        var writerDef = await _projectClient.CreateAgentAsync(new CreateAgentOptions(
            model: "gpt-4o",
            name: "Writer",
            instructions: """
                You are an expert content writer. Given research, create compelling content:

                1. Start with a hook that grabs attention
                2. Structure content with clear sections
                3. Use conversational, engaging language
                4. Include specific examples and data from the research
                5. End with a clear call-to-action or conclusion

                Write complete draftsno placeholders or "insert here" notes.
                Match the requested length and audience.

                If the Editor requests changes, revise your draft accordingly.
                Acknowledge what you changed in your response.
                """
        ));
        _agentIds.Add(writerDef.Value.Id);
        _agents.Add(new AzureAIAgent(writerDef.Value, _projectClient) { Name = "Writer" });

        // Editor Agent
        var editorDef = await _projectClient.CreateAgentAsync(new CreateAgentOptions(
            model: "gpt-4o",
            name: "Editor",
            instructions: """
                You are a senior editor. Review content for:

                1. **Accuracy** - Do claims match the research?
                2. **Clarity** - Is the writing clear and readable?
                3. **Engagement** - Does it hold attention?
                4. **Structure** - Is it well-organized?
                5. **Grammar** - Any errors or awkward phrasing?

                If changes are needed:
                - Be specific about what to fix
                - Explain why the change improves the content
                - Request a revision from the Writer

                If the content is ready for publication:
                - Summarize its strengths
                - End your response with exactly: APPROVED

                You must either request changes OR approve. Don't just comment.
                """
        ));
        _agentIds.Add(editorDef.Value.Id);
        _agents.Add(new AzureAIAgent(editorDef.Value, _projectClient) { Name = "Editor" });

        Console.WriteLine($"✅ Created {_agents.Count} agents");
    }

    public async Task<string> CreateContentAsync(string topic, string audience, int wordCount)
    {
        var chat = new AgentGroupChat(_agents.ToArray())
        {
            ExecutionSettings = new AgentGroupChatSettings
            {
                SelectionStrategy = new SequentialSelectionStrategy(),
                TerminationStrategy = new AggregateTerminationStrategy(
                    new MaxTurnsTerminationStrategy(8),
                    new ApprovalTerminationStrategy()
                )
            }
        };

        // Add the initial request
        chat.AddChatMessage(new ChatMessageContent(
            AuthorRole.User,
            $"Create content about: {topic}\n" +
            $"Target audience: {audience}\n" +
            $"Target length: {wordCount} words\n\n" +
            "Researcher: Start by gathering relevant information."
        ));

        var finalContent = new StringBuilder();
        var messageLog = new List<(string Agent, string Content)>();

        await foreach (var message in chat.InvokeAsync())
        {
            Console.WriteLine($"\n[{message.AuthorName}]:");

            var content = message.Content ?? "";

            // Truncate display for long content
            if (content.Length > 500)
            {
                Console.WriteLine(content[..500] + "...");
            }
            else
            {
                Console.WriteLine(content);
            }

            messageLog.Add((message.AuthorName ?? "Unknown", content));

            // Track the latest Writer output as the final content
            if (message.AuthorName == "Writer")
            {
                finalContent.Clear();
                finalContent.Append(content);
            }
        }

        Console.WriteLine($"\n✅ Pipeline complete! {messageLog.Count} messages exchanged.");

        return finalContent.ToString();
    }

    public async Task CleanupAsync()
    {
        foreach (var agentId in _agentIds)
        {
            await _projectClient.DeleteAgentAsync(agentId);
        }
        Console.WriteLine("🧹 Agents cleaned up");
    }
}

// Custom termination strategy for approval
public class ApprovalTerminationStrategy : TerminationStrategy
{
    public override ValueTask<bool> ShouldTerminateAsync(
        Agent agent,
        IReadOnlyList<ChatMessageContent> history,
        CancellationToken cancellationToken = default)
    {
        var lastMessage = history.LastOrDefault();

        // Only terminate if Editor approves
        if (lastMessage?.AuthorName == "Editor" &&
            lastMessage.Content?.Contains("APPROVED", StringComparison.OrdinalIgnoreCase) == true)
        {
            return ValueTask.FromResult(true);
        }

        return ValueTask.FromResult(false);
    }
}

// Usage
class Program
{
    static async Task Main()
    {
        var pipeline = new ContentCreationPipeline();

        try
        {
            await pipeline.InitializeAsync();

            var content = await pipeline.CreateContentAsync(
                topic: "How AI agents are transforming customer service",
                audience: "Business executives and IT leaders",
                wordCount: 600
            );

            Console.WriteLine("\n" + new string('=', 60));
            Console.WriteLine("FINAL CONTENT:");
            Console.WriteLine(new string('=', 60));
            Console.WriteLine(content);
        }
        finally
        {
            await pipeline.CleanupAsync();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Advanced Patterns

Pattern 1: Parallel Execution

Some tasks can be parallelized. Run multiple agents simultaneously:

public async Task<CombinedAnalysis> AnalyzeParallelAsync(string document)
{
    // Create specialized analysis agents
    var sentimentAgent = CreateAgent("SentimentAnalyzer", "Analyze emotional tone...");
    var keywordAgent = CreateAgent("KeywordExtractor", "Extract key topics...");
    var summaryAgent = CreateAgent("Summarizer", "Create a concise summary...");

    // Run analyses in parallel
    var sentimentTask = RunAgentAsync(sentimentAgent, document);
    var keywordTask = RunAgentAsync(keywordAgent, document);
    var summaryTask = RunAgentAsync(summaryAgent, document);

    await Task.WhenAll(sentimentTask, keywordTask, summaryTask);

    return new CombinedAnalysis
    {
        Sentiment = sentimentTask.Result,
        Keywords = keywordTask.Result,
        Summary = summaryTask.Result
    };
}

private async Task<string> RunAgentAsync(Agent agent, string input)
{
    var chat = new AgentGroupChat(agent)
    {
        ExecutionSettings = new() { TerminationStrategy = new MaxTurnsTerminationStrategy(1) }
    };

    chat.AddChatMessage(new ChatMessageContent(AuthorRole.User, input));

    var result = new StringBuilder();
    await foreach (var message in chat.InvokeAsync())
    {
        result.Append(message.Content);
    }

    return result.ToString();
}
Enter fullscreen mode Exit fullscreen mode

Pattern 2: Supervisor Agent

A supervisor agent that delegates to specialists:

var supervisor = await projectClient.CreateAgentAsync(new CreateAgentOptions(
    model: "gpt-4o",
    name: "Supervisor",
    instructions: """
        You are a project supervisor coordinating a team of specialists.

        Your team:
        - DataAnalyst: For analyzing data and creating visualizations
        - Writer: For creating written content
        - Researcher: For gathering information
        - Coder: For writing and explaining code

        When given a task:
        1. Break it into subtasks
        2. Assign each subtask to the appropriate specialist
        3. Review their work and request revisions if needed
        4. Compile the final deliverable

        Use the format:
        @DataAnalyst: [task description]
        @Writer: [task description]
        etc.

        When all subtasks are complete, compile the final result and say COMPLETE.
        """
));

// Custom selection strategy that parses supervisor directives
public class SupervisorSelectionStrategy : SelectionStrategy
{
    public override ValueTask<Agent> SelectAgentAsync(
        IReadOnlyList<Agent> agents,
        IReadOnlyList<ChatMessageContent> history,
        CancellationToken cancellationToken = default)
    {
        var lastMessage = history.LastOrDefault();

        if (lastMessage?.AuthorName == "Supervisor")
        {
            // Parse the @AgentName directive
            var content = lastMessage.Content ?? "";
            var match = Regex.Match(content, @"@(\w+):");

            if (match.Success)
            {
                var targetName = match.Groups[1].Value;
                var target = agents.FirstOrDefault(a => a.Name == targetName);
                if (target != null) return ValueTask.FromResult(target);
            }
        }

        // Default back to supervisor
        return ValueTask.FromResult(agents.First(a => a.Name == "Supervisor"));
    }
}
Enter fullscreen mode Exit fullscreen mode

Pattern 3: Debate/Adversarial Pattern

Agents that challenge each other for better outcomes:

var proposer = CreateAgent("Proposer", """
    Propose solutions to problems. Be creative and bold.
    Support your proposals with reasoning.
    """);

var critic = CreateAgent("Critic", """
    Critically evaluate proposals. Find weaknesses and risks.
    Suggest improvements or alternatives.
    Be constructive but thorough.
    """);

var synthesizer = CreateAgent("Synthesizer", """
    After the Proposer and Critic have debated (at least 2 rounds each):
    - Combine the best ideas
    - Address the valid concerns raised
    - Present a refined, practical solution

    End with FINAL_SOLUTION when you have a complete answer.
    """);

var debateChat = new AgentGroupChat(proposer, critic, synthesizer)
{
    ExecutionSettings = new()
    {
        SelectionStrategy = new DebateSelectionStrategy(),
        TerminationStrategy = new KeywordTerminationStrategy("FINAL_SOLUTION")
    }
};
Enter fullscreen mode Exit fullscreen mode

Sharing Context Across Agents

Agents in a group chat share the conversation history, but sometimes you need to share structured data or resources.

Using Chat History Effectively

// Prefix messages with structured markers for easy parsing
chat.AddChatMessage(new ChatMessageContent(
    AuthorRole.User,
    """{
    [TASK]
    Create a technical blog post.

    [REQUIREMENTS]
    - Topic: Kubernetes best practices
    - Audience: DevOps engineers
    - Length: 1000 words
    - Include: Code examples, diagrams descriptions

    [CONSTRAINTS]
    - No vendor-specific tools
    - Focus on security
    }"""
));
Enter fullscreen mode Exit fullscreen mode

Shared Kernel for Common Functions

// Create a shared kernel with common plugins
var sharedKernel = Kernel.CreateBuilder()
    .AddAzureOpenAIChatCompletion("gpt-4o", endpoint, credential)
    .Build();

// Add shared plugins
sharedKernel.ImportPluginFromType<DateTimePlugin>();
sharedKernel.ImportPluginFromType<WebSearchPlugin>();
sharedKernel.ImportPluginFromType<DatabasePlugin>();

// All agents can access these plugins through the kernel
var researcher = new ChatCompletionAgent
{
    Name = "Researcher",
    Instructions = "...",
    Kernel = sharedKernel
};
Enter fullscreen mode Exit fullscreen mode

Error Handling and Recovery

Multi-agent systems can fail in complex ways. Build in resilience:

public async Task<string> RunWithRetryAsync(AgentGroupChat chat, int maxRetries = 3)
{
    for (int attempt = 1; attempt <= maxRetries; attempt++)
    {
        try
        {
            var result = new StringBuilder();

            await foreach (var message in chat.InvokeAsync())
            {
                result.AppendLine($"[{message.AuthorName}]: {message.Content}");
            }

            return result.ToString();
        }
        catch (Exception ex) when (attempt < maxRetries)
        {
            Console.WriteLine($"Attempt {attempt} failed: {ex.Message}. Retrying...");

            // Add a recovery message to the chat
            chat.AddChatMessage(new ChatMessageContent(
                AuthorRole.System,
                $"Previous attempt encountered an error: {ex.Message}. " +
                "Please continue from where we left off."
            ));

            await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt))); // Exponential backoff
        }
    }

    throw new Exception("Max retries exceeded");
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

1. Clear Agent Boundaries

Each agent should have a distinct, non-overlapping responsibility:

// ❌ Bad: Overlapping responsibilities
var agent1 = "You write and edit content...";
var agent2 = "You edit content and fact-check...";

// ✅ Good: Clear boundaries
var writer = "You write content. Never edit or fact-check.";
var editor = "You edit for grammar and style. Don't rewrite.";
var factChecker = "You verify claims. Don't edit or write.";
Enter fullscreen mode Exit fullscreen mode

2. Explicit Handoff Signals

Make it clear when an agent is done:

var instructions = """
    When you complete your task:
    - Summarize what you produced
    - Say "READY FOR [NextAgent]" to signal handoff

    If you need information from another agent:
    - Say "NEED FROM [AgentName]: [specific request]"
    """;
Enter fullscreen mode Exit fullscreen mode

3. Limit Conversation Depth

Prevent infinite loops with hard limits:

new AggregateTerminationStrategy(
    // Hard stop at 12 turns no matter what
    new MaxTurnsTerminationStrategy(12),

    // Normal termination on approval
    new KeywordTerminationStrategy("APPROVED")
)
Enter fullscreen mode Exit fullscreen mode

4. Log Everything

Multi-agent debugging is hard without logs:

await foreach (var message in chat.InvokeAsync())
{
    var logEntry = new
    {
        Timestamp = DateTime.UtcNow,
        Agent = message.AuthorName,
        Role = message.Role.ToString(),
        Content = message.Content,
        TokenCount = EstimateTokens(message.Content)
    };

    _logger.LogInformation("Agent message: {@LogEntry}", logEntry);

    // Also store for analysis
    await _telemetry.TrackAgentMessageAsync(logEntry);
}
Enter fullscreen mode Exit fullscreen mode

What's Next

We've built a multi-agent system, but we haven't tackled production concerns yet. In Part 4, we'll cover:

  • State Management — Persisting conversations across sessions
  • Session Patterns — Managing multiple concurrent users
  • Observability — OpenTelemetry tracing for agent execution
  • Cost Management — Token budgets and optimization

These patterns turn demos into production systems.


Summary

In this article, we learned:

  • Why Multi-Agent — Specialization, scalability, and separation of concerns
  • Semantic Kernel — The orchestration layer for agent coordination
  • AgentGroupChat — Managing multi-agent conversations
  • Selection Strategies — Controlling which agent speaks when
  • Termination Strategies — Knowing when the task is complete
  • Advanced Patterns — Parallel execution, supervisors, and debates

Your agents now work as a team. Let's make them production-ready.


Resources:

Next up: Part 4 — Production-Ready AI Agents: State Management, Sessions, and Telemetry

Top comments (0)