Ask an LLM your name, then ask "what's my name?" in a fresh call — it has no idea. LLMs are stateless: each call is independent. "Memory" is nothing magical — it's just you resending the past messages every time. Once you see that, chatbot memory stops being mysterious.
Memory, by hand (understand this first)
The honest core of every memory system: keep a list of messages, append each turn, and send the whole list on every call.
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage, AIMessage
model = init_chat_model("claude-opus-5", model_provider="anthropic")
history = [] # this list IS the memory
def chat(text):
history.append(HumanMessage(text))
reply = model.invoke(history) # send the ENTIRE history each time
history.append(reply) # remember the AI answer too
return reply.content
print(chat("My name is Mohan and I study CSE."))
print(chat("What is my name and branch?")) # "Mohan, CSE" — it remembers!That's it. Every "memory" feature is a smarter version of this list.
Automating it with message history
Managing the list by hand breaks down with many users (each needs their own history). LangChain's RunnableWithMessageHistory wraps a chain and keeps a separate history per session_id.
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
prompt = ChatPromptTemplate.from_messages([
("system", "You are a friendly study buddy."),
MessagesPlaceholder("history"), # past turns get injected here
("user", "{input}"),
])
chain = prompt | model
store = {}
def get_history(session_id):
return store.setdefault(session_id, InMemoryChatMessageHistory())
bot = RunnableWithMessageHistory(chain, get_history,
input_messages_key="input", history_messages_key="history")
cfg = {"configurable": {"session_id": "student-42"}}
print(bot.invoke({"input": "I am learning DSA."}, config=cfg).content)
print(bot.invoke({"input": "Suggest my next topic."}, config=cfg).content) # remembers DSADifferent session_id = different conversation. That's how one server handles thousands of separate chats.
The context-window problem
Resending everything works until the history gets huge and hits the model's context limit (and cost). Real apps trim or summarize old turns — keep the last N messages, or replace old ones with a running summary. LangChain has trim_messages for exactly this.
from langchain_core.messages import trim_messages
trimmer = trim_messages(max_tokens=2000, strategy="last",
token_counter=model, include_system=True)
# put `trimmer` before the model in your chain to cap history sizeWhere memory is heading
For production agents that must remember across sessions and survive restarts, the modern answer is LangGraph persistence (checkpointers) — covered in the advanced LangGraph post. Next up, though, is giving your bot access to knowledge it wasn't trained on: RAG.
← All Articles