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:

Summary

  • Session providers are HistoryProvider subclasses passed via context_providers that handle message storage in the microsoft/agent-framework.
  • InMemoryHistoryProvider stores data in session.state for the process lifetime, while external providers like RedisHistoryProvider offer durable storage.
  • Per-run persistence (default) writes history once when agent.run() completes; per-service-call persistence writes after every LLM interaction when require_per_service_call_history_persistence=True.
  • Session continuity requires passing the same AgentSession object to each run() call, and durability beyond process lifetime requires serializing session.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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →