microsoft / microsoft/agent-framework

.NET: addressable decorator positions in ChatClientAgent's default chat pipeline

Open
#7,726 0 comments 0 reactions 1 assignee View on GitHub

@westey-m is already working on this.

Since Aug 18, 2026.

.NET
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.