Table of Contents

Orchestrate Multi-Agent Workflows with Patterns

Single-agent systems hit a ceiling when tasks require multiple perspectives, sequential processing, or dynamic delegation. LM-Kit.NET provides three orchestration patterns that compose multiple agents into coordinated workflows: Pipeline (sequential stages), Parallel (concurrent execution with aggregation), and Supervisor (dynamic delegation by a coordinator). This tutorial implements all three patterns and shows when to use each.


Why Multi-Agent Orchestration Matters

Two real-world problems that multi-agent orchestration solves:

  1. Content pipelines that need sequential refinement. A blog post goes through research, writing, editing, and SEO optimization. Each stage requires a different system prompt and potentially a different model. A Pipeline orchestrator chains these stages, passing each output as input to the next.
  2. Analysis tasks that benefit from multiple perspectives. A code review that checks security, performance, and maintainability simultaneously. A Parallel orchestrator runs three specialized agents at once and aggregates their findings into a single report.

Prerequisites

Requirement Minimum
.NET SDK 8.0+
Chat model Any chat-capable model (e.g., qwen3:4b)
VRAM 4 GB+

Step 1: Create the Project

dotnet new console -n AgentOrchestration
cd AgentOrchestration
dotnet add package LM-Kit.NET

Step 2: Understand the Three Patterns

┌────────────────────────────────────────────────────────┐
│                  Orchestration Patterns                │
├───────────────┬──────────────────┬─────────────────────┤
│   Pipeline    │    Parallel      │    Supervisor       │
│               │                  │                     │
│  A ──► B ──► C│  ┌─ A ─┐         │    Supervisor       │
│               │  │     │         │    /    |    \      │
│  Sequential   │  ├─ B ─┤► Merge  │   W1   W2   W3      │
│  refinement   │  │     │         │                     │
│               │  └─ C ─┘         │  Dynamic routing    │
└───────────────┴──────────────────┴─────────────────────┘
Pattern Class Execution Best for
Pipeline PipelineOrchestrator Sequential (A then B then C) Content refinement, ETL, multi-step processing
Parallel ParallelOrchestrator Concurrent (A, B, C at once) Multi-perspective analysis, ensemble voting
Supervisor SupervisorOrchestrator Dynamic delegation Complex routing, adaptive workflows

Step 3: Build a Pipeline Orchestrator

A pipeline passes the output of each stage as input to the next:

using System.Text;
using LMKit.Agents;
using LMKit.Agents.Orchestration;
using LMKit.Model;

LMKit.Licensing.LicenseManager.SetLicenseKey("");

Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;

// ──────────────────────────────────────
// 1. Load the model
// ──────────────────────────────────────
using LM model = LM.LoadFromModelID("qwen3:4b",
    loadingProgress: p =>
    {
        Console.Write($"\r  Loading: {p * 100:F0}%   ");
        return true;
    });

Console.WriteLine($"\n  Model loaded: {model.ModelName}\n");

// ──────────────────────────────────────
// 2. Create specialized agents
// ──────────────────────────────────────
Agent researcher = Agent.CreateBuilder(model)
    .WithIdentity("Researcher",
        "You research topics and produce detailed factual notes. " +
        "Output bullet points with key facts and sources.")
    .Build();

Agent writer = Agent.CreateBuilder(model)
    .WithIdentity("Writer",
        "You write clear, engaging blog posts from research notes. " +
        "Transform bullet points into flowing prose. " +
        "Output a complete article with an introduction, body, and conclusion.")
    .Build();

Agent editor = Agent.CreateBuilder(model)
    .WithIdentity("Editor",
        "You edit articles for clarity, grammar, and style. " +
        "Fix errors, improve flow, and ensure a consistent tone. " +
        "Output the polished final version.")
    .Build();

// ──────────────────────────────────────
// 3. Build the pipeline
// ──────────────────────────────────────
var pipeline = new PipelineOrchestrator();
pipeline.AddStage("Research", researcher);
pipeline.AddStage("Write", writer);
pipeline.AddStage("Edit", editor);

Console.WriteLine($"Pipeline stages: {pipeline.Stages.Count}");
foreach (PipelineOrchestrator.PipelineStage stage in pipeline.Stages)
{
    Console.WriteLine($"  - {stage.Name}");
}

// ──────────────────────────────────────
// 4. Execute the pipeline
// ──────────────────────────────────────
Console.WriteLine("\nExecuting content pipeline...\n");

