AI Agents in .NET 10: Semantic Kernel and Function Calling

A chatbot answers. An agent acts. Where a regular AI integration takes a question and returns a paragraph, an agent decomposes a goal into steps, calls tools, observes results, retries, and finishes the job. "Find the three most overdue invoices, draft reminder emails for each customer, and queue them for my review" is an agent task. So is "diagnose why this deployment failed by reading logs and the last commit." The pattern is the dominant 2026 evolution in production AI — and Semantic Kernel with Microsoft.Extensions.AI brings it to .NET 10 as first-class native code.

This guide covers the five agent patterns we see in production, the function-calling mechanics, and the guardrails that separate a useful agent from a runaway loop that burns through your token budget overnight.

SKSemantic Kernel

.NET 10LTS runtime

MCPTool protocol support

The agent stack in .NET 10

Semantic Kernel

The agent loop, plugins, planners, and chat-completion abstractions. Microsoft's official .NET agent framework.

✅ Function calling

Microsoft.Extensions.AI

AIFunctionFactory.Create turns C# methods into tool definitions. Schema generation and result serialization are automatic.

✅ Interop

Model Context Protocol

MCP is the cross-vendor standard for exposing tool servers. .NET MCP SDK lets your agents consume third-party tool servers and expose your own.

🟡 Architecture choice

Long-running orchestration

For multi-step jobs that exceed HTTP timeouts, layer in a background job runner — Hangfire or Quartz.NET — and persist agent state to SQL Server between turns.

🟡 Architecture choice

Multi-agent systems

Semantic Kernel supports orchestrator + specialist agents. Useful pattern for complex workflows; overkill for most use cases.

🟡 Mandatory

Guardrails

Per-run timeouts, per-user cost caps, max iteration counts. Without these, an agent stuck in a loop will spend your monthly token budget in an afternoon.

Quick reference: five agent patterns

  • Function calling: the foundation

A function call is the model saying "to answer this, run get_order_status(orderId: 12345) and tell me the result." The runtime executes the C# method, returns the result, and the model continues with that information in its context. This single mechanic is the entire agent primitive — everything else is composition.

The simplest pattern: register a few C# methods as tools, ask a question that requires one of them, and let the model figure out which to invoke.

public class OrderTools(IOrderRepository orders)

{

[Description("Get the current status, ship date, and tracking number for an order")]

public async Task<OrderStatus> GetOrderStatusAsync(

[Description("The order ID — format like ORD-12345")] string orderId)

=> await orders.GetStatusAsync(orderId);

[Description("Search for orders matching a customer email or name")]

public async Task<List<OrderSummary>> SearchOrdersAsync(

[Description("Customer email or name")] string query,

[Description("Maximum results to return")] int limit = 5)

=> await orders.SearchAsync(query, limit);

}

// Wire up the agent

var chatOptions = new ChatOptions

{

Tools =

[

AIFunctionFactory.Create(orderTools.GetOrderStatusAsync),

AIFunctionFactory.Create(orderTools.SearchOrdersAsync)

]

};

var response = await chatClient.GetResponseAsync(

"What's the status of ORD-12345?",

chatOptions);

Behind the scenes, AIFunctionFactory.Create introspects the method signature, generates a JSON schema from the parameter types and [Description] attributes, and registers it. The model sees a tool catalog and decides when to call which tool. The runtime deserializes the arguments to your C# types and serializes the return value back. You write idiomatic C#; the JSON plumbing is invisible.

  • The ReAct loop: multi-step reasoning

Single-turn calls handle "what's the status of order X." Real agent tasks are messier: "find orders that shipped late this month and email customer-service summaries." That needs multiple tool calls in sequence, with the model deciding what to do at each step based on what it learned at the previous one.

This is the ReAct loop (Reasoning + Acting). Semantic Kernel implements it for you via auto-invocation:

var settings = new OpenAIPromptExecutionSettings

{

FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(),

MaxTokens = 2000,

Temperature = 0.2

};

var kernel = Kernel.CreateBuilder()

.AddOpenAIChatCompletion(model, apiKey)

.Build();

kernel.Plugins.AddFromObject(new OrderTools(orderRepo));

kernel.Plugins.AddFromObject(new EmailTools(mailer));

kernel.Plugins.AddFromObject(new ReportTools(reports));

var result = await kernel.InvokePromptAsync(

"Find all orders that shipped more than 5 days late in November " +

"and draft an apology email to each customer. Save the drafts for review.",

new(settings));

