microsoft / microsoft/agent-framework
.NET: addressable decorator positions in ChatClientAgent's default chat pipeline
@westey-m is already working on this.
Since Aug 18, 2026.
- Dominant language
- Python
- Stars
- 13.6k
- Forks
- 2.3k
- Avg merge
- 2d 45m
- Merged PRs (30d)
- 358
Description
## Problem
A decorator's position inside `ChatClientAgent`'s default chat pipeline is a construction-time secret: `WithDefaultAgentMiddleware` composes `ApprovalResponseBinding → ApprovalNotRequiredFunctionBypassing → FunctionInvokingChatClient → [MessageInjecting] → [PerServiceCallChatHistoryPersisting] → DeferredOpenTelemetry → `, and once the agent exists there is no supported way to observe or change where a decorator sits.
For enforcement-style extensions that need a specific position (the agent-hooks integration from #7564 needs its chat seam **below** `FunctionInvokingChatClient` so every model service call of the tool loop is bracketed individually), the only viable strategy today is:
- start from the raw `IChatClient` and decorate it before agent construction (`AgentHooksChatClientExtensions.Compose`);
- **reject** a supplied client that already contains a `FunctionInvokingChatClient` (it would put the tool loop below the seam — tools executing before any `post_model_call` verdict);
- **reject** caller-supplied per-run `ChatClientAgentRunOptions.ChatClientFactory` callbacks (they replace the pipeline wholesale), while *recognizing* the framework's own function-invocation middleware factory **by walking its closure fields via reflection** to make outer function-middleware composition work (`AgentHooksAgent.IsFrameworkFunctionMiddlewareFactory`) — the single most fragile piece of the integration;
- reject `UseProvidedChatClientAsIs = true` (incompatible with a factory that must customize the stack).
## Proposed shape (for discussion)
Some form of addressable pipeline for the default stack, e.g.:
- named stages (or well-known stage types) with `InsertBefore` / `InsertAfter` on `ChatClientAgentOptions` (or a pipeline-builder callback that receives the ordered default stages and may splice);
- per-run factories declaring *intent* ("wraps" vs "replaces") so wrappers compose instead of being indistinguishable from pipeline replacement.
With that, "add hooks by decorating an existing ChatClientAgent" becomes possible and all four rejections above reduce to ordinary insertion.
## Context
- PR #7564 (agent-hooks enforcement; concrete workaround sites: `AgentHooksChatClientExtensions.Compose`, `AgentHooksAgent.WrapRunOptions` / `IsFrameworkFunctionMiddlewareFactory`)
- Review thread: https://github.com/microsoft/agent-framework/pull/7564#discussion_r3804862167
- Related: #7728, #7729 (companion enhancement issues), #7718 (serializer options for extension packages)
Not urgent (per the review thread) — the shipped factory covers the common cases; this is about making the next such extension not need the rejection dance. cc @westey-m
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.