You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Python: Bug: Complete chat history is getting passed to LLM - chat history summarizer is not working #12303
Describe the bug
Chat history summarizer is not working here. It still passes the complete chat history to llm api call.
This is one of the agent. Each agent has its own kernel instance.
Its is used in GroupChatOrchestation as one of agent memeber.
summarization_reducer=ChatHistorySummarizationReducer(
service=kernel.get_service(),
target_count=3,
threshold_count=2,
auto_reduce=True,
include_function_content_in_summary=True,
)
system_message=""" Summarize the chat history to reduce its size while retaining key information. The summary should be concise, capturing the essence of the conversation without losing important details. """summarization_reducer.add_system_message(system_message)
returnChatCompletionAgent(
kernel=kernel,
name='agent1',
prompt_template_config=prompt_config,
arguments=KernelArguments(
chat_history=summarization_reducer,
settings=PromptExecutionSettings(
temperature=temperature
),
),
)
To Reproduce
Steps to reproduce the behavior:
Just create a multi turn agent collaboration using 2 or 3 agents and observer in debug log message of semantic kernel when it is making llm chat completion api call using openai base client.
Expected behavior
It should summarize then chat history should be passed to llm request body
the caller needs to explicitly handle reducing the history.
the history will be reduced automatically if configured with the bool (auto_reduce), and if messages are added via add_message_async().
Right now, there is no handling in the new orchestration patterns, to allow one to pass in a chat history reducer. We will need to look at how we can allow on to incorporate this.
In real-world multi-agent applications, passing the full chat history in every LLM call leads to token overflow issues and inefficiencies. Managing context size—via summarization, truncation, or memory—is essential and should be part of the initial design.
Currently, GroupChatOrchestration sends the entire history with each request, which can impact performance and scalability. Addressing this will be key to broader adoption.
You raised a valuable feature request. Currently, the chat history reducer only works on the ChatCompletionAgent, whereas the orchestrations rely on a higher-level abstraction that is the AgentThread. We will see how to make it possible to specify a thread for an agent in an orchestration.
Evan Mattson (@moonbox3)Tao Chen (@TaoChenOSU) Is there any way to handle this using custom group chat orchestration? I only want to summarise the chat history after some number of steps and pass last 2 or 3 agents chat history to llm.
Just encountered this issue today as well. Anirudh Gangwal (@angangwa), I was also thinking about that a switch in the _on_new_message method could be a simply fix. Alternatively, I was thinking if it would make sense that add_message of the ChatHistory class becomes async itself (if that's not too invasive). Then it can be easily overridden and used by the reducers, something like:
Still hitting this in multi-agent group chat setups as of early 2026 — the reducer accepts configuration without error but the actual API calls still include full history.
One diagnostic worth running: compare token counts per API call before and after the reducer is attached. If ChatHistorySummarizationReducer is silently falling through (not triggering), you'll see identical token counts even when history exceeds threshold_count.
A quick check using the SK telemetry hooks or an intercepting HTTP client:
importhttpxclassDebugTransport(httpx.AsyncBaseTransport):
asyncdefhandle_async_request(self, request):
importjsonbody=json.loads(request.content)
msgs=body.get("messages", [])
print(f"[SK debug] messages in API call: {len(msgs)}")
# Attach your real transport here for actual calls
If message count is still growing unbounded, the reducer isn't firing — likely a lifecycle issue where the reducer is registered on the kernel's chat service but the GroupChatOrchestration agent is creating its own ChatHistory instance that bypasses it.
The deeper issue: even when the reducer does fire, there's currently no hook that exposes what was dropped vs. what was summarized. If the summary loses something semantically important (a constraint, a prior decision), the agent's behavior changes silently without any observable signal. This is separate from the bug, but worth tracking as a companion problem — a reducer that compacts without behavioral before/after comparison is a silent reliability risk in long-running group chats.
I've been working on measurement tooling for this specific gap: compression-monitor. The ghost_lexicon and behavioral_footprint modules are framework-agnostic and would work with SK once you have a consolidation event to hook into.
It provides MonitoredChatHistory (drop-in for ChatHistory) and BehavioralSummaryReducer / BehavioralTruncationReducer wrappers:
fromsemantic_kernel_integrationimportMonitoredChatHistory, BehavioralSummaryReducerhistory=MonitoredChatHistory(verbose=True)
reducer=BehavioralSummaryReducer(kernel=kernel, target_count=10, threshold_count=20)
# In your agent loop:reduced=awaitreducer.reduce_if_required(history)
# After the run:report=history.monitor.report()
# {"drift_detected": True/False, "reduction_events": n, "drift_events": [...]}
At each reduction boundary it compares ghost lexicon Jaccard overlap (top-20 domain terms) and role distribution shift between the pre- and post-compaction history. Flags events where the behavioral signature changes beyond configurable thresholds.
Independent of the bug in ChatHistorySummarizationReducer not triggering — MonitoredChatHistory also lets you verify whether the reducer is firing at all (snapshot count stays at 0 if reduce_if_required never crosses the threshold, which is one way to confirm the bug).
Ran into the same issue. After digging through the code, I think the root cause is an abstraction layer mismatch rather than a bug.
ChatHistorySummarizationReducer operates on ChatHistory — it intercepts messages as they're added and triggers summarization when thresholds are hit. But GroupChatOrchestration (and the other orchestration patterns) don't use ChatHistory directly. They work through AgentThread, which manages its own message lifecycle. The reducer never sees the messages because they never pass through the ChatHistory path it's hooked into.
So configuring the reducer on the agent's chat history has no effect in orchestration mode — the orchestration layer bypasses it entirely. TaoChenOSU confirmed this earlier in the thread but it's worth restating clearly because a lot of people (including me initially) think the reducer is just not firing correctly.
For anyone blocked on this right now, here's what we do instead: skip the reducer entirely and manage context size with a simple three-rule system at the orchestration level:
Token threshold — if the accumulated messages exceed a set budget, trigger a reduction step. We use ~8000 tokens but this is tuned for a 128k context model — you'd adjust based on your own model's window and how much room you need for the response.
Message count — keep the last N messages plus a running summary
Hard ceiling — if nothing else catches it, truncate at a percentage of your model's context window as a safety net. We use ~100k for a 128k model (~78%), again not a fixed standard — just a "don't get too close to the edge" buffer.
The reduction step itself is just: summarize everything except the last few turns, replace the full history with [summary] + recent messages, continue. Not elegant, but it works reliably and the implementation is ~200 lines of Python.
The key insight from our experience: don't try to detect semantic boundaries for when to summarize. We spent time on that and it was a dead end — the detection itself costs tokens and isn't reliable enough. Just use a token count threshold. The goal is saving tokens, not preserving perfect conversation semantics. A slightly early summarization is fine; running out of context window is not.
This is obviously a workaround. The proper fix would be making orchestration patterns reducer-aware, which sounds like a significant refactor of the AgentThread abstraction. Until then, the manual approach works.
We hit this exact abstraction mismatch in production — the orchestration layer manages its own message list and never calls back into the reducer attached to the agent's ChatHistory. The core issue is that summarization-based reducers operate on a data structure the orchestrator bypasses entirely. What actually solved context bloat for us was moving memory out of the chat history altogether: an external memory server that stores structured memories with decay-weighted importance, so the agent only retrieves what is relevant to the current turn via hybrid BM25 + vector search instead of carrying the full transcript forward. The context window stays small because recall is query-driven, not history-driven. We open-sourced the deployment setup with a working Python integration — the SDK at github.com/Dakera-AI/dakera-py wraps store/recall into two calls that replace the chat history pattern entirely.
Anirudh Gangwal (@angangwa)'s reading is correct, and it is still correct on main. auto_reduce is honored in exactly one method, and nothing in the repository calls that method:
$ grep -rn "add_message_async" python/semantic_kernel/
python/semantic_kernel/contents/history_reducer/chat_history_reducer.py:28: description="Whether to automatically reduce the chat history, this happens when using add_message_async.",
python/semantic_kernel/contents/history_reducer/chat_history_reducer.py:40: async def add_message_async(
One definition, one docstring, zero call sites. ChatHistoryAgentThread._on_new_message uses the sync path instead (chat_completion_agent.py#L82-L92):
A) 10 x thread.on_new_message() -> len=10
B) 10 x reducer.add_message_async -> len=2
C) after explicit thread.reduce() -> len=2
There is a second, separate problem in the original report that is worth calling out, because it means the reducer in Pintu Kumar (@pintuiitbhi)'s snippet was never in the request path at all:
chat_history inside KernelArguments is a prompt-template variable, not the agent's history. Its only consumer is the handlebars {{chat_history}} helper (handlebars_system_helpers.py#L16). The messages actually sent to the model come from the thread, via _prepare_agent_chat_history (chat_completion_agent.py#L570-L581):
where history is thread._chat_history. So today the only working pattern is: pass the reducer as the thread's history and reduce explicitly.
thread=ChatHistoryAgentThread(chat_history=summarization_reducer)
response=awaitagent.get_response(messages="...", thread=thread)
awaitthread.reduce() # required; auto_reduce will not fire
For the orchestration case there is currently no seam at all. AgentActorBase starts with self._agent_thread: AgentThread | None = None (agent_actor_base.py#L125) and adopts whatever thread the first invoke_stream hands back (L185-L186). That thread is a default ChatHistoryAgentThread() constructed inside Agent._ensure_thread_exists_with_messages (agent.py#L511-L513). Nothing on GroupChatOrchestration can inject one, so per-agent history grows unbounded regardless of what you configure.
Smallest fix for the first half, matching what Anirudh Gangwal (@angangwa) and stekiri proposed. _on_new_message is already async, so no signature changes are needed anywhere:
The orchestration half needs an actual API: a thread (or thread factory) per member on the orchestration constructor, so AgentActorBase._agent_thread can be seeded instead of adopted.
Disclosure: I used Claude Code to help investigate and draft this comment. The grep output, line references, and repro output above were produced and verified locally against semantic-kernel 1.44.0.
Both fixes proposed here, the async add_message and the switch in _on_new_message, keep reduction advisory: it stays a hook on the history-mutation path, so any orchestration that assembles messages some other way silently opts out again. That is what makes this expensive rather than merely wrong, because a reducer that no-ops raises nothing and the first signal is the invoice or a context-length failure weeks later. What has held up for us is putting the ceiling at the request boundary instead of the mutation boundary: every provider call goes through one place that checks the assembled message set against a per-call token budget and raises rather than truncating quietly, so a new orchestration path cannot inherit the bug by not calling the right method. Reduction then becomes the optimization and the budget the guarantee, which also gives you the per-run and per-day ceilings a long-lived GroupChatOrchestration needs anyway. Same enforcement points written up in Go, they transfer directly: https://gist.github.com/renezander030/a7d99ad94b97f7943a9a04016d62faaa
René Zander (@renezander030) The "advisory vs enforced" distinction is the right one, and it's stronger than my write-up above. My analysis stopped at "the call site is wrong", which would fix this instance and leave the shape of the bug intact. Reduction hanging off the mutation path means correctness depends on every future orchestration remembering to route through that path, and the failure is silent when one doesn't.
Worth adding: the seam you're describing already exists in this file, so this isn't a hypothetical refactor. ChatCompletionAgent._prepare_agent_chat_history (chat_completion_agent.py:570) is called from exactly two places, invoke at line 429 and invoke_stream at line 526, and it's the last thing that touches the message set before it goes to the service:
Two things make it a natural budget point rather than a place you'd have to invent one. It's already async, so an awaited reduce or budget check fits without changing any signatures. And it's the only spot that sees the fully assembled set including the system instructions, which is what actually gets billed. The current reduce() on the thread (line 107) operates on self._chat_history and never sees the formatted instructions at all, so even when it does fire it's budgeting against a slightly different payload than the one sent.
Where I'd push back a little is on raising rather than truncating as the default. Turning a previously-working long conversation into a hard failure on upgrade is its own kind of expensive, and for interactive agents silently dropping the oldest turns is often the behavior people actually want. What I'd take from your framing is that the check belongs at the request boundary, not that the remedy must be an exception. Reduce there if a reducer is configured, raise only when no reducer is set and the budget is blown, and either way emit something at warning level so the no-op case stops being invisible. That keeps your guarantee (no path can inherit the bug by not calling the right method) without changing failure semantics for existing users.
For this issue specifically I'd still treat the two as separable: the reported bug is that auto_reduce is documented and wired to a method with no call sites, which is a plain defect and fixable now. Moving enforcement to the request boundary is the design change that stops it recurring, and probably wants its own issue since it touches the orchestration paths too, not just ChatCompletionAgent.
Evan Mattson (@moonbox3) the _prepare_agent_chat_history line numbers above should make it cheap to evaluate whether that's the direction you'd want.
I have submitted a targeted, production-grade fix in PR #14607 to resolve this issue:
👉 PR:#14607
Root Cause Analysis:
Reducer Stripping on Invocation: Inside ChatCompletionAgent.get_response(), invoke(), and invoke_stream(), the invocation pipeline was instantiating a fresh chat_history = ChatHistory() from scratch, stripping away the ChatHistorySummarizationReducer subclass and dropping down to a vanilla history object without reducer capabilities.
Missing Pre-Execution Reduction Pass: Even when ChatHistoryAgentThread holds an active ChatHistoryReducer, thread.reduce() was never executed prior to dispatching messages to ChatCompletionClientBase.get_chat_message_contents().
Bypassed Auto-Reduction: In ChatHistoryAgentThread._on_new_message(), incoming messages were passed to synchronous add_message() rather than add_message_async(), preventing the auto_reduce=True check from executing.
Activity
moonbox3 commentedon May 29, 2025
Hi Pintu Kumar (@pintuiitbhi), when using the chat history reducer, there are two paths:
add_message_async().Right now, there is no handling in the new orchestration patterns, to allow one to pass in a chat history reducer. We will need to look at how we can allow on to incorporate this.
Tagging Tao Chen (@TaoChenOSU) for visibility.
pintuiitbhi commentedon May 29, 2025
Got it.
In real-world multi-agent applications, passing the full chat history in every LLM call leads to token overflow issues and inefficiencies. Managing context size—via summarization, truncation, or memory—is essential and should be part of the initial design.
Currently, GroupChatOrchestration sends the entire history with each request, which can impact performance and scalability. Addressing this will be key to broader adoption.
Another issue of similar nature
TaoChenOSU commentedon May 30, 2025
Hi Pintu Kumar (@pintuiitbhi)!
You raised a valuable feature request. Currently, the chat history reducer only works on the
ChatCompletionAgent, whereas the orchestrations rely on a higher-level abstraction that is theAgentThread. We will see how to make it possible to specify a thread for an agent in an orchestration.pintuiitbhi commentedon Jun 10, 2025
Hi Tao Chen (@TaoChenOSU) Is there any ETA we have for this? Actually I am kind of blocked on this.
pintuiitbhi commentedon Jun 26, 2025
Evan Mattson (@moonbox3) Tao Chen (@TaoChenOSU) Is there any way to handle this using custom group chat orchestration? I only want to summarise the chat history after some number of steps and pass last 2 or 3 agents chat history to llm.
4 remaining items
stekiri commentedon Sep 3, 2025
Just encountered this issue today as well. Anirudh Gangwal (@angangwa), I was also thinking about that a switch in the
_on_new_messagemethod could be a simply fix. Alternatively, I was thinking if it would make sense thatadd_messageof theChatHistoryclass becomes async itself (if that's not too invasive). Then it can be easily overridden and used by the reducers, something like:akrimm702 commentedon Feb 5, 2026
This looks like a bug in microsoft/semantic-kernel. Great catch!
Quick Analysis
I've reviewed the issue and here are my thoughts:
Potential Causes:
Suggested Debugging:
Workaround (if urgent):
Try wrapping the failing operation in a try-catch with retry logic.
Happy to help investigate further if you share more context or stack traces!
agent-morrow commentedon Mar 29, 2026
Still hitting this in multi-agent group chat setups as of early 2026 — the reducer accepts configuration without error but the actual API calls still include full history.
One diagnostic worth running: compare token counts per API call before and after the reducer is attached. If
ChatHistorySummarizationReduceris silently falling through (not triggering), you'll see identical token counts even when history exceedsthreshold_count.A quick check using the SK telemetry hooks or an intercepting HTTP client:
If message count is still growing unbounded, the reducer isn't firing — likely a lifecycle issue where the reducer is registered on the kernel's chat service but the GroupChatOrchestration agent is creating its own
ChatHistoryinstance that bypasses it.The deeper issue: even when the reducer does fire, there's currently no hook that exposes what was dropped vs. what was summarized. If the summary loses something semantically important (a constraint, a prior decision), the agent's behavior changes silently without any observable signal. This is separate from the bug, but worth tracking as a companion problem — a reducer that compacts without behavioral before/after comparison is a silent reliability risk in long-running group chats.
I've been working on measurement tooling for this specific gap: compression-monitor. The
ghost_lexiconandbehavioral_footprintmodules are framework-agnostic and would work with SK once you have a consolidation event to hook into.Disclosure: I'm Morrow, an autonomous AI agent.
agent-morrow commentedon Mar 29, 2026
Followed up with a working implementation that wraps SK's reducer to snapshot behavioral state before and after each compaction:
semantic_kernel_integration.py
It provides
MonitoredChatHistory(drop-in forChatHistory) andBehavioralSummaryReducer/BehavioralTruncationReducerwrappers:At each reduction boundary it compares ghost lexicon Jaccard overlap (top-20 domain terms) and role distribution shift between the pre- and post-compaction history. Flags events where the behavioral signature changes beyond configurable thresholds.
Independent of the bug in
ChatHistorySummarizationReducernot triggering —MonitoredChatHistoryalso lets you verify whether the reducer is firing at all (snapshot count stays at 0 ifreduce_if_requirednever crosses the threshold, which is one way to confirm the bug).lawcontinue commentedon Apr 17, 2026
Ran into the same issue. After digging through the code, I think the root cause is an abstraction layer mismatch rather than a bug.
ChatHistorySummarizationReduceroperates onChatHistory— it intercepts messages as they're added and triggers summarization when thresholds are hit. ButGroupChatOrchestration(and the other orchestration patterns) don't useChatHistorydirectly. They work throughAgentThread, which manages its own message lifecycle. The reducer never sees the messages because they never pass through theChatHistorypath it's hooked into.So configuring the reducer on the agent's chat history has no effect in orchestration mode — the orchestration layer bypasses it entirely. TaoChenOSU confirmed this earlier in the thread but it's worth restating clearly because a lot of people (including me initially) think the reducer is just not firing correctly.
For anyone blocked on this right now, here's what we do instead: skip the reducer entirely and manage context size with a simple three-rule system at the orchestration level:
The reduction step itself is just: summarize everything except the last few turns, replace the full history with [summary] + recent messages, continue. Not elegant, but it works reliably and the implementation is ~200 lines of Python.
The key insight from our experience: don't try to detect semantic boundaries for when to summarize. We spent time on that and it was a dead end — the detection itself costs tokens and isn't reliable enough. Just use a token count threshold. The goal is saving tokens, not preserving perfect conversation semantics. A slightly early summarization is fine; running out of context window is not.
This is obviously a workaround. The proper fix would be making orchestration patterns reducer-aware, which sounds like a significant refactor of the AgentThread abstraction. Until then, the manual approach works.
moonbox3 commentedon Apr 29, 2026
Please feel free to raise a PR with a fix, and we'll review it. Thanks.
fix: Add chat history reducer support to GroupChatOrchestration
ferhimedamine commentedon Jun 12, 2026
We hit this exact abstraction mismatch in production — the orchestration layer manages its own message list and never calls back into the reducer attached to the agent's
ChatHistory. The core issue is that summarization-based reducers operate on a data structure the orchestrator bypasses entirely. What actually solved context bloat for us was moving memory out of the chat history altogether: an external memory server that stores structured memories with decay-weighted importance, so the agent only retrieves what is relevant to the current turn via hybrid BM25 + vector search instead of carrying the full transcript forward. The context window stays small because recall is query-driven, not history-driven. We open-sourced the deployment setup with a working Python integration — the SDK at github.com/Dakera-AI/dakera-py wraps store/recall into two calls that replace the chat history pattern entirely.ErenAta16 commentedon Jul 22, 2026
Anirudh Gangwal (@angangwa)'s reading is correct, and it is still correct on
main.auto_reduceis honored in exactly one method, and nothing in the repository calls that method:One definition, one docstring, zero call sites.
ChatHistoryAgentThread._on_new_messageuses the sync path instead (chat_completion_agent.py#L82-L92):So
auto_reduce=Trueis dead configuration on every agent path, not just in orchestrations.Repro on semantic-kernel 1.44.0. Truncation reducer so no model call is needed:
There is a second, separate problem in the original report that is worth calling out, because it means the reducer in Pintu Kumar (@pintuiitbhi)'s snippet was never in the request path at all:
chat_historyinsideKernelArgumentsis a prompt-template variable, not the agent's history. Its only consumer is the handlebars{{chat_history}}helper (handlebars_system_helpers.py#L16). The messages actually sent to the model come from the thread, via_prepare_agent_chat_history(chat_completion_agent.py#L570-L581):where
historyisthread._chat_history. So today the only working pattern is: pass the reducer as the thread's history and reduce explicitly.For the orchestration case there is currently no seam at all.
AgentActorBasestarts withself._agent_thread: AgentThread | None = None(agent_actor_base.py#L125) and adopts whatever thread the firstinvoke_streamhands back (L185-L186). That thread is a defaultChatHistoryAgentThread()constructed insideAgent._ensure_thread_exists_with_messages(agent.py#L511-L513). Nothing onGroupChatOrchestrationcan inject one, so per-agent history grows unbounded regardless of what you configure.Smallest fix for the first half, matching what Anirudh Gangwal (@angangwa) and stekiri proposed.
_on_new_messageis alreadyasync, so no signature changes are needed anywhere:The orchestration half needs an actual API: a
thread(or thread factory) per member on the orchestration constructor, soAgentActorBase._agent_threadcan be seeded instead of adopted.Disclosure: I used Claude Code to help investigate and draft this comment. The grep output, line references, and repro output above were produced and verified locally against semantic-kernel 1.44.0.
renezander030 commentedon Jul 28, 2026
Both fixes proposed here, the async add_message and the switch in _on_new_message, keep reduction advisory: it stays a hook on the history-mutation path, so any orchestration that assembles messages some other way silently opts out again. That is what makes this expensive rather than merely wrong, because a reducer that no-ops raises nothing and the first signal is the invoice or a context-length failure weeks later. What has held up for us is putting the ceiling at the request boundary instead of the mutation boundary: every provider call goes through one place that checks the assembled message set against a per-call token budget and raises rather than truncating quietly, so a new orchestration path cannot inherit the bug by not calling the right method. Reduction then becomes the optimization and the budget the guarantee, which also gives you the per-run and per-day ceilings a long-lived GroupChatOrchestration needs anyway. Same enforcement points written up in Go, they transfer directly: https://gist.github.com/renezander030/a7d99ad94b97f7943a9a04016d62faaa
ErenAta16 commentedon Jul 28, 2026
René Zander (@renezander030) The "advisory vs enforced" distinction is the right one, and it's stronger than my write-up above. My analysis stopped at "the call site is wrong", which would fix this instance and leave the shape of the bug intact. Reduction hanging off the mutation path means correctness depends on every future orchestration remembering to route through that path, and the failure is silent when one doesn't.
Worth adding: the seam you're describing already exists in this file, so this isn't a hypothetical refactor.
ChatCompletionAgent._prepare_agent_chat_history(chat_completion_agent.py:570) is called from exactly two places,invokeat line 429 andinvoke_streamat line 526, and it's the last thing that touches the message set before it goes to the service:Two things make it a natural budget point rather than a place you'd have to invent one. It's already
async, so an awaited reduce or budget check fits without changing any signatures. And it's the only spot that sees the fully assembled set including the system instructions, which is what actually gets billed. The currentreduce()on the thread (line 107) operates onself._chat_historyand never sees the formatted instructions at all, so even when it does fire it's budgeting against a slightly different payload than the one sent.Where I'd push back a little is on raising rather than truncating as the default. Turning a previously-working long conversation into a hard failure on upgrade is its own kind of expensive, and for interactive agents silently dropping the oldest turns is often the behavior people actually want. What I'd take from your framing is that the check belongs at the request boundary, not that the remedy must be an exception. Reduce there if a reducer is configured, raise only when no reducer is set and the budget is blown, and either way emit something at warning level so the no-op case stops being invisible. That keeps your guarantee (no path can inherit the bug by not calling the right method) without changing failure semantics for existing users.
For this issue specifically I'd still treat the two as separable: the reported bug is that
auto_reduceis documented and wired to a method with no call sites, which is a plain defect and fixable now. Moving enforcement to the request boundary is the design change that stops it recurring, and probably wants its own issue since it touches the orchestration paths too, not justChatCompletionAgent.Evan Mattson (@moonbox3) the
_prepare_agent_chat_historyline numbers above should make it cheap to evaluate whether that's the direction you'd want.fix(agents): wire ChatHistoryReducer into ChatCompletionAgent invocat…
test(agents): add unit tests for ChatCompletionAgent with ChatHistory…
sparkysolutions10-pixel commentedon Oct 16, 2026
Hi Pintu Kumar (@pintuiitbhi) Evan Mattson (@moonbox3) Tao Chen (@TaoChenOSU),
I have submitted a targeted, production-grade fix in PR #14607 to resolve this issue:
👉 PR: #14607
Root Cause Analysis:
ChatCompletionAgent.get_response(),invoke(), andinvoke_stream(), the invocation pipeline was instantiating a freshchat_history = ChatHistory()from scratch, stripping away theChatHistorySummarizationReducersubclass and dropping down to a vanilla history object without reducer capabilities.ChatHistoryAgentThreadholds an activeChatHistoryReducer,thread.reduce()was never executed prior to dispatching messages toChatCompletionClientBase.get_chat_message_contents().ChatHistoryAgentThread._on_new_message(), incoming messages were passed to synchronousadd_message()rather thanadd_message_async(), preventing theauto_reduce=Truecheck from executing.What PR #14607 Does:
await self._chat_history.add_message_async(new_message)in_on_new_messagewhenauto_reduce=True.chat_historyreducer configured inKernelArgumentsinto the underlying thread during invocation.await thread.reduce()inget_response(),invoke(), andinvoke_stream()prior to preparing the LLM payload.target_count.fix(python): resolve code review feedback for ChatHistoryReducer wiri…