Mastering Microsoft Agent Framework Configuration in .NET

Writer

Microsoft Agent Framework makes the easy path genuinely easy. Give an agent a model, instructions, and tools, then call RunAsync().
The difficulty appears when the application needs something less ordinary: a longer network timeout, persistent conversation history, a model-specific reasoning option, tool approval, request interception, or a one-turn override. Those settings do not live in one master configuration object. They belong to different layers of the agent pipeline.
That is the mental model for this article:
Microsoft Agent Framework configuration is a layered pipeline, not a single settings file. Configure each concern at the narrowest layer that owns it.
Microsoft Agent Framework 1.0 became production-ready on April 3, 2026. The framework supports agents, context and state management, middleware, tools, observability, and graph-based workflows across .NET and Python. This article focuses specifically on configuring a .NET ChatClientAgent, the agent implementation built on Microsoft.Extensions.AI.IChatClient. Microsoft Agent Framework 1.0 announcement · Agent Framework overview
First, map the configuration layers
The source material presents four places where configuration appears: provider construction, agent construction, post-creation middleware, and the individual run. That four-surface view is useful when navigating the API. Internally, however, the agent surface contains several distinct responsibilities, so the more precise runtime map is:
| Layer | Owns | Typical examples |
|---|---|---|
| Provider and transport | Connectivity and provider SDK behavior | Endpoint, credentials, HttpClient, retries, network timeout |
| Agent definition | Stable identity and baseline behavior | ID, name, description, default chat options |
| Context and history | Per-session state and contextual enrichment | Conversation history, memory, RAG context, dynamic instructions |
| Chat options | Portable inference behavior | Model, instructions, tools, output limit, response format, tool choice |
| Provider escape hatch | Features not represented by the common abstraction | Provider-specific reasoning, service, caching, or hosted-tool options |
| Middleware | Cross-cutting interception | Logging, validation, policy checks, function-call inspection |
| Run | One invocation | Input messages, session, streaming, per-turn option overrides |

This is not just an organizational preference. The documented ChatClientAgent pipeline has three principal runtime layers: agent middleware, a context layer, and the underlying IChatClient pipeline. A request travels through those layers when you invoke the agent. Agent pipeline architecture
1. Provider and transport configuration comes first
Before creating an agent, you create or obtain the provider client. This is where provider connectivity belongs:
- endpoint and authentication;
- the selected provider API surface;
HttpClientlifetime and network timeout;- transport-level retry behavior;
- proxy, certificate, and network configuration;
- provider SDK options that apply to every request made through that client.
Do not assume that Microsoft Agent Framework exposes one universal “provider options” object. The concrete configuration depends on the provider integration and SDK. Azure OpenAI, OpenAI, Anthropic, Ollama, Microsoft Foundry, and other implementations can all surface different construction patterns while meeting at the common IChatClient or AIAgent abstractions.
OpenAI-compatible endpoints add another variation. A provider such as OpenRouter, xAI, or another gateway may be reachable through an OpenAI-compatible client by changing the base endpoint and credentials. Compatibility is not necessarily completeness: models and gateways can differ in supported request fields, tool behavior, streaming, and error semantics. Treat the exact endpoint-model combination as a separate integration and verify it with tests rather than assuming every OpenAI SDK feature is available.
Azure OpenAI illustrates why this distinction matters. Agent Framework supports both the Responses API and Chat Completions API. Microsoft recommends Responses for new agent scenarios that need its richer hosted-tool surface, while Chat Completions remains useful for broad model compatibility and existing integrations. Azure OpenAI provider documentation
Treat timeouts as an end-to-end budget
A long-running model call can fail at several boundaries: your application’s cancellation token, HttpClient, a proxy or gateway, the provider service, or an upstream hosting platform. Increasing one timeout does not automatically increase the others.
A better rule is:
Set an explicit end-to-end deadline, then make sure each lower layer can accommodate it.

