· AI & Automation  · 17 min read

Semantic Kernel: Build Multi-Agent AI Systems in .NET

Master Microsoft Semantic Kernel for AI agents. Learn multi-agent orchestration, MCP integration, vector stores, and enterprise patterns with real C# code examples

Master Microsoft Semantic Kernel for AI agents. Learn multi-agent orchestration, MCP integration, vector stores, and enterprise patterns with real C# code examples

How Semantic Kernel Helps You Build Multi-Agent AI Systems in .NET

This article is part of our 5-part series on AI Agent & Workflow Development Tools where we explore the leading platforms and frameworks for building production-ready AI solutions.

📚 Series: Tools We Use for AI Development

  1. Azure AI Foundry - How Azure AI Foundry helps you build secure enterprise AI solutions
  2. LangChain - How LangChain helps you build production-ready AI agents with Python
  3. Semantic Kernel (this article) - How Semantic Kernel helps you build multi-agent AI systems in .NET
  4. n8n - How n8n democratizes AI automation with low-code workflows
  5. Microsoft Agent Framework - How Microsoft Agent Framework enables scalable multi-agent workflows

What is Microsoft Semantic Kernel?

Semantic Kernel is Microsoft’s open-source AI orchestration framework that makes it easier and faster for .NET developers to build enterprise-grade AI solutions and intelligent agents.

It acts as the “brain” and coordination layer that connects large language models (like GPT-4 and GPT-5) with your organization’s systems, data, and business logic. Its real power lies in transforming stateless text-generation models into stateful, reasoning agents that can interact with your existing enterprise infrastructure.

The core problem Semantic Kernel solves: bridging the gap between probabilistic AI reasoning and deterministic business logic (SQL databases, ERP systems, REST APIs) while maintaining type safety and idiomatic .NET patterns.

Why Multi-Agent Systems?

Early AI solutions often relied on a single, monolithic prompt—a so-called “God Prompt”—that attempted to manage every instruction, rule, and exception at once. This approach quickly breaks down: it’s difficult to maintain, hard to debug, and easily exceeds model context limits.

Multi-Agent Systems (MAS) solve this by breaking complex work into specialized roles—just like a real team. One agent plans the task. Another retrieves information. Another interacts with core business systems. Another validates quality or compliance.

By distributing responsibility, multi-agent AI systems deliver:

  • Higher reliability – agents check and balance each other
  • Better maintainability – each agent is smaller, clearer, and easier to improve
  • Scalability – multiple agents can collaborate on problems too large for a single model
  • Stronger accuracy and consistency – specialization improves output quality
  • Greater adaptability – agents can be swapped, upgraded, or reused across solutions

Core Semantic Kernel Architecture for .NET Developers

The Kernel is your central orchestration unit—a mutable context container carrying AI services, plugins, and filters. Unlike static clients, it’s designed for transient, per-request usage.

Kernel Lifecycle Management: Why Transient Registration Matters

Critical: Always register Kernel as Transient in dependency injection. The Kernel.Plugins collection is mutable—if registered as Singleton, plugin state leaks across concurrent users (a severe security vulnerability).

Enterprise Semantic Kernel Setup with HostApplicationBuilder

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.SemanticKernel;

var builder = Host.CreateApplicationBuilder(args);

// 1. Register AI Services (Singleton - reuse HTTP clients)
// ServiceId enables multi-model routing (e.g., fast vs. accurate models)
builder.Services.AddAzureOpenAIChatCompletion(
    deploymentName: "gpt-4o",
    endpoint: Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!,
    apiKey: Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY")!,
    serviceId: "gpt4-fast"
);

builder.Services.AddAzureOpenAIChatCompletion(
    deploymentName: "gpt-4-turbo",
    endpoint: Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!,
    apiKey: Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY")!,
    serviceId: "gpt4-reasoning" // For complex planning tasks
);

// 2. Register Kernel (ALWAYS Transient)
builder.Services.AddTransient<Kernel>(sp =>
{
    var kBuilder = Kernel.CreateBuilder();

    // Propagate logging and telemetry
    kBuilder.Services.AddLogging(l =>
    {
        l.AddConsole();
        l.SetMinimumLevel(LogLevel.Trace); // See LLM reasoning in dev
    });

    // Inject AI services from host container
    kBuilder.Services.AddSingleton(sp.GetRequiredService<IChatCompletionService>());

    return kBuilder.Build();
});

