You’ve just built your first agent. You proudly run it and have a great first interaction:
You: “My name is Rohan, and I’m interested in local history.”
Agent: “Hello Rohan! It’s nice to meet you. Sodepur has a rich history. How can I help?”
This is fantastic! You ask a follow-up question:
You: “What did I just tell you my name was?”
Agent: “I’m sorry, I don’t have access to our previous conversation. I don’t know your name.”
Your brilliant agent has amnesia.
This happens because the AgentExecutor
we built in the last post is stateless. Each time you call .invoke()
, it’s a completely new, independent event with no memory of the past. To build a true conversational partner, we need to give our agent a memory.
This practical guide will show you how to implement the most fundamental type of memory, ConversationBufferMemory
, to give your agent context from the current conversation.
The Theory: How Memory Works in LangChain
Recall from Post #26 that we can use a MessagesPlaceholder
in our ChatPromptTemplate
. This placeholder is a hook where we can dynamically inject a list of past messages.
The job of a Memory component is to automatically:
- Store the history of the conversation (both the user’s inputs and the agent’s replies).
- Load this history and format it correctly.
- Inject it into the
MessagesPlaceholder
on every new turn.
ConversationBufferMemory
is the simplest implementation of this. It’s like a stenographer, recording every single word and providing the full, verbatim transcript to the agent on each loop.
The Implementation: Let’s Cure Amnesia
We are going to modify the agent we built in Post #30. The process involves three key changes:
- Updating the prompt to accept a history.
- Creating a
ConversationBufferMemory
instance. - Structuring our code into a conversational loop to manage the memory.
Step 1: Updating the Prompt for Memory
Our agent’s “brain” needs to be aware that it will be receiving a chat history. We’ll add a MessagesPlaceholder
with the variable name "chat_history"
.
# The updated prompt
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful local assistant for Sodepur, West Bengal."),
# 1. This is where the memory will be injected
MessagesPlaceholder(variable_name="chat_history"),
("user", "{input}"),
# 2. This is where the agent's internal steps (scratchpad) will go
MessagesPlaceholder(variable_name="agent_scratchpad"),
])
Step 2: The Full Code with a Conversational Loop
A stateless .invoke()
call isn’t enough for a continuous conversation. We need to manage the state ourselves. The standard way to do this is to create a loop that gets user input, loads the memory, runs the agent, and saves the new context.
Here is the complete, updated main.py
file.
# main.py
import os
from dotenv import load_dotenv
from tools import get_current_time, search_local_restaurants
from langchain.memory import ConversationBufferMemory
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.agents import AgentExecutor, create_tool_calling_agent
# --- 1. SETUP: Load environment variables, define tools, and LLM ---
load_dotenv()
tools = [get_current_time, search_local_restaurants]
llm = ChatOpenAI(model="gpt-4o", temperature=0)
# --- 2. PROMPT: Update with a placeholder for chat history ---
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful local assistant for Sodepur, West Bengal. Today is Tuesday, September 30, 2025."),
MessagesPlaceholder(variable_name="chat_history"),
("user", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
])
# --- 3. AGENT: Use the modern 'create_tool_calling_agent' constructor ---
agent = create_tool_calling_agent(llm, tools, prompt)
# --- 4. MEMORY: Create a memory object ---
# 'memory_key' must match the placeholder name in the prompt
# 'return_messages=True' ensures the history is a list of Message objects
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
# --- 5. EXECUTOR: The runtime that powers the agent ---
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# --- 6. CONVERSATIONAL LOOP ---
def run_conversation():
print("Agent is ready. Type 'exit' to end the conversation.")
while True:
user_input = input("Human: ")
if user_input.lower() == 'exit':
print("AI: Goodbye!")
break
# Load the conversation history
chat_history = memory.load_memory_variables({})["chat_history"]
# Invoke the agent with the user input and history
response = agent_executor.invoke({
"input": user_input,
"chat_history": chat_history
})
# Save the context for the next turn
memory.save_context(
{"input": user_input},
{"output": response["output"]}
)
print("AI:", response["output"])
if __name__ == "__main__":
run_conversation()
The Demonstration: A Conversation with Recall
Now, when you run python main.py
, you can have a continuous conversation.
Agent is ready. Type 'exit' to end the conversation.
Human: Hi, my name is Priya.
> Entering new AgentExecutor chain...
... (agent executes)
AI: Hello Priya! It's a pleasure to meet you. How can I help you today?
Human: What's the current time here in Kolkata?
> Entering new AgentExecutor chain...
... (agent calls get_current_time tool)
AI: The current time in Asia/Kolkata is 04:41:43 PM IST.
Human: What did I say my name was?
> Entering new AgentExecutor chain...
... (agent thinks)
AI: You told me your name is Priya.
Success! The agent now remembers previous turns of the conversation. If you look at the verbose=True
output, you’ll see the previous messages being passed into the prompt on each new turn.
Conclusion
By adding a MessagesPlaceholder
to your prompt and managing a ConversationBufferMemory
object, you have transformed your agent from a stateless tool into a stateful conversational partner. This simple addition is one of the most impactful changes you can make to improve the user experience of any chatbot or agent.
While ConversationBufferMemory
is powerful, remember its limitation: in very long conversations, it can eventually fill the context window. In future posts, we’ll explore more advanced memory techniques to manage this, but for now, you have conquered one of the most common challenges in agent development: amnesia.
Author

Experienced Cloud & DevOps Engineer with hands-on experience in AWS, GCP, Terraform, Ansible, ELK, Docker, Git, GitLab, Python, PowerShell, Shell, and theoretical knowledge on Azure, Kubernetes & Jenkins. In my free time, I write blogs on ckdbtech.com