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:
- 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.
- 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
- Create an AI Agent with Tools: build the agents that feed into orchestrators.
- Choose the Right Planning Strategy for Your Agent: give agents reasoning capabilities.
- Monitor Agent Execution with Tracing and Custom Metrics: observe orchestration runs.