using var host = builder.Build();

Semantic Kernel Plugins: Connect AI to Business Logic

An LLM is a “brain in a jar”—it can reason but can’t access files, databases, or APIs. Semantic Kernel Plugins bridge this gap by exposing C# methods as “tools” the LLM can invoke.

How Semantic Kernel Plugins Work

  1. You decorate methods with [KernelFunction]
  2. SK uses reflection to generate a JSON Schema of your method
  3. This schema is sent to the LLM in its system prompt
  4. The LLM outputs structured tool calls (e.g., {"function": "CheckStock", "args": {"sku": "ABC123"}})
  5. SK intercepts, executes your C# code, and returns results to the LLM

Building Strongly-Typed Semantic Kernel Plugins

Never use primitive strings for complex data. Leverage C# records and the LLM’s structured reasoning.

using Microsoft.SemanticKernel;
using System.ComponentModel;

// Define a contract using records (immutable, concise)
public record OrderRequest(
    [Description("Product SKU code")] string Sku,
    [Description("Quantity to order (1-1000)")] int Quantity,
    [Description("Shipping priority: Standard, Express, Overnight")] string Priority = "Standard"
);

// Plugin class with DI support
public class ERPPlugin
{
    private readonly IErpService _erpService;
    private readonly ILogger<ERPPlugin> _logger;

    public ERPPlugin(IErpService erpService, ILogger<ERPPlugin> logger)
    {
        _erpService = erpService;
        _logger = logger;
    }

    [KernelFunction("create_order")]
    [Description("Submit a new order to the ERP system. Returns order ID.")]
    public async Task<string> CreateOrderAsync(
        [Description("Order details")] OrderRequest order,
        CancellationToken ct = default)
    {
        _logger.LogInformation("Creating order for SKU: {Sku}", order.Sku);

        // SK guarantees 'order' is populated based on the JSON schema
        var orderId = await _erpService.SubmitAsync(
            order.Sku,
            order.Quantity,
            order.Priority,
            ct
        );

        return $"Order {orderId} created successfully";
    }

    [KernelFunction("check_inventory")]
    [Description("Check real-time stock levels for a product")]
    public async Task<int> CheckStockAsync(
        [Description("Product SKU")] string sku,
        [Description("Warehouse location code")] string location = "MAIN")
    {
        var stock = await _erpService.GetStockLevel(sku, location);
        return stock;
    }
}

// Register in DI
builder.Services.AddSingleton<IErpService, ErpService>();
builder.Services.AddSingleton<ERPPlugin>();

// Add to Kernel
var plugin = serviceProvider.GetRequiredService<ERPPlugin>();
kernel.Plugins.AddFromObject(plugin, "ERP");

Key Benefits:

  • The LLM sees the JSON schema and “understands” it must provide Sku, Quantity, and optionally Priority
  • Type safety: you get a strongly-typed OrderRequest, not a dictionary
  • Validation happens before your business logic executes

Model Context Protocol (MCP) Integration with Semantic Kernel

Model Context Protocol (MCP) is an open standard that eliminates custom connector code. Instead of writing separate Semantic Kernel plugins for GitHub, Google Drive, and Slack, you connect to existing MCP Servers provided by vendors.

Architecture

  • MCP Server: Standalone process (Node.js, Python, Docker) exposing tools via MCP standard
  • MCP Client: Your .NET app acting as host
  • Transport: Communication channel (stdio for local, SSE for remote)

How to Connect Semantic Kernel to MCP Servers

Package: Microsoft.SemanticKernel.Plugins.Mcp

using Microsoft.SemanticKernel.Plugins.Mcp;

// Connect to GitHub MCP Server (requires Node.js installed)
await using IMcpClient mcpClient = await McpClientFactory.CreateAsync(
    new StdioClientTransport(new()
    {
        Name = "GitHub",
        Command = "npx",
        Arguments = ["-y", "@modelcontextprotocol/server-github"],
        // Optionally pass environment variables
        Environment = new()
        {
            ["GITHUB_TOKEN"] = Environment.GetEnvironmentVariable("GITHUB_TOKEN")
        }
    })
);

// Discover available tools from the server
var mcpTools = await mcpClient.ListToolsAsync();
Console.WriteLine($"Found {mcpTools.Tools.Count} tools");