The model will autonomously call SearchOrders, filter results, call DraftEmail for each affected customer, call SaveDraft for each, and return a summary. You wrote no orchestration code — Semantic Kernel's auto-invocation loop drives the whole sequence.

Iteration limits

The default behavior continues until the model says "done." This is dangerous. Set MaximumAutoInvokeAttempts in your FunctionChoiceBehavior to cap the number of tool calls per run — 10 to 20 is reasonable for most tasks. An infinite loop here will eat your token budget.

  • The plugin pattern

Once your agent has 15+ tools, the model spends increasing tokens reading the catalog and occasionally picks the wrong one. Grouping tools into plugins by domain — Email, Calendar, Orders, Tickets — gives the model context-aware tool descriptions and makes per-user permissioning straightforward.

Semantic Kernel's Kernel.Plugins.AddFromObject(...) registers an entire C# class as a plugin. Each public method becomes a tool, and the plugin name namespaces them:

public class EmailPlugin(IEmailService email)

{

[KernelFunction, Description("Send an email immediately")]

public async Task SendAsync(string to, string subject, string body) => ...

[KernelFunction, Description("Save an email as a draft for human review")]

public async Task<string> SaveDraftAsync(string to, string subject, string body) => ...

[KernelFunction, Description("List drafts awaiting review")]

public async Task<List<DraftSummary>> ListDraftsAsync() => ...

}

kernel.Plugins.AddFromObject(new EmailPlugin(emailService), pluginName: "Email");

// The model now sees: Email-SendAsync, Email-SaveDraftAsync, Email-ListDraftsAsync

Two production benefits: you can pass different plugin subsets to different users (a customer-service agent sees the Tickets plugin; a sales agent sees CRM plugins), and you can gate destructive tools behind a confirmation layer per-plugin.

  • MCP: consuming external tool servers

The Model Context Protocol is the cross-vendor standard for tool servers. A vendor (or an open-source project) publishes an MCP server exposing tools — GitHub publishes one for repo access, Slack publishes one for messaging, file-system MCP servers expose local files. Your .NET agent consumes those servers without writing custom integrations.

// Consume an external MCP server

var mcpClient = await McpClient.CreateAsync(new()

{

Endpoint = new Uri("https://mcp.example.com/sse"),

ServerName = "ExampleVendor"

});

// Get all tools the server exposes

var tools = await mcpClient.ListToolsAsync();

// Register them with your agent

foreach (var tool in tools)

{

kernel.Plugins.AddFromFunctions(tool.Name, [tool.AsKernelFunction()]);

}

The reverse is also possible: expose your own application's capabilities as an MCP server so other AI tools (Claude Desktop, Cursor, any MCP-aware client) can call into your business logic. The .NET MCP SDK provides both client and server APIs.

  • Multi-agent orchestration

For complex workflows, a single agent juggling 30 tools degrades. The pattern that scales: one orchestrator agent decomposes the task and dispatches sub-tasks to specialist agents, each with a focused tool set and system prompt.

var researcher = new ChatCompletionAgent

{

Name = "Researcher",

Instructions = "You search documents and summarize findings. Use the search and read tools.",

Kernel = researchKernel

};

var writer = new ChatCompletionAgent

{

Name = "Writer",

Instructions = "You draft customer-facing copy from research summaries. Use the draft tool.",

Kernel = writerKernel

};

var orchestrator = new AgentGroupChat(researcher, writer)

{

ExecutionSettings = new()

{

SelectionStrategy = new SequentialSelectionStrategy(),

TerminationStrategy = new ApprovalTerminationStrategy { MaximumIterations = 6 }

}

};

await foreach (var msg in orchestrator.InvokeAsync("Write a Q4 product update email"))

{

Console.WriteLine($"[{msg.AuthorName}] {msg.Content}");

}

The Researcher does its work, hands off to the Writer, and the Writer produces the final draft. Each agent stays focused on its domain. The orchestrator pattern is more code than a single-agent setup but pays off when individual agents would have 20+ tools.

Guardrails: the non-optional stuff

An agent without guardrails will eventually hit a state where it calls a tool, gets an error, retries, gets the same error, retries again, and never realizes it's stuck. We've watched agents make 200+ tool calls in 10 minutes before someone noticed. Set limits, log everything, and surface anomalies in real time.

Per-run iteration cap

The hard limit on how many tool calls a single agent run can make. 15-20 is plenty for most tasks. The agent must finish (or escalate) within that budget.

Per-run wall-clock cap