Use a per-operation CancellationToken for the application’s real deadline. Adjust transport timeouts only when the workload and provider behavior justify it. Do not simply make every timeout infinite, because stalled calls then become harder to detect and recover.
2. Define the agent’s stable baseline
For a straightforward agent, the concise factory or extension overload is often enough:
The conceptual minimum is:
- a model, usually selected when constructing the provider client;
- instructions;
- optional tools.
Tools are not required for a conversational agent, and instructions are technically optional. They become part of the practical minimum only when the agent needs a defined role or actions beyond plain model conversation.
Use ChatClientAgentOptions when you need an explicit, maintainable baseline:
ChatClientAgentOptions contains the agent metadata, default ChatOptions, AIContextProviders, and ChatHistoryProvider. It also exposes advanced history-conflict and message-injection behavior. An omitted ID may be generated, but that does not generate a user interface. ChatClientAgentOptions API
What belongs in the baseline?
Put behavior here when it should normally apply to every run:
- the agent’s identity and description;
- default instructions;
- the normal tool set;
- the default output limit or response format;
- context providers;
- the history provider.
Avoid filling every property “just in case.” Defaults preserve provider compatibility and make later upgrades easier.
Construction hooks: client factories, services, and logging
The concise construction overloads and ChatClientAgentOptions also expose infrastructure-oriented hooks. Depending on the framework version and overload, these include a client factory, a service provider, and logging integration. They are easy to overlook because they do not define the agent’s conversational personality.
- Client factory: lets advanced code select, decorate, or replace an
IChatClient. Use it when client choice must be resolved dynamically. If the provider is stable, constructing the intended client explicitly is usually easier to understand. - Services: makes an
IServiceProvideravailable to framework components that resolve dependencies, including tool-related infrastructure. Do not turn this into a service-locator pattern inside ordinary business code. Prefer constructor injection when you control the object. - Logging: can be supplied during construction, while richer telemetry can also be attached through the client or agent builder. Microsoft Agent Framework integrates with OpenTelemetry and emits traces, logs, and metrics using GenAI semantic conventions. Instrument the layer that gives you the required visibility, and avoid duplicate sensitive payload capture across both agent and client instrumentation. Observability
These hooks are available for specialized composition scenarios. They are not a reason to use the longest overload for every agent.
3. ChatOptions is the portable inference contract
ChatOptions, from Microsoft.Extensions.AI, is the common request vocabulary between an agent and an IChatClient. It is where you express inference behavior that can reasonably be abstracted across providers.
The important settings fall into a few groups:
| Concern | Representative settings | Guidance |
|---|---|---|
| Behavior | Instructions, ModelId | Define the task and optionally override the client’s default model |
| Tools | Tools, ToolMode | Advertise tools and control whether tool use is automatic, prohibited, required, or narrowed to a particular function where supported |
| Output | MaxOutputTokens, ResponseFormat | Bound generation and request structured output |
| Sampling | Temperature, TopP, penalties, Seed, stop sequences | Use only when the selected model and provider support them |
| Reasoning | Reasoning effort and reasoning-output options, where exposed | Request a supported reasoning level or summary behavior without dropping directly to native provider types |
| Concurrency and state | AllowMultipleToolCalls, ConversationId | Opt into supported parallel calls or service-managed conversation state |
| Provider extensions | AdditionalProperties, RawRepresentationFactory | Carry non-portable options when the adapter supports them |
Portable does not mean universally supported
A property on ChatOptions is a request, not a guarantee that every provider-model combination will honor it. The underlying client may translate it, reject it, or be unable to apply it.
This is especially important for sampling controls. Do not classify temperature, seeds, or stop sequences as “legacy” across the board. They remain valid for many models, but some reasoning models constrain or reject them. A seed can improve repeatability on a supporting backend, but it does not make a distributed LLM application deterministic.
Likewise, MaxOutputTokens is not “required for Anthropic and ignored by most others.” It is a portable output bound that many providers can map to their own request schema. Support and exact semantics remain provider-specific.
Reasoning options are becoming more portable, but remain uneven
Microsoft.Extensions.AI can expose reasoning-oriented options through its common abstractions, reducing the need for provider-native request objects in supported cases. That does not mean every provider shares one reasoning scale or output model. A value such as low, medium, or high may map differently, and some providers add levels or controls that the common contract does not represent.
Use the portable reasoning option when it expresses the behavior you need and the selected adapter documents support. Drop to provider-native options only for a real gap, such as a provider-only effort level, reasoning summary mode, token budget, or service-specific threshold. This keeps the normal path portable without pretending the underlying APIs are identical.
Tool choice is a policy lever
The default automatic mode lets the model decide whether to call a tool. Requiring a tool means the model must produce a tool call, and some APIs can require a particular function. Disallowing tools can be clearer than removing them when you intentionally want to preserve the baseline tool definition but suppress invocation for one run.
Do not confuse tool choice with authorization. A model selecting a tool is not the same as your application approving a side effect. Microsoft warns that tools are invoked without user approval by default and recommends explicit approval for sensitive, irreversible, or side-effecting actions. Function arguments should always be treated as untrusted input. ChatClientAgent security remarks
Parallel tool calls are permission, not a promise
AllowMultipleToolCalls indicates whether the client may return multiple function calls for the application to process together. It does not force the model to do so, and actual behavior depends on the model, provider, client adapter, and tool-calling loop. Parallel execution also introduces concurrency concerns. Only execute calls concurrently when the tools are independent and thread-safe, and when their side effects can safely occur in an uncertain order.
Conversation IDs and local history solve related problems differently
ConversationId can identify provider-managed conversation state when the underlying service supports it. A ChatHistoryProvider instead lets the application load and persist messages. In many applications the framework session and history provider should own continuity; in provider-managed scenarios, the remote conversation ID may be the better source of truth. Avoid replaying local history into a provider-managed conversation unless the integration explicitly requires it.
Seeds and stop sequences are specialized, not useless
A seed can request greater repeatability from a supporting backend, but does not guarantee identical results. Stop sequences tell a supporting model to halt when it generates one of the configured strings. They remain useful for constrained text protocols and delimiters, though structured output is usually a stronger contract when available.
Structured output belongs here, but support still varies
ResponseFormat can request JSON or a schema-backed JSON response. It is valuable when downstream code needs a typed contract rather than free-form text. The provider and selected model must support the requested format, and the application should still validate the returned data before acting on it. ChatResponseFormatJson API
4. Use context providers and history for different jobs
These two concepts are related, but they are not interchangeable.
ChatHistoryProvider: conversation continuity
A history provider loads and persists the messages required for a multi-turn conversation. Use it when your application, rather than the remote model service, owns conversational history.
Some provider APIs manage conversation state themselves and return a conversation ID. Configuring local history at the same time can create a conflict or duplicate context. ChatClientAgentOptions therefore includes explicit conflict behavior. Choose one state model deliberately instead of letting local and service-managed history overlap accidentally. ChatClientAgentOptions API
AIContextProvider: just-in-time enrichment
A context provider participates around each run. It can contribute or override messages, instructions, tools, and other context before execution, then process information after execution. That makes it appropriate for memory retrieval, RAG, tenant-specific guidance, or dynamically selected tools. Context providers
A crucial lifecycle rule is that a context-provider instance is attached to the agent and can be shared across sessions. Do not keep session-specific state in instance fields. Store per-session data in the AgentSession or an external store instead. Context providers
A simple division of responsibility is:
- history provider: what happened earlier in this conversation;
- context provider: what the model needs to know for this run;
- middleware: what the system should observe, validate, transform, or block while the run executes.

