LangChain Mastery

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_id in older code, thread_id in LangGraph.

Field names across three generations

JobLegacy memory classRunnableWithMessageHistory (deprecated)LangGraph (current)
Where history goes in the promptmemory_key="history"history_messages_key = MessagesPlaceholder nameState key messages, passed to your prompt
Which input is the new user turninput_keyinput_messages_keyThe messages you pass to invoke
Which output to saveoutput_keyoutput_messages_keyWhatever your node returns under messages
Which conversationone object per chatsession_id in configthread_id in config

In LangGraph: the state key and its reducer

Python
from typing import Annotatedfrom typing_extensions import TypedDictfrom langgraph.graph.message import add_messagesclass ChatState(TypedDict):    messages: Annotated[list, add_messages]   # appended and saved every turn    sources: list[str]                        # overwritten each turn

MessagesState 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?

Python
from langchain.messages import AIMessagedef answer(state: ChatState):    result = rag_chain.invoke({"messages": state["messages"]})    return {"messages": [AIMessage(result["answer"])],   # saved as conversation            "sources": result["sources"]}                # saved, not shown to the model

What 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 messages in 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?" — history in 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.