Multi-Agent Systems with .NET 10 and Semantic Kernel Process Framework

By · · AI Engineering

With .NET 10 shipping in November 2025, C# developers picked up solid new tools for building production AI systems. Semantic Kernel's Process Framework gives .NET developers a native way to orchestrate multi-agent workflows where distinct AI agents collaborate through a structured, event-driven pipeline.

I will walk through the Process Framework's core concepts and build a document processing pipeline where multiple agents handle different stages of the work.

What is the Process Framework?

The Semantic Kernel Process Framework is an orchestration layer that lets you define processes composed of steps. Each step encapsulates a unit of work -- an AI agent call, a data transformation, or an external API interaction. Steps communicate through events, creating a directed graph of execution.

Think of it as a state machine for AI workflows. Unlike simple sequential chains, the Process Framework supports branching, looping, and conditional routing. That matters in real-world multi-agent scenarios where one agent's output determines what happens next.

Setting up the project

Create a new .NET 10 console application and add the required packages:

dotnet new console -n AgentPipeline --framework net10.0
cd AgentPipeline
dotnet add package Microsoft.SemanticKernel --version 1.34.0
dotnet add package Microsoft.SemanticKernel.Process.Core --version 1.34.0-alpha
dotnet add package Microsoft.SemanticKernel.Connectors.OpenAI --version 1.34.0

Defining the process steps

Let us build a pipeline that takes a raw document, extracts key information, generates a summary, and then performs a quality review. Each stage is handled by a separate agent, modeled as a process step.

First, define the events that flow between steps:

using Microsoft.SemanticKernel;

public static class ProcessEvents
{
    public const string DocumentReceived = nameof(DocumentReceived);
    public const string ExtractionComplete = nameof(ExtractionComplete);
    public const string SummaryComplete = nameof(SummaryComplete);
    public const string ReviewApproved = nameof(ReviewApproved);
    public const string ReviewRejected = nameof(ReviewRejected);
}

public record DocumentPayload(string Title, string Content);
public record ExtractionResult(string Title, List<string> KeyPoints, string Category);
public record SummaryResult(string Title, string Summary, string Category);
public record ReviewResult(string Title, string Summary, bool Approved, string Feedback);

Now define the extraction step. Each step is a class that inherits from KernelProcessStep and uses the [KernelFunction] attribute on methods that respond to events:

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Process;
using System.Text.Json;

public class ExtractionStep : KernelProcessStep
{
    [KernelFunction(ProcessEvents.DocumentReceived)]
    public async Task ExtractAsync(KernelProcessStepContext context, Kernel kernel, DocumentPayload document)
    {
        var prompt = 
quot;"" Analyze the following document and extract key information. Return a JSON object with: keyPoints (array of strings) and category (string). Document Title: {document.Title} Document Content: {document.Content} """; var chatService = kernel.GetRequiredService<IChatCompletionService>(); var result = await chatService.GetChatMessageContentAsync(prompt); var extraction = JsonSerializer.Deserialize<ExtractionData>(result.Content!); var extractionResult = new ExtractionResult( document.Title, extraction?.KeyPoints ?? [], extraction?.Category ?? "uncategorized" ); await context.EmitEventAsync(ProcessEvents.ExtractionComplete, extractionResult); } } record ExtractionData(List<string> KeyPoints, string Category);

The summarization step takes the extraction result and produces a polished summary:

public class SummarizationStep : KernelProcessStep
{
    [KernelFunction(ProcessEvents.ExtractionComplete)]
    public async Task SummarizeAsync(KernelProcessStepContext context, Kernel kernel, ExtractionResult extraction)
    {
        var keyPointsList = string.Join("\n- ", extraction.KeyPoints);

        var prompt = 
quot;"" Write a concise, professional summary based on these key points. The document is categorized as: {extraction.Category} Key Points: - {keyPointsList} Write 2-3 paragraphs. Be informative and precise. """; var chatService = kernel.GetRequiredService<IChatCompletionService>(); var result = await chatService.GetChatMessageContentAsync(prompt); var summaryResult = new SummaryResult(extraction.Title, result.Content!, extraction.Category); await context.EmitEventAsync(ProcessEvents.SummaryComplete, summaryResult); } }

The review step is where things get interesting. This agent evaluates the summary and can either approve it or reject it, sending it back for revision:

