Sahaj.blogs
17 Apr 2026 · StudyPhase 13 · LangChain

LangChain · Memory and message history

Chat models are stateless; RunnableWithMessageHistory stores and re-sends past messages per session, with trimming to keep the history within limits.

A chat model is stateless. Every call only sees the messages you send in that call. “Memory” is application code that:

  1. stores the conversation so far, per session
  2. inserts it into the prompt (the MessagesPlaceholder from the templates post)
  3. appends the new question and answer after each turn

RunnableWithMessageHistory wraps a chain and does exactly this, using a get_session_history(session_id) function. Long histories eventually exceed the context window or cost too much, so trim_messages keeps only the most recent part.

To show what really reaches the model, I used a tiny model that just reports how many messages it received.

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.runnables import RunnableLambda
from langchain_core.messages import AIMessage, trim_messages

def echo_model(prompt_value):
    msgs = prompt_value.to_messages()
    return AIMessage(f"I received {len(msgs)} messages; last: {msgs[-1].content!r}")

prompt = ChatPromptTemplate.from_messages([("system", "Be brief."), MessagesPlaceholder("history"), ("human", "{q}")])
chain = prompt | RunnableLambda(echo_model)

store = {}
def get_history(session_id):
    return store.setdefault(session_id, InMemoryChatMessageHistory())

chat = RunnableWithMessageHistory(chain, get_history, input_messages_key="q", history_messages_key="history")
for q in ["My name is Sahaj.", "I study ICT.", "What is my name?"]:
    print("alice:", chat.invoke({"q": q}, config={"configurable": {"session_id": "alice"}}).content)
print("bob  :", chat.invoke({"q": "What is my name?"}, config={"configurable": {"session_id": "bob"}}).content)
print("stored messages:", {k: len(v.messages) for k, v in store.items()})

trimmed = trim_messages(store["alice"].messages, max_tokens=2, token_counter=len, strategy="last")
print("trimmed to last 2:", [m.content for m in trimmed])

Output:

alice: I received 2 messages; last: 'My name is Sahaj.'
alice: I received 4 messages; last: 'I study ICT.'
alice: I received 6 messages; last: 'What is my name?'
bob  : I received 2 messages; last: 'What is my name?'
stored messages: {'alice': 6, 'bob': 2}
trimmed to last 2: ['What is my name?', "I received 6 messages; last: 'What is my name?'"]

#langchain#memory

Sources

  1. LangChain reference: RunnableWithMessageHistory — LangChain
  2. LangChain reference: trim_messages — LangChain
  3. LangChain docs: Short-term memory — LangChain