Course Content
LangChain Mastery
7 sections · 109 lessons
What is the role of memory keys in LangChain?
What you need to know
Two kinds of "key"
People mean two different things by "memory key", and a good answer separates them:
- Field names — which variable the history is stored in and injected into.
- Conversation keys — which conversation to load:
session_idin older code,thread_idin LangGraph.
Field names across three generations
| Job | Legacy memory class | RunnableWithMessageHistory (deprecated) | LangGraph (current) |
|---|---|---|---|
| Where history goes in the prompt | memory_key="history" | history_messages_key = MessagesPlaceholder name | State key messages, passed to your prompt |
| Which input is the new user turn | input_key | input_messages_key | The messages you pass to invoke |
| Which output to save | output_key | output_messages_key | Whatever your node returns under messages |
| Which conversation | one object per chat | session_id in config | thread_id in config |
In LangGraph: the state key and its reducer
1from typing import Annotated2from typing_extensions import TypedDict3from langgraph.graph.message import add_messages45class ChatState(TypedDict):6 messages: Annotated[list, add_messages] # appended and saved every turn7 sources: list[str] # overwritten each turnMessagesState is exactly the messages line above. The add_messages reducer tells LangGraph to append new messages (and to replace or delete by ID) instead of overwriting the list. A field without a reducer, like sources, is replaced by each update. Both are checkpointed under the thread_id.
So the key question in LangGraph is: does my node write its answer into messages?
1from langchain.messages import AIMessage23def answer(state: ChatState):4 result = rag_chain.invoke({"messages": state["messages"]})5 return {"messages": [AIMessage(result["answer"])], # saved as conversation6 "sources": result["sources"]} # saved, not shown to the modelWhat goes wrong
- Legacy: placeholder name and key differ — the prompt is missing a variable and raises a "missing variables" error. Annoying but safe.
- Any version: no placeholder or no
messagesin the prompt — no error; history is saved and loaded, then ignored. This is the dangerous one. - Wrong output key — the saved "AI turn" is the wrong field or nothing, so the next turn's history is broken.
- Conversation key from the client, unchecked — a user can pass someone else's ID and read their conversation. Build it from your authenticated user.
A real-life example
A law firm's contract Q&A graph returned {"answer", "citations"} from its RAG chain. The developer's node wrote the result into custom state fields — answer and citations — but never appended an AIMessage to messages. Each turn looked fine on screen, but the saved conversation contained only the lawyer's questions. Follow-ups like "What about the second one?" got nonsense, because the model had never seen its own previous answer.
A reviewer spotted it by calling graph.get_state(cfg).values["messages"] after two turns: four expected messages, two present. Returning the answer inside messages, as in the code above, fixed it. The team added a test that runs two turns and asserts the second prompt contains the first answer.
The same bug existed in the firm's older RunnableWithMessageHistory version as a wrong output_messages_key="citations" — different API, same mistake.
Follow-up questions to expect
- "What is the default
memory_key?" —historyin the legacy buffer classes; the prompt had to use the same variable name. - "Why is the conversation ID in config, not in the input?" — It is metadata about the run, not prompt content; keeping it in config stops it leaking into the prompt.
- "How do you remember a field like the current order ID?" — Add it to the LangGraph state schema; it is checkpointed with the messages.