public class ReviewStep : KernelProcessStep
{
    private int _revisionCount;

    [KernelFunction(ProcessEvents.SummaryComplete)]
    public async Task ReviewAsync(KernelProcessStepContext context, Kernel kernel, SummaryResult summary)
    {
        var prompt = 
quot;"" You are a quality reviewer. Evaluate this summary for accuracy, clarity, and completeness. Title: {summary.Title} Category: {summary.Category} Summary: {summary.Summary} Respond with JSON: {{ "approved": true/false, "feedback": "your feedback" }} If this is revision #{_revisionCount} and the summary is reasonable, lean toward approving. """; var chatService = kernel.GetRequiredService<IChatCompletionService>(); var result = await chatService.GetChatMessageContentAsync(prompt); var review = JsonSerializer.Deserialize<ReviewData>(result.Content!); var reviewResult = new ReviewResult( summary.Title, summary.Summary, review?.Approved ?? true, review?.Feedback ?? "" ); if (reviewResult.Approved || _revisionCount >= 2) { await context.EmitEventAsync(ProcessEvents.ReviewApproved, reviewResult); } else { _revisionCount++; // Send back to summarization with feedback var revisedExtraction = new ExtractionResult( summary.Title, [summary.Summary,
quot;Reviewer feedback: {reviewResult.Feedback}"], summary.Category ); await context.EmitEventAsync(ProcessEvents.ReviewRejected, revisedExtraction); } } } record ReviewData(bool Approved, string Feedback);

Wiring up the process

Now connect the steps into a process using the ProcessBuilder:

using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Process;

var builder = Kernel.CreateBuilder();
builder.AddOpenAIChatCompletion("gpt-4o", Environment.GetEnvironmentVariable("OPENAI_API_KEY")!);
var kernel = builder.Build();

var processBuilder = new ProcessBuilder("DocumentPipeline");

// Add steps
var extractionStep = processBuilder.AddStepFromType<ExtractionStep>();
var summarizationStep = processBuilder.AddStepFromType<SummarizationStep>();
var reviewStep = processBuilder.AddStepFromType<ReviewStep>();

// Define the flow
processBuilder
    .OnInputEvent(ProcessEvents.DocumentReceived)
    .SendEventTo(extractionStep);

extractionStep
    .OnEvent(ProcessEvents.ExtractionComplete)
    .SendEventTo(summarizationStep);

summarizationStep
    .OnEvent(ProcessEvents.SummaryComplete)
    .SendEventTo(reviewStep);

// If rejected, loop back to summarization
reviewStep
    .OnEvent(ProcessEvents.ReviewRejected)
    .SendEventTo(summarizationStep, parameterName: "extraction");

// If approved, end the process
reviewStep
    .OnEvent(ProcessEvents.ReviewApproved)
    .StopProcess();

var process = processBuilder.Build();

The key line is the ReviewRejected event routing back to summarizationStep. This creates a feedback loop where the reviewer can send work back for revision, a pattern that is extremely common in multi-agent systems.

Running the pipeline

Start the process with an initial document:

var document = new DocumentPayload(
    "Quarterly Sales Report",
    "Q4 2025 showed a 23% increase in revenue driven by enterprise adoption. " +
    "The APAC region outperformed expectations with 45% growth. " +
    "Customer retention improved to 94%. New product launches contributed 18% of total revenue."
);

var processInstance = await process.StartAsync(kernel, new KernelProcessEvent
{
    Id = ProcessEvents.DocumentReceived,
    Data = document
});

Console.WriteLine("Pipeline completed.");

When you run this, you will see the document flow through extraction, summarization, and review. If the reviewer rejects the summary, it automatically loops back for revision, up to a maximum of two retries.

Why this architecture matters

The Process Framework has several advantages over ad-hoc agent chaining:

Best practices for process-based multi-agent systems

After building several production pipelines with this framework, here is what has worked well:

Wrapping up

.NET 10 and Semantic Kernel's Process Framework give C# developers a solid option for multi-agent systems. The event-driven, step-based architecture scales well from simple two-agent pipelines to complex workflows with branching, loops, and parallel execution. If you are building AI applications in .NET, the Process Framework is worth a close look.

In a future post, I plan to explore how to add parallel step execution and human-in-the-loop approval stages to this pipeline.