// Convert MCP tools to Kernel functions (automatic schema mapping)
var functions = mcpTools.Tools.Select(tool => tool.AsKernelFunction());
kernel.Plugins.AddFromFunctions("GitHub", functions);

// Agent can now invoke: list_issues, create_pull_request, search_code, etc.

Polyglot Benefit: A Python team builds an MCP server for data science tools. Your C# agents consume it without rewriting logic in .NET.

Building Your Own MCP Server with Semantic Kernel

Expose your C# business logic as an MCP server for Claude Desktop, Python agents, or other SK instances.

Package: ModelContextProtocol

// Program.cs (Console App)
using Microsoft.Extensions.Hosting;
using Microsoft.SemanticKernel;

var builder = Host.CreateApplicationBuilder(args);

// Register your plugins
builder.Services.AddSingleton<ERPPlugin>();
builder.Services.AddSingleton<InventoryPlugin>();

// Add MCP Server capability
builder.Services.AddMcpServer()
    .WithStdioServerTransport() // stdio for local, SSE for HTTP endpoints
    .WithToolsFromAssembly(typeof(ERPPlugin).Assembly); // Auto-discover [KernelFunction]

var app = builder.Build();
await app.RunAsync(); // Blocks and listens for MCP JSON-RPC messages

Now Claude Desktop or any MCP client can discover and call your create_order and check_inventory functions.

Semantic Kernel Agent Framework: Types and Use Cases

Package: Microsoft.SemanticKernel.Agents

Agent TypeExecutionState LocationBest For
ChatCompletionAgent.NET in-processClient-side (memory/DB)Low latency, custom history, non-OpenAI models
OpenAIAssistantAgentOpenAI APIServer-side (OpenAI)Code Interpreter, File Search, persistent threads
AzureAIAgentAzure AI FoundryServer-side (Azure)Enterprise security, VNETs, Azure AI Search integration

ChatCompletionAgent: Building Stateless AI Agents in .NET

Stateless by design—you control the conversation history. Works with any IChatCompletionService (Azure OpenAI, Hugging Face, Ollama).

using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Connectors.OpenAI;

// Create specialized agents
ChatCompletionAgent plannerAgent = new()
{
    Name = "Planner",
    Instructions = """
        You are a strategic planner. Break down user requests into actionable steps.
        Output a numbered plan. Do NOT execute—just plan.
        """,
    Kernel = kernel,
    Arguments = new KernelArguments(new OpenAIPromptExecutionSettings
    {
        Temperature = 0.7,
        MaxTokens = 500
    })
};

ChatCompletionAgent coderAgent = new()
{
    Name = "Developer",
    Instructions = """
        You write clean, idiomatic C# code using dependency injection.
        Follow SOLID principles. Use the provided ERP plugin when needed.
        """,
    Kernel = kernel, // This kernel has ERPPlugin registered
    Arguments = new KernelArguments(new OpenAIPromptExecutionSettings
    {
        FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(), // Enable tool calling
        Temperature = 0.2 // Lower for deterministic code
    })
};

ChatCompletionAgent reviewerAgent = new()
{
    Name = "Reviewer",
    Instructions = """
        Review the Developer's code for:
        - Security vulnerabilities (SQL injection, XSS)
        - SOLID principle violations
        - Missing error handling
        If valid, output: APPROVED
        If issues found, output: REJECTED with reasons
        """,
    Kernel = kernel,
    Arguments = new KernelArguments(new OpenAIPromptExecutionSettings
    {
        Temperature = 0.1 // Very deterministic for review
    })
};

Multi-Agent Orchestration with AgentGroupChat

The collaboration runtime—manages turn-taking, shared history, and termination logic.

using Microsoft.SemanticKernel.Agents.Chat;

// Create the group chat with selection strategy
AgentGroupChat chat = new(plannerAgent, coderAgent, reviewerAgent)
{
    ExecutionSettings = new()
    {
        // Selection Strategy: Who speaks next?
        SelectionStrategy = new SequentialSelectionStrategy(),
        // Could also use KernelFunctionSelectionStrategy for dynamic routing

        // Termination Strategy: When to stop?
        TerminationStrategy = new RegexTerminationStrategy("APPROVED")
        {
            Agents = [reviewerAgent], // Only reviewer can approve
            MaximumIterations = 15 // Safety limit
        }
    }
};

// Initialize conversation
chat.AddChatMessage(new ChatMessageContent(
    AuthorRole.User,
    "Create an OrderService that validates inventory before placing orders"
));

// Execute the loop
await foreach (var message in chat.InvokeAsync())
{
    Console.WriteLine($"[{message.AuthorName}] {message.Content}");

    // Handle streaming or artifacts
    if (message.Items.Any(i => i is FileReferenceContent))
    {
        // Process file outputs
    }
}

Console.WriteLine($"Conversation ended after {chat.History.Count} messages");

Advanced Agent Routing: Dynamic Selection Strategy (ReAct Pattern)

Instead of round-robin, use an LLM to route dynamically based on context.

// Create a routing function
KernelFunction selectionFunction = AgentGroupChat.CreatePromptFunctionForStrategy(
    $$$"""
    Analyze the conversation and select the next agent to speak.

    Available agents:
    - Planner: Creates strategic plans, breaks down complex tasks
    - Developer: Writes C# code, implements solutions
    - Reviewer: Checks code quality, approves or rejects

    Rules:
    - After user request → Planner
    - After plan → Developer
    - After code → Reviewer
    - After REJECTED → Developer (to fix)
    - After APPROVED → DONE

    Conversation history:
    {{$history}}

    Output only the agent name.
    """
);

var strategy = new KernelFunctionSelectionStrategy(selectionFunction, kernel)
{
    // Fallback if LLM outputs invalid name
    ResultParser = (result) => result.GetValue<string>() ?? "Planner",

    // History to send to the routing LLM
    HistoryVariableName = "history",
    HistoryReducer = new ChatHistoryTruncationReducer(10) // Last 10 messages only
};

chat.ExecutionSettings.SelectionStrategy = strategy;

This enables Handoff patterns: if the user asks a billing question mid-conversation, the router can hand off to a BillingAgent dynamically.

Azure AI Foundry Integration: Enterprise Semantic Kernel Agents

Package: Microsoft.SemanticKernel.Agents.AzureAI

For production scenarios requiring Code Interpreter (secure Python sandbox), File Search (RAG), and Managed Identity integration.

Implementing Code Interpreter and File Search with Semantic Kernel

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

// 1. Create Azure AI Project Client (uses Managed Identity in production)
var projectClient = new AIProjectClient(
    new Uri(Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")!),
    new DefaultAzureCredential()
);

// 2. Define the Agent with managed capabilities
var agentDefinition = await AzureAIAgent.CreateAsync(
    kernel,
    client: projectClient,
    definition: new AzureAIAgentDefinition("DataScientist")
    {
        ModelId = "gpt-4-turbo",
        Instructions = """
            You are a data analyst. Use Python to:
            - Load and clean CSV data
            - Generate statistical summaries
            - Create visualizations (matplotlib/seaborn)
            Always explain your analysis before showing code.
            """,

        // Enable managed tools (Azure hosts the execution)
        CodeInterpreter = new AzureAICodeInterpreterToolDefinition(),
        FileSearch = new AzureAIFileSearchToolDefinition()
    }
);

// 3. Upload file for analysis (stored in Azure, not local memory)
var agentsClient = projectClient.GetAgentsClient();
var fileInfo = await agentsClient.UploadFileAsync(
    "sales_data.csv",
    AgentFilePurpose.Agents
);
await agentDefinition.AddFileAsync(fileInfo.Id);

// 4. Create a persistent thread (state lives in Azure)
AgentThread thread = await agentDefinition.CreateThreadAsync();
await agentDefinition.AddChatMessageAsync(
    thread,
    "Analyze Q4 sales trends and create a monthly bar chart"
);

// 5. Execute (agent writes Python, Azure runs it in sandbox)
await foreach (var msg in agentDefinition.InvokeAsync(thread))
{
    Console.WriteLine($"[{msg.AuthorName}]: {msg.Content}");

    // Extract generated images (charts)
    foreach (var item in msg.Items)
    {
        if (item is ImageContent imgContent)
        {
            var imageBytes = imgContent.Data!.Value.ToArray();
            await File.WriteAllBytesAsync("sales_chart.png", imageBytes);
            Console.WriteLine("Chart saved to sales_chart.png");
        }
    }
}

// 6. Clean up
await agentDefinition.DeleteAsync();

Key Differences from ChatCompletionAgent:

  • State managed by Azure, not your app (thread survives restarts)
  • Secure Python execution in Hyper-V isolated containers
  • Native RAG via File Search (no manual chunking/embedding)
  • Managed Identity support (no API keys in code)

Semantic Kernel Memory: Vector Stores and RAG Implementation

Memory distinguishes reactive chatbots from stateful AI assistants. Semantic Kernel separates:

  • Short-term memory: The LLM’s context window (conversation history)
  • Long-term memory: Vector/semantic search (RAG - Retrieval-Augmented Generation)

Package: Microsoft.Extensions.VectorData

The new abstraction provides a unified interface for Azure AI Search, CosmosDB, Qdrant, Redis, and in-memory stores.

Defining Vector Data Models in Semantic Kernel

Use attributes to map C# properties to vector store schema.

using Microsoft.Extensions.VectorData;

public class ProductDocument
{
    [VectorStoreRecordKey]
    public string ProductId { get; set; } = string.Empty;

    [VectorStoreRecordData]
    public string Name { get; set; } = string.Empty;

    [VectorStoreRecordData]
    public string Description { get; set; } = string.Empty;

    [VectorStoreRecordData]
    public decimal Price { get; set; }

    [VectorStoreRecordData]
    public string Category { get; set; } = string.Empty;

    // Embedding vector (1536 dimensions for text-embedding-ada-002)
    [VectorStoreRecordVector(1536, DistanceFunction.CosineSimilarity)]
    public ReadOnlyMemory<float> DescriptionEmbedding { get; set; }
}

Connecting Semantic Kernel to CosmosDB Vector Store

using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel.Connectors.AzureCosmosDBMongoDB;
using Microsoft.SemanticKernel.Embeddings;

// 1. Register embedding service
builder.Services.AddAzureOpenAITextEmbeddingGeneration(
    deploymentName: "text-embedding-ada-002",
    endpoint: azureOpenAiEndpoint,
    apiKey: azureOpenAiKey
);

// 2. Create vector store client
var mongoClient = new MongoClient(cosmosConnectionString);
var vectorStore = new AzureCosmosDBMongoDBVectorStore(mongoClient);

// 3. Get collection (auto-creates if needed)
var collection = vectorStore.GetCollection<string, ProductDocument>("products");
await collection.CreateCollectionIfNotExistsAsync();

// 4. Generate embedding and store
var embeddingService = kernel.GetRequiredService<ITextEmbeddingGenerationService>();

var product = new ProductDocument
{
    ProductId = "SKU-12345",
    Name = "Wireless Keyboard",
    Description = "Ergonomic wireless keyboard with mechanical switches",
    Price = 89.99m,
    Category = "Electronics"
};

// Generate embedding for description
var embedding = await embeddingService.GenerateEmbeddingAsync(product.Description);
product.DescriptionEmbedding = embedding;

// Store in vector DB
await collection.UpsertAsync(product);

// 5. Semantic search
var queryEmbedding = await embeddingService.GenerateEmbeddingAsync(
    "I need a comfortable keyboard for coding"
);

var searchResults = await collection.VectorizedSearchAsync(
    queryEmbedding,
    new VectorSearchOptions { Top = 5 }
);

await foreach (var result in searchResults.Results)
{
    Console.WriteLine($"Match: {result.Record.Name} (Score: {result.Score:F3})");
}

Implementing Memory Scoping in Multi-Agent Systems

In multi-agent systems, memory scoping prevents context pollution:

// Shared memory: All agents see the same conversation
var sharedHistory = new ChatHistory();
chat.AddChatMessage(sharedHistory);

// Private memory: Each agent has isolated knowledge
var menuKernel = kernel.Clone();
menuKernel.Plugins.AddFromObject(new MenuPlugin(vectorStore)); // Menu data

var kitchenKernel = kernel.Clone();
kitchenKernel.Plugins.AddFromObject(new KitchenPlugin(equipmentDb)); // Equipment data

ChatCompletionAgent menuAgent = new()
{
    Name = "MenuAgent",
    Kernel = menuKernel, // Has menu vector store
    Instructions = "Answer questions about menu items using the menu database"
};

ChatCompletionAgent kitchenAgent = new()
{
    Name = "KitchenAgent",
    Kernel = kitchenKernel, // Has equipment database
    Instructions = "Manage kitchen equipment and inventory"
};

// Each agent only sees their own tools and memory

Critical Pattern: Use kernel.Clone() or separate DI scopes to isolate plugin access. Without this, all agents share the same Kernel.Plugins collection, creating security and logic issues.

Production-Ready Semantic Kernel: Observability and Error Handling

Monitoring Semantic Kernel with OpenTelemetry

Debugging agent reasoning is notoriously difficult (“Why did it choose that tool?”). SK integrates deeply with OpenTelemetry.

using OpenTelemetry.Trace;
using OpenTelemetry.Logs;

var builder = Host.CreateApplicationBuilder(args);

// Enable OpenTelemetry tracing
builder.Services.AddOpenTelemetry()
    .WithTracing(tracing =>
    {
        tracing
            .AddSource("Microsoft.SemanticKernel*")
            .AddAzureMonitorTraceExporter() // Or Jaeger, Zipkin
            .AddConsoleExporter(); // Dev only
    });

// Logs integration
builder.Logging.AddOpenTelemetry(logging =>
{
    logging.AddAzureMonitorLogExporter();
});

SK emits Activities for:

  • Function invocation (which plugin, what args)
  • Model calls (prompt, tokens used, response time)
  • Planner steps (reasoning traces)

Implementing Semantic Kernel Filters: Middleware Pipeline

Filters intercept execution for logging, PII redaction, or retry logic.

using Microsoft.SemanticKernel;

// Function Invocation Filter (logs tool calls)
public class FunctionLoggingFilter : IFunctionInvocationFilter
{
    private readonly ILogger<FunctionLoggingFilter> _logger;

    public FunctionLoggingFilter(ILogger<FunctionLoggingFilter> logger)
        => _logger = logger;

    public async Task OnFunctionInvocationAsync(
        FunctionInvocationContext context,
        Func<FunctionInvocationContext, Task> next)
    {
        _logger.LogInformation(
            "Agent invoking {Plugin}.{Function} with args: {Args}",
            context.Function.PluginName,
            context.Function.Name,
            context.Arguments
        );

        await next(context); // Execute the function

        _logger.LogInformation(
            "Function {Function} returned: {Result}",
            context.Function.Name,
            context.Result?.ToString()
        );
    }
}

// Prompt Filter (PII redaction before sending to LLM)
public class PiiRedactionFilter : IPromptRenderFilter
{
    private readonly Regex _ssnRegex = new(@"\b\d{3}-\d{2}-\d{4}\b");
    private readonly Regex _emailRegex = new(@"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b");

    public async Task OnPromptRenderAsync(
        PromptRenderContext context,
        Func<PromptRenderContext, Task> next)
    {
        await next(context); // Render the prompt

        // Redact PII before sending to LLM
        var renderedPrompt = context.RenderedPrompt!;
        renderedPrompt = _ssnRegex.Replace(renderedPrompt, "***-**-****");
        renderedPrompt = _emailRegex.Replace(renderedPrompt, "***@***.***");

        context.RenderedPrompt = renderedPrompt;
    }
}

// Register filters in DI
builder.Services.AddSingleton<IFunctionInvocationFilter, FunctionLoggingFilter>();
builder.Services.AddSingleton<IPromptRenderFilter, PiiRedactionFilter>();

Error Handling and Resilience Patterns in Semantic Kernel

using Polly;
using Polly.Extensions.Http;

// Retry policy for transient failures (429 throttling, network issues)
var retryPolicy = HttpPolicyExtensions
    .HandleTransientHttpError()
    .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));

builder.Services.AddHttpClient("AzureOpenAI")
    .AddPolicyHandler(retryPolicy);

// Agent-level error handling
try
{
    await foreach (var message in chat.InvokeAsync())
    {
        Console.WriteLine(message.Content);
    }
}
catch (HttpOperationException ex) when (ex.StatusCode == 429)
{
    // Throttled - backoff or switch to different model
    _logger.LogWarning("Rate limited. Retrying with slower model...");
}
catch (KernelException ex) when (ex.InnerException is JsonException)
{
    // LLM generated invalid JSON for function call
    _logger.LogError("Invalid tool call format: {Error}", ex.Message);
    // Inject error into chat history for LLM to see and retry
    chat.AddChatMessage(new ChatMessageContent(
        AuthorRole.System,
        $"Error: Invalid function call format. Please try again."
    ));
}

Managing State Persistence in Stateless Semantic Kernel Deployments

Agents in Azure Functions or Kubernetes must survive restarts.

using System.Text.Json;

// Serialize AgentGroupChat state
public class AgentChatStateManager
{
    public async Task SaveStateAsync(AgentGroupChat chat, string sessionId)
    {
        var state = new
        {
            History = chat.History.Select(m => new
            {
                m.Role,
                m.Content,
                m.AuthorName
            }).ToList(),
            Iteration = chat.History.Count
        };

        var json = JsonSerializer.Serialize(state);
        await _blobStorage.UploadAsync($"sessions/{sessionId}.json", json);
    }

    public async Task<ChatHistory> LoadStateAsync(string sessionId)
    {
        var json = await _blobStorage.DownloadAsync($"sessions/{sessionId}.json");
        var state = JsonSerializer.Deserialize<dynamic>(json);

        var history = new ChatHistory();
        foreach (var msg in state.History)
        {
            history.AddMessage(
                new ChatMessageContent(msg.Role, msg.Content)
                {
                    AuthorName = msg.AuthorName
                }
            );
        }
        return history;
    }
}

Conclusion: Building Enterprise AI with Semantic Kernel

Semantic Kernel has evolved from a simple LLM wrapper to a comprehensive framework for building enterprise AI systems. The key takeaways for senior developers:

  1. Decompose, Don’t Monolith: Break complex tasks into specialized agents (Planner, Coder, Reviewer, Data Analyst)
  2. Type Safety First: Use strongly-typed DTOs in plugins. Let the LLM reason about structured data.
  3. Leverage Standards: MCP eliminates custom integrations. Consume vendor-provided tool servers.
  4. Memory = Long-term + Short-term: Vector stores for knowledge, context window for immediate history
  5. Production-Ready Patterns:
    • Transient Kernel registration (avoid state leaks)
    • OpenTelemetry for observability
    • Filters for PII redaction and logging
    • State persistence for stateless hosts (Azure Functions, Kubernetes)
  6. Agent Orchestration:
    • Sequential for deterministic pipelines
    • KernelFunctionSelectionStrategy for dynamic routing (ReAct pattern)
    • Termination strategies to prevent infinite loops

The future lies in the Process Framework (BPMN-like state machines for agents, currently in preview) and deeper Azure AI Foundry integration. But the principles remain: orchestrate with rigor, tool with precision, and observe with clarity.

Build your multi-agent systems like you build microservices—isolated, testable, and resilient. Semantic Kernel provides the primitives. You provide the architecture.


Frequently Asked Questions (FAQ)

What is Semantic Kernel used for?

Semantic Kernel is used to build production-ready AI agents and multi-agent systems in .NET. It connects LLMs like GPT-4 to your business logic, databases, and APIs, enabling intelligent automation of complex enterprise workflows.

Is Semantic Kernel free to use?

Yes, Semantic Kernel is open-source and free to use under the MIT license. However, you’ll need API keys for AI services like Azure OpenAI or OpenAI, which have their own pricing.

What’s the difference between Semantic Kernel and LangChain?

Semantic Kernel is designed for .NET developers with strong typing, dependency injection, and enterprise patterns. LangChain is Python-first. SK also has first-class support for Model Context Protocol (MCP) and Azure AI Foundry integration.

Can I use Semantic Kernel with local LLMs?

Yes! Semantic Kernel supports any IChatCompletionService implementation, including Ollama, Hugging Face models, and other local LLM providers. You’re not locked into Azure OpenAI.

How do I debug Semantic Kernel agents?

Use OpenTelemetry integration to trace function invocations, model calls, and reasoning steps. Enable detailed logging with LogLevel.Trace during development to see the full LLM conversation flow.

What is the Model Context Protocol (MCP)?

MCP is an open standard that allows Semantic Kernel agents to use pre-built tool servers (GitHub, Slack, Google Drive) without writing custom plugins. It enables polyglot AI systems where Python, Node.js, and .NET agents share tools.

Does Semantic Kernel support multi-agent systems?

Yes, the AgentGroupChat framework enables sophisticated multi-agent orchestration with sequential, dynamic, or custom routing strategies. Agents can collaborate with shared or isolated memory contexts.


Next Steps: Master Semantic Kernel

Back to Blog