Blog Post #31: My Agent Has Amnesia! Adding Conversational Memory to Your LangChain Agent

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:

  1. Store the history of the conversation (both the user’s inputs and the agent’s replies).
  2. Load this history and format it correctly.
  3. 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:

  1. Updating the prompt to accept a history.
  2. Creating a ConversationBufferMemory instance.
  3. 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

Debjeet Bhowmik

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

Leave a Comment