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.
| Scope | When it runs | Typical use |
|---|---|---|
| Agent-level | Every RunAsync() call on that agent. | Security validation, audit logging, error handling - concerns that always apply. |
| Run-level | Only 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
Register at construction time using AsBuilder().Use(). The resulting agent object always has this middleware active.
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:
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:
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
| Question | Use agent-level | Use run-level |
|---|---|---|
| Should this middleware always be active for this agent? | Yes | No |
| Is it applied to every caller without them knowing? | Yes | No - the caller opts in |
| Examples | Security, audit log, retry policy | Debug tracing, A/B experiments, temporary diagnostics |
Summary
| Concept | API | Description |
|---|---|---|
| Agent-level registration | agent.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 order | Agent-level then run-level | Agent-level is outermost; run-level is innermost before execution. |
Required Packages
| Package | Purpose |
|---|---|
Microsoft.Agents.AI | AIAgent, AgentResponse, AgentRunOptions, builder APIs |
Microsoft.Extensions.AI.OpenAI | AsIChatClient() and OpenAI integration |