OrchestrationResult result = await pipeline.ExecuteAsync(
    "Write a blog post about the benefits of local LLM inference for enterprise applications.");

if (result.Success)
{
    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine("=== Pipeline Result ===\n");
    Console.ResetColor();
    Console.WriteLine(result.Content);

    Console.WriteLine($"\n--- Stats ---");
    Console.WriteLine($"Duration: {result.Duration.TotalSeconds:F1}s");
    Console.WriteLine($"Inference calls: {result.TotalInferenceCount}");
    Console.WriteLine($"Agent results: {result.AgentResults.Count}");
}
else
{
    Console.ForegroundColor = ConsoleColor.Red;
    Console.WriteLine($"Pipeline failed: {result.Error}");
    Console.ResetColor();
}

Step 4: Add Input Transformers to Pipeline Stages

Transform the output of one stage before passing it to the next:

var pipeline = new PipelineOrchestrator();

pipeline.AddStage(researcher);

// Transform research output before passing to the writer
pipeline.AddStage(writer, (previousOutput, context) =>
{
    return $"Based on the following research notes, write a 500-word article:\n\n{previousOutput}";
});

pipeline.AddStage(editor, (previousOutput, context) =>
{
    return $"Edit the following article for a professional audience. " +
           $"Keep it under 500 words:\n\n{previousOutput}";
});

Step 5: Build a Parallel Orchestrator

Run multiple agents simultaneously and merge their results:

// ──────────────────────────────────────
// 1. Create parallel review agents
// ──────────────────────────────────────
Agent securityReviewer = Agent.CreateBuilder(model)
    .WithIdentity("Security Reviewer",
        "You review code for security vulnerabilities. " +
        "Check for injection attacks, authentication flaws, " +
        "data exposure, and OWASP Top 10 issues. " +
        "Output a numbered list of findings with severity.")
    .Build();

Agent performanceReviewer = Agent.CreateBuilder(model)
    .WithIdentity("Performance Reviewer",
        "You review code for performance issues. " +
        "Check for N+1 queries, memory leaks, " +
        "unnecessary allocations, and algorithmic inefficiency. " +
        "Output a numbered list of findings with impact.")
    .Build();

Agent maintainabilityReviewer = Agent.CreateBuilder(model)
    .WithIdentity("Maintainability Reviewer",
        "You review code for maintainability. " +
        "Check for code duplication, poor naming, missing documentation, " +
        "tight coupling, and SOLID principle violations. " +
        "Output a numbered list of findings with suggestions.")
    .Build();

// ──────────────────────────────────────
// 2. Build the parallel orchestrator
// ──────────────────────────────────────
var parallel = new ParallelOrchestrator();
parallel.AddAgent("Security", securityReviewer);
parallel.AddAgent("Performance", performanceReviewer);
parallel.AddAgent("Maintainability", maintainabilityReviewer);

// Set max concurrency (default -1 means unlimited)
parallel.MaxDegreeOfParallelism = 3;

// ──────────────────────────────────────
// 3. Add a custom aggregator
// ──────────────────────────────────────
parallel.WithAggregator((agentResults, context) =>
{
    var sb = new StringBuilder();
    sb.AppendLine("# Code Review Report\n");

    foreach (AgentExecutionResult agentResult in agentResults)
    {
        sb.AppendLine($"## {agentResult.AgentName}");
        sb.AppendLine($"Status: {agentResult.Status}");
        sb.AppendLine();

        if (agentResult.IsSuccess)
        {
            sb.AppendLine(agentResult.Content);
        }
        else
        {
            sb.AppendLine($"*Review failed: {agentResult.Error?.Message}*");
        }

        sb.AppendLine();
    }

    return sb.ToString();
});

// ──────────────────────────────────────
// 4. Execute the parallel review
// ──────────────────────────────────────
string codeToReview = @"
public class UserController {
    public string GetUser(string id) {
        var query = ""SELECT * FROM users WHERE id = '"" + id + ""'"";
        var result = db.Execute(query);
        return result.ToString();
    }
}";

Console.WriteLine("Running parallel code review...\n");

OrchestrationResult reviewResult = await parallel.ExecuteAsync(
    $"Review the following code:\n```csharp\n{codeToReview}\n```");

if (reviewResult.Success)
{
    Console.WriteLine(reviewResult.Content);
    Console.WriteLine($"Duration: {reviewResult.Duration.TotalSeconds:F1}s");
    Console.WriteLine($"All {reviewResult.AgentResults.Count} reviews completed in parallel.");
}

Step 6: Build a Supervisor Orchestrator

