Microsoft Agent Framework Middleware Created: 23 Feb 2026 Updated: 23 Feb 2026

Agent vs Run Scope

Middleware can be scoped at either the agent level or the run level, giving you fine-grained control over when a middleware applies.

ScopeWhen it runsTypical use
Agent-levelEvery RunAsync() call on that agent.Security validation, audit logging, error handling - concerns that always apply.
Run-levelOnly the specific call it is attached to.Debug logging, per-request feature flags, temporary diagnostics.

When both are active on the same call, agent-level runs first (outermost), then run-level, then the agent itself. The return path is the reverse.

Agent-level middleware (outermost)
Run-level middleware
Agent execution
Run-level middleware (return)
Agent-level middleware (return)

Agent-level Middleware

Register at construction time using AsBuilder().Use(). The resulting agent object always has this middleware active.

using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI;

// Middleware function - runs on EVERY call to agentWithSecurity.
async Task<AgentResponse> SecurityMiddleware(
IEnumerable<ChatMessage> messages,
AgentSession? session,
AgentRunOptions? options,
AIAgent innerAgent,
CancellationToken cancellationToken)
{
Console.WriteLine("[Security] Validating request...");
AgentResponse response = await innerAgent
.RunAsync(messages, session, options, cancellationToken)
.ConfigureAwait(false);
Console.WriteLine("[Security] Request completed.");
return response;
}

AIAgent baseAgent = new OpenAIClient("<your_api_key>")
.GetChatClient("gpt-4o-mini")
.AsIChatClient()
.AsAIAgent(instructions: "You are a helpful assistant.", name: "Assistant");

// Register middleware at the agent level - applies to ALL runs.
AIAgent agentWithSecurity = baseAgent
.AsBuilder()
.Use(runFunc: SecurityMiddleware, runStreamingFunc: null)
.Build();

// SecurityMiddleware fires for both calls.
Console.WriteLine(await agentWithSecurity.RunAsync("What's the weather in Paris?"));
Console.WriteLine(await agentWithSecurity.RunAsync("What's the capital of Germany?"));

Run-level Middleware

Run-level middleware is intended for a single specific call only. The official docs show AgentRunOptions.RunMiddleware for this purpose. Because that property is not yet available in v1.0.0-rc1, the practical approach is to wrap the base agent with an additional middleware layer only when that extra behaviour is needed:

// Middleware for one specific call only.
async Task<AgentResponse> DebugMiddleware(
IEnumerable<ChatMessage> messages,
AgentSession? session,
AgentRunOptions? options,
AIAgent innerAgent,
CancellationToken cancellationToken)
{
Console.WriteLine($"[Debug] Input messages : {messages.Count()}");
AgentResponse response = await innerAgent
.RunAsync(messages, session, options, cancellationToken)
.ConfigureAwait(false);
Console.WriteLine($"[Debug] Output messages: {response.Messages.Count}");
return response;
}

// Add the extra layer only when debug information is needed.
AIAgent debugAgent = agentWithSecurity
.AsBuilder()
.Use(runFunc: DebugMiddleware, runStreamingFunc: null)
.Build();

// Only this call has both SecurityMiddleware AND DebugMiddleware.
Console.WriteLine(await debugAgent.RunAsync("What's the weather in Tokyo?"));

Note: AsBuilder() never modifies the original agent. agentWithSecurity is unchanged; debugAgent is a new object that wraps it.

Execution Order Illustrated

For the call debugAgent.RunAsync("What's the weather in Tokyo?") the console output would be:

[Debug] Input messages : 1
[Security] Validating request...
(agent executes and calls LLM)
[Security] Request completed.
[Debug] Output messages: 1

DebugMiddleware (run-level, outermost on debugAgent) wraps SecurityMiddleware (agent-level, registered on agentWithSecurity), which in turn wraps the actual agent execution.

Choosing the Right Scope

QuestionUse agent-levelUse run-level
Should this middleware always be active for this agent?YesNo
Is it applied to every caller without them knowing?YesNo - the caller opts in
ExamplesSecurity, audit log, retry policyDebug tracing, A/B experiments, temporary diagnostics

Summary

ConceptAPIDescription
Agent-level registrationagent.AsBuilder().Use(runFunc: ...).Build()Middleware applies to every RunAsync call on the built agent.
Run-level registration (docs)new AgentRunOptions { RunMiddleware = ... }Applies only to the specific RunAsync call. (Not yet in v1.0.0-rc1.)
Run-level workaround (rc1)existingAgent.AsBuilder().Use(...).Build()Wrap the existing agent with an extra layer only when needed.
Execution orderAgent-level then run-levelAgent-level is outermost; run-level is innermost before execution.

Required Packages

PackagePurpose
Microsoft.Agents.AIAIAgent, AgentResponse, AgentRunOptions, builder APIs
Microsoft.Extensions.AI.OpenAIAsIChatClient() and OpenAI integration


Share this lesson: