Implementation:Microsoft Semantic kernel ChatHistoryAgentThread
Overview
ChatHistoryAgentThread is the primary implementation of the AgentThread abstraction in Microsoft Semantic Kernel, providing in-memory conversation state management for agent interactions. It stores the sequence of messages exchanged between users and agents as a ChatHistory object, enabling multi-turn conversations where the agent can reference prior context.
Source file: dotnet/src/Agents/Core/ChatHistoryAgentThread.cs:L16-101
Principle page: Conversation Thread Management
Principle:Microsoft_Semantic_kernel_Conversation_Thread_Management
Code Reference
Class Definition
public class ChatHistoryAgentThread : AgentThread
{
public ChatHistory ChatHistory { get; }
public ChatHistoryAgentThread();
public ChatHistoryAgentThread(IEnumerable<ChatMessageContent>? initialMessages);
}
Constructors
| Constructor | Description |
|---|---|
ChatHistoryAgentThread() |
Creates a new empty thread with no prior messages. |
ChatHistoryAgentThread(IEnumerable<ChatMessageContent>?) |
Creates a thread pre-populated with the specified messages, enabling conversation resumption or context injection. |
Key Properties
| Property | Type | Description |
|---|---|---|
ChatHistory |
ChatHistory |
The underlying ordered collection of ChatMessageContent messages. Contains all user messages, assistant responses, system messages, and tool call results.
|
Id |
string |
A unique identifier for the thread instance, inherited from AgentThread.
|
Internal Behavior
When a ChatHistoryAgentThread is used in an InvokeAsync call:
- The agent reads the thread's
ChatHistoryto reconstruct the conversation so far. - The agent prepends its
Instructionsas a system message. - The new user message is appended to the history.
- The chat completion service is called with the full assembled history.
- The assistant's response is appended to the history.
- The updated thread is returned via
response.Thread.
I/O Contract
Input (Construction)
new ChatHistoryAgentThread(initialMessages?)
├── initialMessages: IEnumerable<ChatMessageContent>?
│ ├── Each message has:
│ │ ├── Role: AuthorRole (User, Assistant, System, Tool)
│ │ └── Content: string (the message text)
│ └── null or omitted → empty thread
└── Returns: ChatHistoryAgentThread instance
Output (Thread State)
ChatHistoryAgentThread instance
├── ChatHistory: ChatHistory
│ ├── Contains all messages in chronological order
│ ├── Grows with each agent invocation
│ └── Accessible for inspection or serialization
├── Id: string → Unique thread identifier
└── Passable to agent.InvokeAsync(message, thread)
Thread as Return Value
After each invocation, the thread is accessible from the response:
AgentResponseItem<ChatMessageContent> response
└── response.Thread → AgentThread (the same or updated thread instance)
└── Cast to ChatHistoryAgentThread to access ChatHistory
Usage Examples
Empty Thread (Implicit Creation via InvokeAsync)
// When no thread is provided, InvokeAsync creates one automatically
AgentThread? thread = null;
ChatMessageContent message = new(AuthorRole.User, "Hello!");
await foreach (var response in agent.InvokeAsync(message, thread))
{
thread = response.Thread; // Capture the implicitly created thread
Console.WriteLine(response.Message.Content);
}
// Thread now contains: [User: "Hello!", Assistant: "<response>"]
Explicit Empty Thread
// Explicitly create an empty thread
AgentThread thread = new ChatHistoryAgentThread();
ChatMessageContent message = new(AuthorRole.User, "What is the capital of France?");
await foreach (var response in agent.InvokeAsync(message, thread))
{
thread = response.Thread;
Console.WriteLine(response.Message.Content);
}
Pre-Seeded Thread for Context Injection
AgentThread thread = new ChatHistoryAgentThread([
new ChatMessageContent(AuthorRole.User, "Tell me a joke."),
new ChatMessageContent(AuthorRole.Assistant, "Why did the chicken cross the road? To get to the other side!"),
]);
// Agent knows about the prior exchange
ChatMessageContent message = new(AuthorRole.User, "That was terrible. Tell a better one.");
await foreach (var response in agent.InvokeAsync(message, thread))
{
thread = response.Thread;
Console.WriteLine(response.Message.Content);
}
Multi-Turn Conversation Loop
AgentThread? thread = null;
string[] userMessages = [
"What is machine learning?",
"How does it differ from traditional programming?",
"Give me a simple example."
];
foreach (string userInput in userMessages)
{
ChatMessageContent message = new(AuthorRole.User, userInput);
await foreach (var response in agent.InvokeAsync(message, thread))
{
thread = response.Thread;
Console.WriteLine($"Agent: {response.Message.Content}");
}
}
// Thread now contains all 6 messages (3 user + 3 assistant)
Inspecting Thread History
if (thread is ChatHistoryAgentThread chatThread)
{
foreach (var msg in chatThread.ChatHistory)
{
Console.WriteLine($"[{msg.Role}]: {msg.Content}");
}
}
Related Pages
- Conversation Thread Management (Principle) -- The conceptual foundation for thread-based state management.
- Agent InvokeAsync (Implementation) -- The invocation API that uses threads.
- ChatCompletionAgent (Implementation) -- The agent that operates on threads.
- Single Agent Invocation (Principle) -- How threads participate in the invocation lifecycle.
- Orchestration Patterns (Implementation) -- How threads are managed in multi-agent scenarios.