From ae3896baff84c59873d957b10b844a6c3e19f814 Mon Sep 17 00:00:00 2001 From: westey <164392973+westey-m@users.noreply.github.com> Date: Fri, 20 Feb 2026 18:13:50 +0000 Subject: [PATCH 1/3] Create a sample to show bounded chat history with overflow into chat history memory --- dotnet/agent-framework-dotnet.slnx | 1 + ...ithMemory_Step05_BoundedChatHistory.csproj | 22 ++++ .../BoundedChatHistoryProvider.cs | 119 ++++++++++++++++++ .../Program.cs | 79 ++++++++++++ .../README.md | 40 ++++++ .../TruncatingChatReducer.cs | 65 ++++++++++ .../GettingStarted/AgentWithMemory/README.md | 1 + 7 files changed, 327 insertions(+) create mode 100644 dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/AgentWithMemory_Step05_BoundedChatHistory.csproj create mode 100644 dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/BoundedChatHistoryProvider.cs create mode 100644 dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/Program.cs create mode 100644 dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/README.md create mode 100644 dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/TruncatingChatReducer.cs diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index f01cd409d6..6148c7bbba 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -139,6 +139,7 @@ + diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/AgentWithMemory_Step05_BoundedChatHistory.csproj b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/AgentWithMemory_Step05_BoundedChatHistory.csproj new file mode 100644 index 0000000000..860089b621 --- /dev/null +++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/AgentWithMemory_Step05_BoundedChatHistory.csproj @@ -0,0 +1,22 @@ + + + + Exe + net10.0 + + enable + enable + + + + + + + + + + + + + + diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/BoundedChatHistoryProvider.cs b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/BoundedChatHistoryProvider.cs new file mode 100644 index 0000000000..5776832958 --- /dev/null +++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/BoundedChatHistoryProvider.cs @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Agents.AI; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.VectorData; + +namespace SampleApp; + +/// +/// A that keeps a bounded window of recent messages in session state +/// (via ) and overflows older messages to a vector store +/// (via ). When providing chat history, it searches the vector +/// store for relevant older messages and prepends them as a memory context message. +/// +internal sealed class BoundedChatHistoryProvider : ChatHistoryProvider, IDisposable +{ + private readonly InMemoryChatHistoryProvider _inMemoryProvider; + private readonly ChatHistoryMemoryProvider _memoryProvider; + private readonly TruncatingChatReducer _reducer; + private readonly string _contextPrompt; + + /// + /// Initializes a new instance of the class. + /// + /// The maximum number of non-system messages to keep in session state before overflowing to the vector store. + /// The vector store to use for storing and retrieving overflow chat history. + /// The name of the collection for storing overflow chat history in the vector store. + /// The number of dimensions to use for the chat history vector store embeddings. + /// A delegate that initializes the memory provider state, providing the storage and search scopes. + /// Optional prompt to prefix memory search results. Defaults to a standard memory context prompt. + public BoundedChatHistoryProvider( + int maxSessionMessages, + VectorStore vectorStore, + string collectionName, + int vectorDimensions, + Func stateInitializer, + string? contextPrompt = null) + { + this._reducer = new TruncatingChatReducer(maxSessionMessages); + this._inMemoryProvider = new InMemoryChatHistoryProvider(new InMemoryChatHistoryProviderOptions + { + ChatReducer = this._reducer, + ReducerTriggerEvent = InMemoryChatHistoryProviderOptions.ChatReducerTriggerEvent.AfterMessageAdded, + StorageInputMessageFilter = msgs => msgs, + }); + this._memoryProvider = new ChatHistoryMemoryProvider( + vectorStore, + collectionName, + vectorDimensions, + stateInitializer, + options: new ChatHistoryMemoryProviderOptions + { + SearchInputMessageFilter = msgs => msgs, + StorageInputMessageFilter = msgs => msgs, + }); + this._contextPrompt = contextPrompt + ?? "The following are memories from earlier in this conversation. Use them to inform your responses:"; + } + + /// + protected override async ValueTask> ProvideChatHistoryAsync( + InvokingContext context, + CancellationToken cancellationToken = default) + { + // Delegate to the inner provider's full lifecycle (retrieve, filter, stamp, merge with request messages). + var allMessages = await this._inMemoryProvider.InvokingAsync(context, cancellationToken).ConfigureAwait(false); + + // Search the vector store for relevant older messages. + var aiContext = new AIContext { Messages = context.RequestMessages.ToList() }; + var invokingContext = new AIContextProvider.InvokingContext( + context.Agent, context.Session, aiContext); + + var result = await this._memoryProvider.InvokingAsync(invokingContext, cancellationToken).ConfigureAwait(false); + + // Extract only the messages added by the memory provider (stamped with AIContextProvider source type). + var memoryMessages = result.Messages? + .Where(m => m.GetAgentRequestMessageSourceType() == AgentRequestMessageSourceType.AIContextProvider) + .ToList(); + + if (memoryMessages is { Count: > 0 }) + { + var memoryText = string.Join("\n", memoryMessages.Select(m => m.Text).Where(t => !string.IsNullOrWhiteSpace(t))); + + if (!string.IsNullOrWhiteSpace(memoryText)) + { + var contextMessage = new ChatMessage(ChatRole.User, $"{this._contextPrompt}\n{memoryText}"); + return new[] { contextMessage }.Concat(allMessages); + } + } + + return allMessages; + } + + /// + protected override async ValueTask StoreChatHistoryAsync( + InvokedContext context, + CancellationToken cancellationToken = default) + { + // Delegate storage to the in-memory provider. Its TruncatingChatReducer (AfterMessageAdded trigger) + // will automatically truncate to the configured maximum and expose any removed messages. + var innerContext = new InvokedContext( + context.Agent, context.Session, context.RequestMessages, context.ResponseMessages!); + await this._inMemoryProvider.InvokedAsync(innerContext, cancellationToken).ConfigureAwait(false); + + // Archive any messages that the reducer removed to the vector store. + if (this._reducer.RemovedMessages is { Count: > 0 }) + { + var overflowContext = new AIContextProvider.InvokedContext( + context.Agent, context.Session, this._reducer.RemovedMessages, []); + await this._memoryProvider.InvokedAsync(overflowContext, cancellationToken).ConfigureAwait(false); + } + } + + /// + public void Dispose() + { + this._memoryProvider.Dispose(); + } +} diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/Program.cs b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/Program.cs new file mode 100644 index 0000000000..ab3a0376eb --- /dev/null +++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/Program.cs @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft. All rights reserved. + +// This sample shows how to create a bounded chat history provider that keeps a configurable number of +// recent messages in session state and automatically overflows older messages to a vector store. +// When the agent is invoked, it searches the vector store for relevant older messages and +// prepends them as a "memory" context message before the recent session history. + +using Azure.AI.OpenAI; +using Azure.Identity; +using Microsoft.Agents.AI; +using Microsoft.Extensions.AI; +using Microsoft.Extensions.VectorData; +using Microsoft.SemanticKernel.Connectors.InMemory; +using OpenAI.Chat; +using SampleApp; + +var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set."); +var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini"; +var embeddingDeploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME") ?? "text-embedding-3-large"; + +// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production. +// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid +// latency issues, unintended credential probing, and potential security risks from fallback mechanisms. +var credential = new DefaultAzureCredential(); + +// Create a vector store to store overflow chat messages. +// For demonstration purposes, we are using an in-memory vector store. +// Replace this with a persistent vector store implementation for production scenarios. +VectorStore vectorStore = new InMemoryVectorStore(new InMemoryVectorStoreOptions() +{ + EmbeddingGenerator = new AzureOpenAIClient(new Uri(endpoint), credential) + .GetEmbeddingClient(embeddingDeploymentName) + .AsIEmbeddingGenerator() +}); + +var sessionId = Guid.NewGuid().ToString(); + +// Create the BoundedChatHistoryProvider with a maximum of 4 non-system messages in session state. +// It internally creates an InMemoryChatHistoryProvider with a TruncatingChatReducer and a +// ChatHistoryMemoryProvider with the correct configuration to ensure overflow messages are +// automatically archived to the vector store and recalled via semantic search. +var boundedProvider = new BoundedChatHistoryProvider( + maxSessionMessages: 4, + vectorStore, + collectionName: "chathistory-overflow", + vectorDimensions: 3072, + session => new ChatHistoryMemoryProvider.State( + storageScope: new() { UserId = "UID1", SessionId = sessionId }, + searchScope: new() { UserId = "UID1" })); + +// Create the agent with the bounded chat history provider. +AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), credential) + .GetChatClient(deploymentName) + .AsAIAgent(new ChatClientAgentOptions + { + ChatOptions = new() { Instructions = "You are a helpful assistant. Answer questions concisely." }, + Name = "Assistant", + ChatHistoryProvider = boundedProvider, + }); + +// Start a conversation. The first several exchanges will fill up the session state window. +AgentSession session = await agent.CreateSessionAsync(); + +Console.WriteLine("--- Filling the session window (4 messages max) ---\n"); + +Console.WriteLine(await agent.RunAsync("My favorite color is blue.", session)); +Console.WriteLine(await agent.RunAsync("I have a dog named Max.", session)); + +// At this point the session state holds 4 messages (2 user + 2 assistant). +// The next exchange will push the oldest messages into the vector store. +Console.WriteLine("\n--- Next exchange will trigger overflow to vector store ---\n"); + +Console.WriteLine(await agent.RunAsync("What is the capital of France?", session)); + +// The oldest messages about favorite color have now been archived to the vector store. +// Ask the agent something that requires recalling the overflowed information. +Console.WriteLine("\n--- Asking about overflowed information (should recall from vector store) ---\n"); + +Console.WriteLine(await agent.RunAsync("What is my favorite color?", session)); diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/README.md b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/README.md new file mode 100644 index 0000000000..69dd1af17b --- /dev/null +++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/README.md @@ -0,0 +1,40 @@ +# Bounded Chat History with Vector Store Overflow + +This sample demonstrates how to create a custom `ChatHistoryProvider` that keeps a bounded window of recent messages in session state and automatically overflows older messages to a vector store. When the agent is invoked, it searches the vector store for relevant older messages and prepends them as memory context. + +## Concepts + +- **`TruncatingChatReducer`**: A custom `IChatReducer` that keeps the most recent N messages and exposes removed messages via a `RemovedMessages` property. +- **`BoundedChatHistoryProvider`**: A custom `ChatHistoryProvider` that composes: + - `InMemoryChatHistoryProvider` for fast session-state storage (bounded by the reducer) + - `ChatHistoryMemoryProvider` for vector-store overflow and semantic search of older messages + +## Prerequisites + +- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) +- An Azure OpenAI resource with: + - A chat deployment (e.g., `gpt-4o-mini`) + - An embedding deployment (e.g., `text-embedding-3-large`) + +## Configuration + +Set the following environment variables: + +| Variable | Description | Default | +|---|---|---| +| `AZURE_OPENAI_ENDPOINT` | Your Azure OpenAI endpoint URL | *(required)* | +| `AZURE_OPENAI_DEPLOYMENT_NAME` | Chat model deployment name | `gpt-4o-mini` | +| `AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME` | Embedding model deployment name | `text-embedding-3-large` | + +## Running the Sample + +```bash +dotnet run +``` + +## How it Works + +1. The agent starts a conversation with a bounded session window of 4 messages. +2. As messages accumulate beyond the limit, the `TruncatingChatReducer` removes the oldest messages. +3. The `BoundedChatHistoryProvider` detects the removed messages and stores them in a vector store via `ChatHistoryMemoryProvider`. +4. On subsequent invocations, the provider searches the vector store for relevant older messages and prepends them as memory context, allowing the agent to recall information from earlier in the conversation. diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/TruncatingChatReducer.cs b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/TruncatingChatReducer.cs new file mode 100644 index 0000000000..b32df40dd7 --- /dev/null +++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/TruncatingChatReducer.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.AI; + +namespace SampleApp; + +/// +/// A truncating chat reducer that keeps the most recent messages up to a configured maximum, +/// preserving any leading system message. Removed messages are exposed via +/// so that a caller can archive them (e.g. to a vector store). +/// +internal sealed class TruncatingChatReducer : IChatReducer +{ + private readonly int _maxMessages; + + /// + /// Initializes a new instance of the class. + /// + /// The maximum number of non-system messages to retain. + public TruncatingChatReducer(int maxMessages) + { + this._maxMessages = maxMessages > 0 ? maxMessages : throw new ArgumentOutOfRangeException(nameof(maxMessages)); + } + + /// + /// Gets the messages that were removed during the most recent call to . + /// + public IReadOnlyList RemovedMessages { get; private set; } = []; + + /// + public Task> ReduceAsync(IEnumerable messages, CancellationToken cancellationToken) + { + _ = messages ?? throw new ArgumentNullException(nameof(messages)); + + ChatMessage? systemMessage = null; + Queue retained = new(capacity: this._maxMessages); + List removed = []; + + foreach (var message in messages) + { + if (message.Role == ChatRole.System) + { + // Preserve the first system message outside the counting window. + systemMessage ??= message; + } + else if (!message.Contents.Any(c => c is FunctionCallContent or FunctionResultContent)) + { + if (retained.Count >= this._maxMessages) + { + removed.Add(retained.Dequeue()); + } + + retained.Enqueue(message); + } + } + + this.RemovedMessages = removed; + + IEnumerable result = systemMessage is not null + ? new[] { systemMessage }.Concat(retained) + : retained; + + return Task.FromResult(result); + } +} diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/README.md b/dotnet/samples/GettingStarted/AgentWithMemory/README.md index 6e36ba0511..1152668035 100644 --- a/dotnet/samples/GettingStarted/AgentWithMemory/README.md +++ b/dotnet/samples/GettingStarted/AgentWithMemory/README.md @@ -8,3 +8,4 @@ These samples show how to create an agent with the Agent Framework that uses Mem |[Memory with MemoryStore](./AgentWithMemory_Step02_MemoryUsingMem0/)|This sample demonstrates how to create and run an agent that uses the Mem0 service to extract and retrieve individual memories.| |[Custom Memory Implementation](./AgentWithMemory_Step03_CustomMemory/)|This sample demonstrates how to create a custom memory component and attach it to an agent.| |[Memory with Azure AI Foundry](./AgentWithMemory_Step04_MemoryUsingFoundry/)|This sample demonstrates how to create and run an agent that uses Azure AI Foundry's managed memory service to extract and retrieve individual memories.| +|[Bounded Chat History with Overflow](./AgentWithMemory_Step05_BoundedChatHistory/)|This sample demonstrates how to create a bounded chat history provider that overflows older messages to a vector store and recalls them as memories.| From 61a5a07bba1878ce1e3cf3e6c5e174aff31cdf5a Mon Sep 17 00:00:00 2001 From: westey <164392973+westey-m@users.noreply.github.com> Date: Mon, 23 Feb 2026 18:42:59 +0000 Subject: [PATCH 2/3] Address PR comments. --- .../BoundedChatHistoryProvider.cs | 9 +++++++++ .../AgentWithMemory_Step05_BoundedChatHistory/README.md | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/BoundedChatHistoryProvider.cs b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/BoundedChatHistoryProvider.cs index 5776832958..9118350a77 100644 --- a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/BoundedChatHistoryProvider.cs +++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/BoundedChatHistoryProvider.cs @@ -12,6 +12,10 @@ namespace SampleApp; /// (via ). When providing chat history, it searches the vector /// store for relevant older messages and prepends them as a memory context message. /// +/// +/// Only non-system messages are counted towards the session state limit and overflow mechanism. System messages are always retained in session state and are not included in the vector store. +/// Function calls and function results are also dropped when truncation happens, both from in-memory state, and they are also not persisted to the vector store. +/// internal sealed class BoundedChatHistoryProvider : ChatHistoryProvider, IDisposable { private readonly InMemoryChatHistoryProvider _inMemoryProvider; @@ -36,6 +40,11 @@ public BoundedChatHistoryProvider( Func stateInitializer, string? contextPrompt = null) { + if (maxSessionMessages < 0) + { + throw new ArgumentOutOfRangeException(nameof(maxSessionMessages), "maxSessionMessages must be non-negative."); + } + this._reducer = new TruncatingChatReducer(maxSessionMessages); this._inMemoryProvider = new InMemoryChatHistoryProvider(new InMemoryChatHistoryProviderOptions { diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/README.md b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/README.md index 69dd1af17b..c1e35f5a88 100644 --- a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/README.md +++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/README.md @@ -34,7 +34,7 @@ dotnet run ## How it Works -1. The agent starts a conversation with a bounded session window of 4 messages. +1. The agent starts a conversation with a bounded session window of 4 non-system, non-function messages (i.e., user/assistant turns). System messages are always preserved, and function call/result messages are truncated and not preserved. 2. As messages accumulate beyond the limit, the `TruncatingChatReducer` removes the oldest messages. 3. The `BoundedChatHistoryProvider` detects the removed messages and stores them in a vector store via `ChatHistoryMemoryProvider`. 4. On subsequent invocations, the provider searches the vector store for relevant older messages and prepends them as memory context, allowing the agent to recall information from earlier in the conversation. From ef5184679123166a2e357dc9b56184186b4781a6 Mon Sep 17 00:00:00 2001 From: westey <164392973+westey-m@users.noreply.github.com> Date: Tue, 3 Mar 2026 18:25:49 +0000 Subject: [PATCH 3/3] Address PR comment and fix bug --- .../BoundedChatHistoryProvider.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/BoundedChatHistoryProvider.cs b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/BoundedChatHistoryProvider.cs index 9118350a77..b4d6ca3072 100644 --- a/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/BoundedChatHistoryProvider.cs +++ b/dotnet/samples/02-agents/AgentWithMemory/AgentWithMemory_Step05_BoundedChatHistory/BoundedChatHistoryProvider.cs @@ -18,10 +18,11 @@ namespace SampleApp; /// internal sealed class BoundedChatHistoryProvider : ChatHistoryProvider, IDisposable { - private readonly InMemoryChatHistoryProvider _inMemoryProvider; + private readonly InMemoryChatHistoryProvider _chatHistoryProvider; private readonly ChatHistoryMemoryProvider _memoryProvider; private readonly TruncatingChatReducer _reducer; private readonly string _contextPrompt; + private IReadOnlyList? _stateKeys; /// /// Initializes a new instance of the class. @@ -46,11 +47,11 @@ public BoundedChatHistoryProvider( } this._reducer = new TruncatingChatReducer(maxSessionMessages); - this._inMemoryProvider = new InMemoryChatHistoryProvider(new InMemoryChatHistoryProviderOptions + this._chatHistoryProvider = new InMemoryChatHistoryProvider(new InMemoryChatHistoryProviderOptions { ChatReducer = this._reducer, ReducerTriggerEvent = InMemoryChatHistoryProviderOptions.ChatReducerTriggerEvent.AfterMessageAdded, - StorageInputMessageFilter = msgs => msgs, + StorageInputRequestMessageFilter = msgs => msgs, }); this._memoryProvider = new ChatHistoryMemoryProvider( vectorStore, @@ -60,19 +61,23 @@ public BoundedChatHistoryProvider( options: new ChatHistoryMemoryProviderOptions { SearchInputMessageFilter = msgs => msgs, - StorageInputMessageFilter = msgs => msgs, + StorageInputRequestMessageFilter = msgs => msgs, }); this._contextPrompt = contextPrompt ?? "The following are memories from earlier in this conversation. Use them to inform your responses:"; } + /// + public override IReadOnlyList StateKeys => this._stateKeys ??= this._chatHistoryProvider.StateKeys.Concat(this._memoryProvider.StateKeys).ToArray(); + /// protected override async ValueTask> ProvideChatHistoryAsync( InvokingContext context, CancellationToken cancellationToken = default) { // Delegate to the inner provider's full lifecycle (retrieve, filter, stamp, merge with request messages). - var allMessages = await this._inMemoryProvider.InvokingAsync(context, cancellationToken).ConfigureAwait(false); + var chatHistoryProviderInputContext = new InvokingContext(context.Agent, context.Session, []); + var allMessages = await this._chatHistoryProvider.InvokingAsync(chatHistoryProviderInputContext, cancellationToken).ConfigureAwait(false); // Search the vector store for relevant older messages. var aiContext = new AIContext { Messages = context.RequestMessages.ToList() }; @@ -109,7 +114,7 @@ protected override async ValueTask StoreChatHistoryAsync( // will automatically truncate to the configured maximum and expose any removed messages. var innerContext = new InvokedContext( context.Agent, context.Session, context.RequestMessages, context.ResponseMessages!); - await this._inMemoryProvider.InvokedAsync(innerContext, cancellationToken).ConfigureAwait(false); + await this._chatHistoryProvider.InvokedAsync(innerContext, cancellationToken).ConfigureAwait(false); // Archive any messages that the reducer removed to the vector store. if (this._reducer.RemovedMessages is { Count: > 0 })