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
Is it possible to manually add messages to an AgentThread?
Let's take a look to this sample of code:
varuserQuestion="Why is Mars called the Red Planet"?var informationForRAG ="...";// Get from a Vector StorevaractualQuestion=$"You're an assistant the knows only the following information: {informationForRAG}. You need to answer the following question: {userQuestion}";varthread=agent.GetNewThread();varresponse=awaitagent.RunAsync(actualQuestion,thread);
As a result, the Thread’s MessageStore will include all the content of actualQuestion followed by the assistant’s response. For my use case, though, I’d like to add only the userQuestion and the response.
Is that possible?
Reacted by Totti, Mike De Petris, nanny07, Valerio Cassano, Binoy Chitale and Charles Chen
Marco Minerva (@marcominerva), thanks for logging the issue. This is not currently possible exactly as you describe it, but the scenario definitely makes sense and there are some closely related ways to achieve this.
Do note that for some agent types it would never be possible. Any agent type that relies on the underlying service for chat history storage, e.g. ChatClientAgents based on Responses, or A2AAgents, would not be able to support this. With these services, whatever we send to to the service is persisted as part of the chat history automatically by the service and these services do not expose any way to alter this.
For cases where the service does not manage chat history (E.g. OpenAI ChatCompletion or Responses with store=false), the agent adds messages automatically to the ChatMessageStore attached to the AgentThread. If tno customized the default ChatMessageStore is the InMemoryChatMessageStore.
In this case, I can see a few options:
We introduce some callback mechanism that allows modifying the message sent to the underlying service, but we continue to persist the original message.
You use an AIContextProvider to do RAG, which adds additional messages into the chat history, which are much easier to just filter out or remove. This will be especially easy to filter out, if we also address this issue: .NET: Consider changing ChatMessageStore abstraction to match AIContextProvider interface #2518, where we would separate messages by source when notifying the message store.
Filtering out the additional messages from the AIContextProvider can be done by adding a decorator over InMemoryChatMessageStore, which filters out anything provided by the Agent before storage.
You can also modify the thread manually after the run from outside the agent, by getting the list of messages from the thread: IList<ChatMessage>? chatHistory = thread.GetService<IList<ChatMessage>>();. Note that this will return null if you are using an AI service with service side chat history storage or if you have a custom chat message store that does not expose this.
I think 2i is probably cleanest, and is well supported right now.
It also means that at usage time, callers don't have to concern themselves with RAG or message filtering, because once you have created / setup the agent everything is pretty much self contained.
AIAgentagent=azureOpenAIClient// Choosing the ChatCompletion service here, since it doesn't support or require in-service chat history storage..GetChatClient(deploymentName).CreateAIAgent(newChatClientAgentOptions{Instructions="You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available.",AIContextProviderFactory= ctx =>newTextSearchProvider(SearchAdapter,ctx.SerializedState,ctx.JsonSerializerOptions,textSearchOptions),ChatMessageStoreFactory= ctx =>newRAGResultFilteringMessageStore(newInMemoryChatMessageStore())});
Please let me know if this works well, we could also look at providing more out of the box filtering options for client side ChatMessageStores.
Upvoting this since our backend currently has a lot of threads that we are self-managing that will need to be reconstructed. Would appreciate an easier on-ramp for this use case and for official docs on how to load existing ChatMessage threads into the agent.
Wait, maybe I'm mis-understanding something: what is the intended delineation between ChatMessage and AgentThread? I noticed that this signature accepts both:
It's not clear to me why the signature would accept both and when each should be used.
Is it safe to say that for teams currently self-managing chat history, AgentThread is a superset. More specifically, it is still possible to self manage chat history via the RunAsync(IEnumerable<ChatMessage> messages, ...) signature?
Charles Chen (@CharlieDigital), AgentThread is designed to abstract the differences in underlying chat history storage requirements of underlying services away from you, since they can vary significantly.
Some services, e.g. Responses (with store = true, which is the default), Foundry Agents, and OpenAI Assistants, only support storing chat history in the service itself, so there really is no way to manage chat history yourself when using these services. In this case the AgentThread just stores the service provided id for the chat history.
If you are using a service that doesn't support/require service side chat history, e.g. OpenAI Chat Completion or Responses (store = false), the chat history is automatically managed in an InMemoryChatMessageStore, which is just storing an IList.
It is possible to get at these as mentioned above and modify. Or you can provide your own message store if you want more control.
If using one of these services, you could always not call GetNewThread, and not pass a thread to each invocation, and instead pass the full chat history to each run. That way you have to build your chat history manually.
Note that if you do this with services that require service chat history storage, it will create a new service side thread for each run, which could be inefficient.
You use an AIContextProvider to do RAG, which adds additional messages into the chat history, which are much easier to just filter out or remove. This will be especially easy to filter out, if we also address this issue: .NET: Consider changing ChatMessageStore abstraction to match AIContextProvider interface #2518, where we would separate messages by source when notifying the message store.
Filtering out the additional messages from the AIContextProvider can be done by adding a decorator over InMemoryChatMessageStore, which filters out anything provided by the Agent before storage.
Thanks westey (@westey-m), this approach sounds interesting, I’ll give it a try!
For cases where the service does not manage chat history (E.g. OpenAI ChatCompletion or Responses with store=false), the agent adds messages automatically to the ChatMessageStore attached to the AgentThread. If tno customized the default ChatMessageStore is the InMemoryChatMessageStore.
You mention Responses with store=false. Is this a configuration setting? Could you give me an example of how to set store=false when using the Responses API?
Could you give me an example of how to set store=false when using the Responses API
Marco Minerva (@marcominerva), sure, see the following. It's possible to provide a ChatOptions instance for the underlying IChatClient used by the agent and that in turn supports providing the raw options type for the underlying service SDK. In this case CreateResponseOptions.
Marco Minerva (@marcominerva), I've merged a PR that makes it easier to filter out AIContextProvider generated messages from storage when storing messages in a message store.
//we can also add a message removal policy
// that removes messages produced by the TextSearchProvider before they are added to the chat history, so that
// we don't bloat chat history with all the search result messages.
This is done using the WithAIContextProviderMessageRemoval extension method. Could you point me to where the messages are actually removed? I would like to understand the removal logic (for example, the presence of a specific AdditionalProperty), so that I can customize this if needed.
WithMessageFilters is generic and allows you to define your own filter, while the other is the more specific WithAIContextProviderMessageRemoval. And yes, that comment does refer to WithAIContextProviderMessageRemoval.
The invokedMessagesFilterFunc can be passed to WithMessageFilters. It receives a ChatMessageStore.InvokedContext and produces one. In this function you can modify the provided InvokedContext to remove or change (as required) any messages before they are passed to the ChatMessageStore. Whatever is returned by this Func will be used by the ChatMessageStore.
The built-in ChatMessageStore implementations will save the messages from the InvokedContext in the order RequestMessages, AIContextProviderMessages, and lastly ResponseMessages. Removing or modifying any of those will affect what is saved.
Activity
[-]Manually add messages to thread?[/-][+].NET: Manually add messages to thread?[/+]westey-m commentedon Nov 28, 2025
Marco Minerva (@marcominerva), thanks for logging the issue. This is not currently possible exactly as you describe it, but the scenario definitely makes sense and there are some closely related ways to achieve this.
Do note that for some agent types it would never be possible. Any agent type that relies on the underlying service for chat history storage, e.g. ChatClientAgents based on Responses, or A2AAgents, would not be able to support this. With these services, whatever we send to to the service is persisted as part of the chat history automatically by the service and these services do not expose any way to alter this.
For cases where the service does not manage chat history (E.g. OpenAI ChatCompletion or Responses with store=false), the agent adds messages automatically to the ChatMessageStore attached to the AgentThread. If tno customized the default ChatMessageStore is the InMemoryChatMessageStore.
In this case, I can see a few options:
IList<ChatMessage>? chatHistory = thread.GetService<IList<ChatMessage>>();. Note that this will return null if you are using an AI service with service side chat history storage or if you have a custom chat message store that does not expose this.I think 2i is probably cleanest, and is well supported right now.
It also means that at usage time, callers don't have to concern themselves with RAG or message filtering, because once you have created / setup the agent everything is pretty much self contained.
See the following docs on how to use a TextSearchProvider to add RAG to your Agent: https://learn.microsoft.com/en-us/agent-framework/user-guide/agents/agent-rag
This component will inject a message into the chat history that contains the search result and the instruction to the LLM around using the data to answer the question. (the instructions are configurable via TextSearchProviderOptions).
The injected messages are stamped with an additional property, so are easy to find later:
["IsTextSearchProviderOutput"] = trueSee: https://github.com/microsoft/agent-framework/blob/main/dotnet/src/Microsoft.Agents.AI/Data/TextSearchProvider.cs#L151
A filtering decorator over ChatMessageStore would look something like this:
And you would be able to use it like this:
Please let me know if this works well, we could also look at providing more out of the box filtering options for client side ChatMessageStores.
CharlieDigital commentedon Dec 2, 2025
Upvoting this since our backend currently has a lot of threads that we are self-managing that will need to be reconstructed. Would appreciate an easier on-ramp for this use case and for official docs on how to load existing
ChatMessagethreads into the agent.CharlieDigital commentedon Dec 2, 2025
Wait, maybe I'm mis-understanding something: what is the intended delineation between
ChatMessageandAgentThread? I noticed that this signature accepts both:It's not clear to me why the signature would accept both and when each should be used.
westey-m commentedon Dec 3, 2025
Charles Chen (@CharlieDigital), an AgentThread is a container for various bits of data including chat history or a reference to chat history.
See more here: https://learn.microsoft.com/en-us/agent-framework/user-guide/agents/multi-turn-conversation?pivots=programming-language-csharp
and here: https://learn.microsoft.com/en-us/agent-framework/user-guide/agents/agent-memory?pivots=programming-language-csharp
Messages in the highlighted snippet is the input for the current run.
CharlieDigital commentedon Dec 3, 2025
Is it safe to say that for teams currently self-managing chat history,
AgentThreadis a superset. More specifically, it is still possible to self manage chat history via theRunAsync(IEnumerable<ChatMessage> messages, ...)signature?westey-m commentedon Dec 4, 2025
Charles Chen (@CharlieDigital), AgentThread is designed to abstract the differences in underlying chat history storage requirements of underlying services away from you, since they can vary significantly.
Some services, e.g. Responses (with store = true, which is the default), Foundry Agents, and OpenAI Assistants, only support storing chat history in the service itself, so there really is no way to manage chat history yourself when using these services. In this case the AgentThread just stores the service provided id for the chat history.
If you are using a service that doesn't support/require service side chat history, e.g. OpenAI Chat Completion or Responses (store = false), the chat history is automatically managed in an InMemoryChatMessageStore, which is just storing an IList.
It is possible to get at these as mentioned above and modify. Or you can provide your own message store if you want more control.
If using one of these services, you could always not call GetNewThread, and not pass a thread to each invocation, and instead pass the full chat history to each run. That way you have to build your chat history manually.
Note that if you do this with services that require service chat history storage, it will create a new service side thread for each run, which could be inefficient.
marcominerva commentedon Dec 9, 2025
Thanks westey (@westey-m), this approach sounds interesting, I’ll give it a try!
marcominerva commentedon Dec 16, 2025
westey (@westey-m), I was reading your note again:
You mention Responses with store=false. Is this a configuration setting? Could you give me an example of how to set
store=falsewhen using the Responses API?westey-m commentedon Dec 30, 2025
Marco Minerva (@marcominerva), sure, see the following. It's possible to provide a ChatOptions instance for the underlying IChatClient used by the agent and that in turn supports providing the raw options type for the underlying service SDK. In this case CreateResponseOptions.
westey-m commentedon Jan 5, 2026
Marco Minerva (@marcominerva), I've merged a PR that makes it easier to filter out AIContextProvider generated messages from storage when storing messages in a message store.
See the following for a RAG sample that doesn't store the context messages added by the rag AIContextProvider: https://github.com/microsoft/agent-framework/pull/2604/changes#diff-bb6e6dfc75745c0890d32d026c5fff875dbc7d1bca3548be9d7dc46bc59c55d7
This should be in the next release.
marcominerva commentedon Jan 8, 2026
Hi westey (@westey-m)!
Thank you for your effort. I have one last question: on these lines in the PR: https://github.com/microsoft/agent-framework/pull/2604/changes#diff-bb6e6dfc75745c0890d32d026c5fff875dbc7d1bca3548be9d7dc46bc59c55d7R66-R70, I read the following comment:
This is done using the
WithAIContextProviderMessageRemovalextension method. Could you point me to where the messages are actually removed? I would like to understand the removal logic (for example, the presence of a specific AdditionalProperty), so that I can customize this if needed.westey-m commentedon Jan 8, 2026
Marco Minerva (@marcominerva), sure, I've added two extension methods. You can see them here:
https://github.com/microsoft/agent-framework/blob/main/dotnet/src/Microsoft.Agents.AI.Abstractions/ChatMessageStoreExtensions.cs
WithMessageFiltersis generic and allows you to define your own filter, while the other is the more specificWithAIContextProviderMessageRemoval. And yes, that comment does refer toWithAIContextProviderMessageRemoval.The
invokedMessagesFilterFunccan be passed toWithMessageFilters. It receives aChatMessageStore.InvokedContextand produces one. In this function you can modify the providedInvokedContextto remove or change (as required) any messages before they are passed to theChatMessageStore. Whatever is returned by this Func will be used by theChatMessageStore.The built-in
ChatMessageStoreimplementations will save the messages from theInvokedContextin the order RequestMessages, AIContextProviderMessages, and lastly ResponseMessages. Removing or modifying any of those will affect what is saved.marcominerva commentedon Jan 8, 2026
Thank you westey (@westey-m), this perfectly fits my scenario.
lindexi commentedon Feb 24, 2026
Add messages to
AgentSessionin 1.0.0-rc1 version:westey-m commentedon Feb 25, 2026
Marco Minerva (@marcominerva) & lindexi (@lindexi), also see #4140