5. Long-running calls: background responses and continuation tokens
Background responses are not merely a generic timeout switch. They are a resumable execution feature for supported agents and provider APIs. When enabled, a request may finish immediately or return a continuation token. The application can then poll for completion in a non-streaming flow or resume an interrupted streaming flow. A null continuation token indicates that processing has completed, failed, or cannot proceed further without another action.
As of the current Microsoft documentation, explicit background-response support is limited to agents using the OpenAI Responses API, including Azure OpenAI Responses agents. Do not expose a “background” toggle in a provider-neutral configuration screen without checking whether the concrete agent supports it. Agent background responses
A useful distinction is:
- timeout or cancellation: how long the caller is willing to wait now;
- background response: whether supported work can continue beyond the current wait;
- continuation token: how the caller reconnects to that work.
6. RawRepresentationFactory is the provider escape hatch
Common abstractions inevitably lag behind fast-moving provider APIs. ChatOptions.RawRepresentationFactory lets an adapter start from the native request-options object expected by the underlying SDK.
For an OpenAI Responses client, the pattern can look like this:
The exact native type depends on the adapter and API surface. Even within one provider, Chat Completions and Responses can use different option types. Do not copy a native options type from an Azure OpenAI example into an Anthropic or Gemini client and expect it to work.
This escape hatch is appropriate when:
- the feature is genuinely provider-specific;
ChatOptionsdoes not represent it;- the underlying SDK exposes it;
- the selected Agent Framework adapter consumes that raw type.
It is not proof that every bleeding-edge feature “lives” there. Sometimes the provider SDK has not exposed the feature, the adapter does not translate it, or the feature requires a different client entirely. Microsoft maintainers document RawRepresentationFactory as the path for seeding provider-native request settings, including OpenAI Responses options. Microsoft Agent Framework discussion: raw request settings · Microsoft Agent Framework discussion: provider-specific metadata
Why AdditionalProperties is not always enough
AdditionalProperties is a dictionary carried by the abstraction, but an adapter must explicitly translate those values into the provider request. Do not assume an arbitrary key becomes arbitrary JSON on the wire. If a required option is not mapped, use the documented provider-native path or a custom IChatClient, and verify the emitted request in integration tests.
The portability trade-off
RawRepresentationFactory is a deliberate break in provider portability. Keep it isolated:
- wrap provider-specific construction in one factory;
- test it against the exact SDK and package versions you ship;
- expect changes when the provider SDK evolves;
- avoid scattering native request types throughout business logic.
Think of it as the framework’s emergency exit, not the main entrance.
7. Middleware configures the execution pipeline
Middleware is for cross-cutting behavior that should wrap agent runs, function calls, or model-client calls without being embedded in the agent’s core instructions.
The current .NET builder pattern starts from an existing agent:
Agent Framework documents three relevant interception points:
- agent-run middleware wraps the overall agent invocation;
- function-calling middleware wraps tools invoked by the agent;
IChatClientmiddleware wraps calls to the underlying model client.
Use these layers for logging, validation, error handling, result transformation, tool-call inspection, and other cross-cutting concerns. Multiple middleware components form a chain, and each component is responsible for invoking the next delegate when execution should continue. Agent middleware
The most direct equivalent of the source’s “tool-calling middleware” is function-calling middleware. It can inspect the requested function and arguments, record the call, alter permitted processing, or stop execution before the tool runs. Agent-run middleware is broader and wraps the whole invocation. IChatClient middleware sits lower and sees model requests and responses. Choose the narrowest interception point that owns the concern.
Context enrichment can also enter the pipeline dynamically. Use an AIContextProvider when the behavior is semantically context, such as injecting retrieved messages, RAG evidence, temporary instructions, or run-specific tools. Use middleware when the behavior is interception, such as validation, auditing, timing, or blocking. Both can affect a run, but they communicate different design intent.
Custom middleware does not require a special framework product. Teams can package their builder calls and callbacks behind their own extension methods, allowing a standard policy or telemetry chain to be applied consistently to many agents.
Streaming middleware needs special care
If you register only non-streaming run middleware, the framework can satisfy it by processing a streaming invocation in non-streaming mode. That changes response behavior. Provide both streaming and non-streaming middleware when output interception matters, or use the shared overload when you only need to inspect or modify input without overriding output. Agent middleware
Tool approval is more than a log entry
Function middleware can inspect and conditionally stop a call, but approval is a first-class safety boundary. Use approval for tools that send messages, modify records, spend money, reveal sensitive data, or perform irreversible operations. “Approve and don’t ask again” decisions must be scoped carefully to the user, session, tool, arguments, and policy context. Convenience should not silently become permanent authorization.
Observability has two levels
OpenTelemetry is the right default for production diagnostics because it supports correlated traces and standardized exporters. Instrument both the agent and IChatClient pipelines when you need to distinguish orchestration time, model latency, and tool execution. Treat prompts, responses, and tool arguments as potentially sensitive telemetry, and enable content capture only under an explicit data-handling policy.
8. Apply per-run overrides at execution time
RunAsync() is the final configuration layer. The overloads largely reflect combinations of:
- string or message-based input;
- one message or multiple messages;
- an
AgentSessionfor stateful interaction; - run-specific options;
- cancellation;
- streaming versus non-streaming output.
Do not memorize the overload count. It changes as the API evolves. Instead, choose the overload based on payload, state, and execution mode.
The same caution applies to serialization-related overloads and session APIs. Sessions can be created and serialized so conversation state can survive process boundaries, but serialization is about persisting the framework’s session state, not changing model inference behavior. Use the agent’s session creation and deserialization APIs for durable sessions rather than manually serializing internal implementation objects. Running agents · Custom agents and serializable sessions
Use a plain string for simple text. Use ChatMessage and content objects for multimodal input. Use a list of messages when you intentionally supply multiple messages. Use an AgentSession for framework-managed multi-turn state rather than treating a message list as an automatic substitute for session semantics.
A run-specific override looks like this:
Per-run ChatOptions are combined with the agent defaults. When both specify the same scalar option, the run value typically takes precedence. Collections such as tools may be unioned, not replaced. This detail matters: passing a new tools collection is not necessarily a reliable way to remove the baseline tools. Use the appropriate tool mode when the goal is to prevent tool use for that invocation. ChatClientAgentRunOptions.ChatOptions API
Per-run configuration is ideal for a temporary change:
- tighten the output limit;
- request a structured response;
- suppress or require tool use;
- change instructions for one task;
- select a compatible model;
- apply provider-native options for one invocation;
- enable or resume a supported background response;
- select a different client through a type-specific client factory when the concrete API supports that extension.
If the override becomes normal behavior, move it into the agent baseline. If it changes how every request is transported, move it down to the client or provider layer.
A practical placement guide
When you cannot find a setting, ask these questions in order:
- Is this connectivity or HTTP behavior? Configure the provider client or transport.
- Should it apply to nearly every run? Put it in
ChatClientAgentOptionsor its defaultChatOptions. - Is it conversation state? Use a history provider or service-managed conversation state.
- Is it dynamic knowledge or memory? Use an
AIContextProvider. - Is it a portable inference option? Use
ChatOptions. - Is it native to one provider or API? Use the adapter’s documented native option path, often
RawRepresentationFactory. - Is it cross-cutting execution behavior? Use middleware.
- Is it needed for one invocation? Use run options.
That sequence prevents two common mistakes: putting stable configuration into ad hoc run code, and forcing provider-specific behavior into a supposedly portable abstraction.
Common configuration mistakes
Mistake 1: Setting every option explicitly
This creates brittle code and can send unsupported parameters to a model. Start with defaults, then add settings because the application requires them.
Mistake 2: Treating all providers as interchangeable
The common abstraction is valuable, but API surfaces, tool support, state models, and native settings differ. Test the exact provider-model-client combination.
Mistake 3: Confusing context, history, and middleware
History reconstructs the conversation. Context enriches a run. Middleware intercepts execution. Mixing these responsibilities makes state difficult to reason about.
Mistake 4: Assuming per-run collections replace defaults
The documented merge behavior can union tool collections. Use an explicit tool mode or build a distinct agent configuration when isolation is required.
Mistake 5: Assuming raw options stay portable
They do not. Native SDK types couple the code to a provider, API surface, and package version.
Mistake 6: Treating model output and tool arguments as trusted
Validate tool arguments, sanitize output before rendering or execution, and require approval for consequential actions. The framework’s own API guidance identifies hallucination, indirect prompt injection, malicious output, and unapproved tool invocation as security concerns. ChatClientAgent security remarks
Reducing configuration fatigue responsibly
Deep nesting is not always accidental complexity. It often reflects real boundaries between transport, provider, inference, state, interception, and execution. Flattening those boundaries can improve ergonomics, but it can also hide merge behavior or provider coupling.
If your organization repeatedly creates similar agents, build a thin internal factory that:
- starts from safe defaults;
- exposes only the choices your teams actually need;
- isolates provider-native options;
- wires standard telemetry and approval behavior;
- returns normal
AIAgentorChatClientAgentabstractions; - includes integration tests for emitted requests and tool policy.
The community-maintained Agent Framework Toolkit takes this approach as an opinionated C# wrapper. Its provider-specific factories expose a flatter options object and surface settings relevant to the chosen provider, including native options and middleware wiring, while producing agents compatible with Microsoft Agent Framework. This can make options such as an OpenAI service tier or an Anthropic output limit easier to discover than navigating several nested objects.
It is available on NuGet, but it is not a Microsoft-supported component. A flatter object also cannot erase the real semantic differences between providers. Evaluate package maintenance, version alignment, provider coverage, hidden defaults, and escape hatches as you would for any third-party dependency. AgentFrameworkToolkit on NuGet · Agent Framework Toolkit repository
The configuration model to remember
Microsoft Agent Framework becomes easier to configure once you stop searching for one all-powerful options object.
- Provider clients own connectivity and native SDK behavior.
ChatClientAgentOptionsowns the agent’s stable baseline.ChatOptionsowns portable inference settings.- History and context providers own state and just-in-time enrichment.
RawRepresentationFactoryis an intentional provider-specific escape hatch.- Middleware owns cross-cutting interception.
- Run options own temporary, invocation-specific changes.
Configure a concern at the lowest layer that fully owns it, and no lower. Keep provider-specific code contained. Leave unsupported options unset. Test the actual request path, not just the object graph.
Once you adopt that mental model, the nested settings stop looking like a maze. They become a map of the runtime itself.
References
- Microsoft Agent Framework documentation
- Microsoft Agent Framework overview
- Agent pipeline architecture
- Agent middleware
- Running agents
- Agent background responses
- Observability
- Context providers
ChatClientAgentOptionsAPIChatClientAgentRunOptions.ChatOptionsAPI- Azure OpenAI provider documentation
- Microsoft Agent Framework 1.0 announcement
- AgentFrameworkToolkit on NuGet
Read next