CancellationTokenSource.CancelAfter(TimeSpan.FromMinutes(2)) on the kernel invocation. Long-running tasks should be background jobs with persisted state, not synchronous HTTP requests.

Per-user cost cap

Track tokens per user per day. Hard-cap to prevent runaway bills. Store in SQL Server and check on every request.

Audit trail

Every tool call gets logged: (RunId, ToolName, Arguments, Result, DurationMs, Timestamp). When an agent does something unexpected, this trail tells you exactly what happened. It's also your compliance evidence for any regulated industry.

Human-in-the-loop for destructive actions

Mark sensitive tools — anything that sends external emails, cancels orders, deletes data, charges cards — as confirmation-required. The tool itself returns "pending confirmation," your UI surfaces a button, and the actual action runs only after a human approves.

[KernelFunction, Description("Cancel an order. Requires human approval.")]

public async Task<CancelResult> CancelOrderAsync(string orderId)

{

var approvalId = await _approvals.RequestAsync(new()

{

Action = "CancelOrder",

Args = new { OrderId = orderId },

RequestedBy = _currentUser.Id

});

return new CancelResult

{

Status = "PendingApproval",

ApprovalId = approvalId,

Message = $"Cancellation queued. An admin must approve before it takes effect."

};

}

Long-running agents: background jobs + persisted state

An agent task that takes 5 minutes is fine. One that takes 5 hours is incompatible with an HTTP request lifecycle. The pattern:

User submits the task via a web request.

Server enqueues an agent job to Hangfire / Quartz.NET.

A background worker picks up the job, runs the agent, persists checkpoints to SQL Server after each major step.

The user polls a status endpoint (or gets a Blazor SignalR push) to see progress.

On completion, results are stored and a notification is sent.

Hangfire is the most common choice on Windows + IIS — battle-tested, has a dashboard, persists to SQL Server. Quartz.NET is the alternative for cron-style scheduling. Both work cleanly on Adaptive Web Hosting since they don't need anything beyond what's included.

Hosting recommendations

ASP.NET Business — $17.49/mo

Customer-facing agents: chat-based ordering, conversational booking, multi-turn support. 2 GB RAM per app pool. Most agents fit here.

View Business plan →

ASP.NET Professional — $27.49/mo

Multi-agent platforms, agency-built AI assistants, high-throughput automation pipelines. 4 GB per pool, highest priority scheduling.

View Professional plan →

FAQs

What's the difference between Semantic Kernel and LangChain?

Semantic Kernel is Microsoft's first-party .NET agent framework. LangChain is Python-native (with a separate .NET port that lags behind). For a .NET codebase, Semantic Kernel is the default — better integration with Microsoft.Extensions.AI, native async, and first-class support for OpenAI, Azure OpenAI, Anthropic, and local models.

Do I need fine-tuning to make agents work well?

No. Modern GPT-4-class models follow function-calling instructions reliably out of the box. The agent's quality depends on tool descriptions, system prompts, and guardrails — not on training a custom model.

How do I test an agent?

Maintain a golden set of (task, expected tool sequence, expected outcome) tuples. On every change, run the suite and assert the agent calls the right tools in roughly the right order. Use a deterministic temperature (0.0–0.2) for testing.

Can two agents talk to each other?

Yes — that's the multi-agent orchestration pattern. Semantic Kernel's AgentGroupChat manages the conversation between agents, with selection strategies (round-robin, model-decided, sequential) controlling who speaks next.

What's MCP and do I need it?

The Model Context Protocol is the emerging standard for tool servers. If you only build internal agents using your own tools, you can ignore it. If you want to consume third-party tool servers (GitHub, Slack, file systems) or expose your app as a tool to other AI clients, MCP is the integration layer.

How do I prevent prompt injection through tool results?

Treat tool results like untrusted user input. Don't let a tool result contain instructions the model will follow as if they came from you. Strip or escape content that looks like prompt syntax, especially when reading external content (emails, web pages, file contents).

Ship it

Agents are the natural next step beyond chatbots — same primitives, more autonomy. The .NET 10 + Semantic Kernel + Microsoft.Extensions.AI stack gives you everything: function calling, plugins, MCP, multi-agent orchestration, and the integration with EF Core 10, SQL Server, and Hangfire that makes the supporting infrastructure straightforward.

Adaptive Web Hosting's ASP.NET hosting plans run all of this on real Windows + IIS with SQL Server 2022 included on every plan, dedicated app pools for isolation, and free SSL. Build the orchestration application on Adaptive; pair it with your chosen model provider; ship a real agent in days, not months.

Back to Blog