Building AI Agents with Memory & Conversation History
A foundational limitation of standard AI agents is their statelessness: by default, every user input is processed as an isolated request. Without state persistence, an agent cannot answer follow-up questions, remember previous tool call execution outputs, or adapt to long-running conversational contexts.
This chapter covers how to integrate Short-Term Conversational Memory into a Google Search AI Agent using langgraph. You will learn how checkpointers manage conversation history, how thread_id isolates user sessions, and how to scale this architecture from local prototyping to production-grade applications.
1. The Problem: Stateless AI Agents
In a basic ReAct (Reasoning + Acting) agent setup, each invocation evaluates only the input messages provided in the payload of that specific request.
Key Issues with Stateless Agents:
- Context Loss: Follow-up questions fail because pronouns ("he", "it", "they") or implicit subjects ("total runs") have no reference point.
- Redundant Tool Calls: The agent must re-query information it already retrieved in previous turns.
- Degraded User Experience: Multi-turn dialogue feels fragmented, forcing the user to restate full context in every prompt.
2. Core Mental Model: Checkpointers, Threads, and State Persistence
To solve statelessness, the agent framework must save its Graph State (the sequence of messages, tool calls, and model outputs) after every step. In the LangChain ecosystem, memory management for agents is governed by LangGraph Checkpointers.
Core Concepts
- Checkpointer (
MemorySaver): An in-memory or database-backed storage driver that records state snapshots after each node in the execution graph completes. - Thread ID (
thread_id): A unique key used to isolate conversation histories. Each session, user, or chat window corresponds to a distinctthread_id. - Configurable Context: A parameter dictionary passed during agent invocation that supplies metadata (such as the
thread_id) to the underlying runtime without modifying the message payload.
Note: Although previously handled directly within legacy LangChain abstractions, modern agent memory is explicitly managed via
langgraphcheckpointers to ensure deterministic state transitions.
3. Naive vs. Stateful Agent Architecture
4. Environment Setup and Library Dependencies
To use checkpointers, ensure langgraph is added to your project dependencies alongside langchain-community and your chosen LLM integration.
requirements.txt
langchain>=0.3.0
langchain-community
langchain-google-genai
langgraph>=0.2.0
google-search-results
Install the dependencies:
pip install -r requirements.txt
5. Step-by-Step Implementation: Adding Memory to an Agent
Step 1: Import the Checkpointer Module
Import MemorySaver from the langgraph.checkpoint.memory package:
from langgraph.checkpoint.memory import MemorySaver
Step 2: Instantiate the Memory Object
Create a single instance of MemorySaver to serve as the runtime checkpoint database:
memory = MemorySaver()
Step 3: Pass the Checkpointer to the Agent Factory
When creating your agent (e.g., using create_react_agent), pass the instantiated memory object via the checkpointer parameter:
from langgraph.prebuilt import create_react_agent
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_community.utilities import SerpAPIWrapper
from langchain_core.tools import Tool
# Initialize LLM and Tools
llm = ChatGoogleGenerativeAI(model="gemini-2.5-flash")
search = SerpAPIWrapper()
tools = [
Tool(
name="google_search",
description="Search Google for recent events and real-time information.",
func=search.run
)
]
# Instantiate Agent with Checkpointer
agent_executor = create_react_agent(
model=llm,
tools=tools,
checkpointer=memory
)
6. Dynamic Thread Routing via Configurable Parameters
To query a stateful agent, pass a configurable parameter dictionary containing a thread_id inside the .invoke() call alongside the message payload.
Invocation Schema
config = {
"configurable": {
"thread_id": "user_session_123"
}
}
response = agent_executor.invoke(
{"messages": [("user", "Who won India's World Cup 2025?")]},
config=config
)
How thread_id Controls Context Boundaries
- Matching
thread_id: Re-uses history. The agent reads previous human messages, AI messages, and tool call outputs before making its next step. - New
thread_id: Creates a fresh, isolated execution context. The agent has zero awareness of actions taken under other thread IDs.
7. Complete Walkthrough: Interactive Memory-Enabled Agent
Below is the complete implementation of a CLI-based Google Search AI Agent that preserves context across multiple turns using MemorySaver.
import os
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_community.utilities import SerpAPIWrapper
from langchain_core.tools import Tool
from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import MemorySaver
# 1. Environment Configuration
os.environ["GOOGLE_API_KEY"] = "YOUR_GEMINI_API_KEY"
os.environ["SERPAPI_API_KEY"] = "YOUR_SERPAPI_API_KEY"
# 2. Define Tools and LLM
search_wrapper = SerpAPIWrapper()
google_search_tool = Tool(
name="google_search",
description="Useful for searching real-time queries on the web.",
func=search_wrapper.run
)
tools = [google_search_tool]
llm = ChatGoogleGenerativeAI(model="gemini-2.5-flash", temperature=0)
# 3. Setup Checkpointer and Agent
memory = MemorySaver()
agent = create_react_agent(
model=llm,
tools=tools,
checkpointer=memory
)
# 4. Interactive Execution Loop
def run_interactive_agent():
print("--- Memory-Enabled AI Agent Initialized ---")
# Assign a thread ID representing a specific user or chat session
session_config = {"configurable": {"thread_id": "session_user_001"}}
while True:
user_input = input("\nUser: ")
if user_input.lower() in ["exit", "quit"]:
break
# Pass both input messages and the context config
input_payload = {"messages": [("user", user_input)]}
response = agent.invoke(input_payload, config=session_config)
# Extract the last message generated by the model
latest_message = response["messages"][-1].content
print(f"\nAgent: {latest_message}")
if __name__ == "__main__":
run_interactive_agent()
8. Analyzing Execution Flows Across Threads
To see how state preservation works, consider the following interaction trace comparing execution within the same thread versus across different threads.
Trace 1: Multi-Turn Conversation within thread_id: "asd_123"
Trace 2: Isolated Conversation with modified thread_id: "asd_124"
Real-Life Analogy: ChatGPT URL Session IDs
When using ChatGPT, opening a new chat updates the browser URL with a unique UUID (e.g., chatgpt.com/c/672a1b2c-38d4-...).
If you copy that URL and paste it into another window, the application uses that unique key to fetch the exact same thread state from its database checkpointer. Changing the ID loads an entirely different context.
9. Real-World Execution: Combining Search Tools and Memory
When an agent has access to real-time tools and short-term memory, it can resolve complex, relative multi-turn queries that static LLMs cannot answer.
Interactive Demonstration
Algorithmic Context Growth ( Context Complexity)
As turns accumulate within a thread_id, the message list passed to the LLM grows linearly with each turn :
Because token cost and processing overhead grow as , production agents periodically truncate, summarize, or trim context windows while preserving key system messages.
10. Production Storage Backends: In-Memory vs. Persistent Checkpointers
While MemorySaver works well for development, tests, and temporary notebook execution, it stores data in volatile RAM. Restarting the Python process deletes all context.
In production environments, replace MemorySaver with a persistent checkpointer backend without changing your core agent code logic.
Enterprise Architecture Pattern
Summary and Key Takeaways
- Statelessness Limitation: Default AI agents process inputs as isolated events, making multi-turn dialogue and follow-up context resolution impossible without explicit state storage.
- LangGraph Integration: In modern LangChain applications, short-term conversational memory is managed through
langgraphcheckpointers rather than legacy memory classes. - The
MemorySaverDriver: Imports fromlanggraph.checkpoint.memory.MemorySaver. It records graph state snapshots in RAM after each execution step. - Agent Configuration: Memory is attached to an agent during instantiation by passing the memory object to the
checkpointerargument (create_react_agent(..., checkpointer=memory)). - Isolation via
thread_id: Thread IDs isolate user contexts. Passingconfig={"configurable": {"thread_id": "<unique_id>"}}routes the execution to a specific saved history. - Context Resolution: Memory allows the LLM to map relative pronouns ("his", "it", "total runs") back to entities retrieved in previous tool calls or prompts.
- Production Readiness: To persist memory across server restarts, swap
MemorySaverwith database checkpointers such asPostgresSaverorRedisSaver.