A supervisor agent dynamically delegates tasks to worker agents:

// ──────────────────────────────────────
// 1. Create worker agents
// ──────────────────────────────────────
Agent translatorWorker = Agent.CreateBuilder(model)
    .WithIdentity("Translator",
        "You translate text between languages. " +
        "Preserve the original meaning and tone.")
    .Build();

Agent summarizerWorker = Agent.CreateBuilder(model)
    .WithIdentity("Summarizer",
        "You summarize long texts into concise summaries. " +
        "Capture all key points in 2-3 sentences.")
    .Build();

Agent classifierWorker = Agent.CreateBuilder(model)
    .WithIdentity("Classifier",
        "You classify text into categories. " +
        "Output the category and a confidence percentage.")
    .Build();

// ──────────────────────────────────────
// 2. Create the supervisor
// ──────────────────────────────────────
Agent supervisor = Agent.CreateBuilder(model)
    .WithIdentity("Task Router",
        "You analyze incoming requests and delegate them " +
        "to the most appropriate worker agent. " +
        "Choose the best worker based on the task type.")
    .Build();

// ──────────────────────────────────────
// 3. Build the supervisor orchestrator
// ──────────────────────────────────────
var supervisorOrch = new SupervisorOrchestrator(supervisor);
supervisorOrch.AddWorker("Translator", translatorWorker);
supervisorOrch.AddWorker("Summarizer", summarizerWorker);
supervisorOrch.AddWorker("Classifier", classifierWorker);

// ──────────────────────────────────────
// 4. Execute with orchestration options
// ──────────────────────────────────────
var options = new OrchestrationOptions
{
    MaxSteps = 5,
    Timeout = TimeSpan.FromMinutes(2),
    StopOnFailure = true,
    EnableTracing = true
};

Console.WriteLine("Sending task to supervisor...\n");

OrchestrationResult supervisorResult = await supervisorOrch.ExecuteAsync(
    "Translate the following text to French: 'The quarterly revenue exceeded expectations by 15%.'",
    options);

if (supervisorResult.Success)
{
    Console.WriteLine($"Result: {supervisorResult.Content}");
    Console.WriteLine($"\nDelegation chain:");

    foreach (AgentExecutionResult agentResult in supervisorResult.AgentResults)
    {
        Console.WriteLine($"  - {agentResult.AgentName}: {agentResult.Status} " +
                          $"({agentResult.Duration.TotalMilliseconds:F0}ms)");
    }
}

Step 7: Use Orchestration Events

Monitor agent execution within any orchestrator:

pipeline.BeforeAgentExecution += (sender, e) =>
{
    Console.ForegroundColor = ConsoleColor.Cyan;
    Console.WriteLine($"  >>> Starting: {e.AgentName}");
    Console.ResetColor();
};

pipeline.AfterAgentExecution += (sender, e) =>
{
    Console.ForegroundColor = ConsoleColor.Yellow;
    Console.WriteLine($"  <<< Finished: {e.AgentName} ({e.Duration.TotalMilliseconds:F0}ms)");
    Console.ResetColor();
};

Step 8: Run the Application

dotnet run

OrchestrationOptions Reference

Property Default Description
MaxSteps 10 Maximum number of agent execution steps
Timeout null Overall timeout for the orchestration
AgentTimeout null Per-agent timeout
StopOnFailure true Stop the entire workflow if one agent fails
EnableTracing false Enable tracing for observability
StreamToolCalls true Stream tool call results in real time

Pattern Selection Guide

Scenario Pattern Why
Content goes through research, writing, editing Pipeline Each step refines the previous output
Code review from multiple angles Parallel Independent analyses that combine into one report
Customer request routing to specialized agents Supervisor Request type determines which worker handles it
Translation then summarization Pipeline Order matters: translate first, then summarize
Sentiment + entity extraction on same text Parallel Independent analyses on the same input
Complex multi-skill assistant Supervisor Dynamic selection of the right skill per query

Common Issues

Problem Cause Fix
Pipeline stage receives wrong input Missing input transformer Add an input transformer to format the prompt for each stage
Parallel results are empty Agents failing silently Check agentResult.Status and agentResult.Error for each result
Supervisor delegates to wrong worker Unclear worker descriptions Make each worker's identity description distinctive and specific
Timeout exceeded Agent stuck in long generation Set AgentTimeout in OrchestrationOptions
StopOnFailure halts the pipeline One agent in the chain fails Set StopOnFailure = false to continue despite failures

Next Steps