How to Configure Chat History Persistence with Session Providers in the Microsoft Agent Framework
Set context_providers with a HistoryProvider subclass when constructing an Agent, and optionally enable require_per_service_call_history_persistence=True to persist chat history after every LLM service call rather than only at the end of the run.
The microsoft/agent-framework repository provides a flexible session architecture that separates conversation state from the agent itself, allowing you to configure chat history persistence with session providers ranging from in-memory dictionaries to external databases like Redis or Cosmos DB. By implementing the HistoryProvider interface and adjusting persistence timing flags, you control exactly when and where turn-by-turn messages are stored.
Understanding Session Providers and History Persistence
The framework stores conversation context in an AgentSession object, while HistoryProvider implementations handle the actual storage mechanics. These providers participate in the context-provider pipeline through before_run and after_run hooks, determining when messages are loaded and saved.
The Session and Provider Architecture
An AgentSession (defined in python/packages/core/agent_framework/_sessions.py) acts as a lightweight container for mutable conversation state. Each session holds a state dictionary that history providers can read from and write to during execution cycles.
# From _agents.py - session creation
def create_session(self, *, session_id: str | None = None) -> AgentSession:
"""Create a new lightweight session."""
return AgentSession(session_id=session_id)
The HistoryProvider abstract base class (also in _sessions.py) extends ContextProvider and defines the contract for persistence. Concrete implementations must provide get_messages and save_messages methods that the framework calls at specific lifecycle points.
class HistoryProvider(ContextProvider):
"""Base class for conversation history storage providers."""
Built-In and External Storage Options
The framework ships with an InMemoryHistoryProvider that stores messages directly in session.state["messages"]. This provider requires no external dependencies and persists only for the process lifetime.
For production scenarios, you can implement custom providers such as RedisHistoryProvider (located in python/packages/redis/agent_framework_redis/_history_provider.py). This implementation serializes messages to JSON and uses Redis lists for durable storage:
async def save_messages(self, session_id, messages, *, state=None, **kwargs):
key = self._redis_key(session_id)
serialized = [self._serialize_json(m) for m in messages]
async with self._redis_client.pipeline(transaction=True) as pipe:
for s in serialized:
await pipe.rpush(key, s)
await pipe.execute()
Configuring Persistence Timing: Per-Run vs Per-Service-Call
The require_per_service_call_history_persistence boolean flag on BaseAgent (defined in python/packages/core/agent_framework/_agents.py at lines 372-376) determines when the framework invokes history storage.
Default Per-Run Persistence
By default (require_per_service_call_history_persistence=False), history providers execute their after_run method only after the entire agent.run() completes. This guarantees atomic updates and simplifies debugging, but risks message loss if the process crashes during long tool-calling loops.
Opt-In Per-Service-Call Persistence
When you set require_per_service_call_history_persistence=True, the framework skips the standard after_run persistence for history providers and instead injects PerServiceCallChatHistoryPersistingChatClient as middleware. This writes messages immediately after each underlying LLM service call, keeping stored history synchronized with the service state.
The behavior matrix from ADR 0022 clarifies the interaction between user-provided clients and this flag:
UseProvidedChatClientAsIs |
RequirePerServiceCallChatHistoryPersistence |
Result |
|---|---|---|
false (default) |
false (default) |
Per-run persistence |
false |
true |
Per-service-call persistence (framework-injected) |
true |
false |
Per-run (no injection) |
true |
true |
User must provide persisting client; framework warns only |
Practical Configuration Examples
Basic In-Memory Setup
For development or stateless microservices, use the built-in provider with default per-run timing:
from agent_framework import Agent, InMemoryHistoryProvider
agent = Agent(
client=my_chat_client,
context_providers=[InMemoryHistoryProvider()],
)
# Create and use a session
session = agent.create_session()
await agent.run("What's the capital of France?", session=session)
await agent.run("What's its population?", session=session)
# Serialize for later restoration
import json
with open("session.json", "w") as f:
json.dump(session.to_dict(), f)
Redis-Backed Persistence with Per-Service-Call
For production resilience during multi-step tool calls, configure Redis with immediate persistence:
from agent_framework import Agent
from agent_framework_redis import RedisHistoryProvider
redis_provider = RedisHistoryProvider(
redis_url="redis://localhost:6379",
max_messages=1000,
store_inputs=True,
store_outputs=True,
)
agent = Agent(
client=azure_openai_client,
context_providers=[redis_provider],
require_per_service_call_history_persistence=True, # Opt-in flag
)
session = agent.create_session()
# Each LLM round-trip is immediately written to Redis
response = await agent.run("Analyze this dataset and create a summary", session=session)
Session Restoration from External Storage
To resume conversations across process restarts, reconstruct the session from its serialized state:
from agent_framework import AgentSession
# Load from database, cache, or file
saved_state = database.get_session(user_id)
session = AgentSession.from_dict(saved_state)
# Continue with the same agent configuration
await agent.run("Continue our previous discussion", session=session)
Key Source Implementation Details
The persistence logic resides in specific files across the repository:
python/packages/core/agent_framework/_agents.py: ContainsBaseAgentwith therequire_per_service_call_history_persistenceflag and the_run_after_providerslogic (lines 63-70) that skips history providers when per-service-call mode is active.python/packages/core/agent_framework/_sessions.py: DefinesAgentSession, theHistoryProviderABC, andInMemoryHistoryProviderimplementation (lines 763-788).python/packages/redis/agent_framework_redis/_history_provider.py: Production-ready Redis implementation with pipelined writes for performance.docs/decisions/0022-chat-history-persistence-consistency.md: Architectural decision record documenting the rationale for persistence modes.
Summary
- Session providers are
HistoryProvidersubclasses passed viacontext_providersthat handle message storage in the microsoft/agent-framework. - InMemoryHistoryProvider stores data in
session.statefor the process lifetime, while external providers likeRedisHistoryProvideroffer durable storage. - Per-run persistence (default) writes history once when
agent.run()completes; per-service-call persistence writes after every LLM interaction whenrequire_per_service_call_history_persistence=True. - Session continuity requires passing the same
AgentSessionobject to eachrun()call, and durability beyond process lifetime requires serializingsession.to_dict().
Frequently Asked Questions
What is the default chat history persistence behavior in Agent Framework?
By default, the framework uses per-run persistence with InMemoryHistoryProvider. History is stored in session.state["messages"] only after the entire agent.run() execution completes. This provides atomic updates but does not survive process crashes during execution.
How do I enable persistence after every LLM service call?
Set require_per_service_call_history_persistence=True when constructing the Agent. According to the source in _agents.py, this flag causes the framework to skip the standard after_run hook for history providers and instead inject PerServiceCallChatHistoryPersistingChatClient middleware, which writes messages immediately after each underlying service interaction.
Can I use a custom database for chat history storage?
Yes. Implement the HistoryProvider abstract base class from _sessions.py, providing get_messages and save_messages methods. Pass your custom provider instance to the Agent constructor via context_providers=[MyCustomProvider()]. The framework will invoke your methods at the appropriate lifecycle hooks based on the persistence timing configuration.
How do I restore a conversation after restarting my application?
Serialize the session state using session.to_dict() before shutdown, store it in your preferred database or file system, then reconstruct it with AgentSession.from_dict(saved_state). Pass the restored session to agent.run() to maintain conversation continuity across process lifetimes.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →