# How to Configure Chat History Persistence with Session Providers in the Microsoft Agent Framework

> Configure chat history persistence in Microsoft Agent Framework by setting context providers with a HistoryProvider subclass. Learn to enable per-service call history persistence.

- Repository: [Microsoft/agent-framework](https://github.com/microsoft/agent-framework)
- Tags: how-to-guide
- Published: 2026-04-05

---

**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`](https://github.com/microsoft/agent-framework/blob/main/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.

```python

# 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`](https://github.com/microsoft/agent-framework/blob/main/_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.

```python
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`](https://github.com/microsoft/agent-framework/blob/main/python/packages/redis/agent_framework_redis/_history_provider.py)). This implementation serializes messages to JSON and uses Redis lists for durable storage:

```python
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`](https://github.com/microsoft/agent-framework/blob/main/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:

```python
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:

```python
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:

```python
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`](https://github.com/microsoft/agent-framework/blob/main/python/packages/core/agent_framework/_agents.py)**: Contains `BaseAgent` with the `require_per_service_call_history_persistence` flag and the `_run_after_providers` logic (lines 63-70) that skips history providers when per-service-call mode is active.
- **[`python/packages/core/agent_framework/_sessions.py`](https://github.com/microsoft/agent-framework/blob/main/python/packages/core/agent_framework/_sessions.py)**: Defines `AgentSession`, the `HistoryProvider` ABC, and `InMemoryHistoryProvider` implementation (lines 763-788).
- **[`python/packages/redis/agent_framework_redis/_history_provider.py`](https://github.com/microsoft/agent-framework/blob/main/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`](https://github.com/microsoft/agent-framework/blob/main/docs/decisions/0022-chat-history-persistence-consistency.md)**: Architectural decision record documenting the rationale for persistence modes.

## 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`](https://github.com/microsoft/agent-framework/blob/main/_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`](https://github.com/microsoft/agent-framework/blob/main/_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.