Who this is for and what you will be able to do
This guide is for developers who already have a simple LangChain agent and want it to remember what was said earlier in the same conversation. After reading it, you can add thread-level short-term memory with a checkpointer, persist that state in a database, and choose between trimming, deleting and summarizing when a conversation gets long.
What short-term memory is
Short-term memory lets an application remember previous interactions within a single thread or conversation.[1] A thread works like an email conversation: it groups several interactions into one session.[1]
Conversation history is the most common form of short-term memory.[1] It runs into limits as conversations grow. A full history may not fit inside the model's context window, which leads to lost context or errors.[1] Even when a model supports the full context length, most LLMs still perform poorly over long contexts. They get distracted by stale or off-topic content, and responses become slower and more expensive.[1]
Chat models receive context as messages. A system message carries the instructions, and human messages carry the inputs. In a chat application the list of messages grows longer over time.[1]
Short-term memory is not the same as long-term memory. Long-term memory stores user-specific or application-level data across different threads and sessions.[1]
Adding memory to an agent
To add thread-level persistence, specify a checkpointer when you create the agent.[1] The agent keeps this memory as part of its state, stored in the graph's state. That lets it see the full context of one conversation while keeping different threads separate.[1] The state is persisted through the checkpointer, so a thread can be resumed at any time.[1]
- Create a checkpointer. The Python example uses
InMemorySaverfromlanggraph.checkpoint.memory.[1] - Pass it to
create_agentas thecheckpointerargument. - Pass a
thread_idin the config on each call. Calls with the same thread_id share the same memory.[1]
The complete Python example below is a custom state that also shows InMemorySaver and the thread_id config:
from langchain.agents import create_agent, AgentState
from langgraph.checkpoint.memory import InMemorySaver
class CustomAgentState(AgentState): # [!code highlight]
user_id: str # [!code highlight]
preferences: dict # [!code highlight]
agent = create_agent(
"gpt-6.1-sol",
tools=[get_user_info],
state_schema=CustomAgentState, # [!code highlight]
checkpointer=InMemorySaver(),
)
# Custom state can be passed in invoke
result = agent.invoke(
{
"messages": [{"role": "user", "content": "Hello"}],
"user_id": "user_123", # [!code highlight]
"preferences": {"theme": "dark"} # [!code highlight]
},
{"configurable": {"thread_id": "1"}})State is updated when the agent is invoked and when a step such as a tool call completes. The state is read at the start of each step.[1]
Moving to a database in production
The in-memory checkpointer suits experiments. For production, the docs recommend a checkpointer backed by a database. The options listed include SQLite, Postgres and Azure Cosmos DB.[1] For Postgres with Python, install with pip:
pip install -U langgraph-checkpoint-postgres "psycopg[binary]"With uv:
uv add langgraph-checkpoint-postgres "psycopg[binary]"By default, langgraph-checkpoint-postgres installs psycopg without extras. Most users should use psycopg[binary].[1] Then create the PostgresSaver and hand it to the agent:
from langchain.agents import create_agent
from langgraph.checkpoint.postgres import PostgresSaver # [!code highlight]
def get_user_info() -> str:
"""Look up information about the current user."""
return "No user profile on file."
DB_URI = "postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable"
with PostgresSaver.from_conn_string(DB_URI) as checkpointer:
checkpointer.setup() # auto create tables in PostgreSQL
agent = create_agent(
"gpt-6.1-sol",
tools=[get_user_info],
checkpointer=checkpointer, # [!code highlight]
)creates the tables in PostgreSQL automatically.[1] The JavaScript version creates the saver from a connection string:
import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";
const DB_URI = "postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable";
const checkpointer = PostgresSaver.fromConnString(DB_URI);Customizing agent state
By default an agent uses AgentState, and the conversation history lives under the messages key.[1] To store extra fields such as a user ID or preferences, extend AgentState. In Python, pass the custom state schema to create_agent with the state_schema parameter.[1] In JavaScript, add the state through middleware with a stateSchema. The docs prefer the StateSchema class, though plain Zod objects also work.[1]
Handling long conversations
Four common approaches appear in the docs: trimming messages, deleting messages, summarizing messages, and custom strategies such as message filtering.[1]
Trimming. In Python, the @before_model decorator turns a function into middleware that runs before each model call.[1] The example below keeps the first message and the most recent few:
@before_model
def trim_messages(state: AgentState, runtime: Runtime) -> dict[str, Any] | None:
"""Keep only the last few messages to fit context window."""
messages = state["messages"]
if len(messages) <= 3:
return None # No changes needed
first_msg = messages[0]
recent_messages = messages[-3:] if len(messages) % 2 == 0 else messages[-4:]
new_messages = [first_msg] + recent_messages
return {
"messages": [
RemoveMessage(id=REMOVE_ALL_MESSAGES),
*new_messages
]
}Deleting. RemoveMessage only takes effect on a state key that uses the add_messages reducer. The default AgentState provides this.[1] To clear every message, return a RemoveMessage with REMOVE_ALL_MESSAGES, imported from langgraph.graph.message.[1] After deleting, keep the history valid. Some providers expect a user message first, and most require each assistant tool call to be followed by its tool result.[1]
Summarizing. Trimming and deleting can drop useful information. The built-in SummarizationMiddleware uses a chat model to summarize earlier messages. The example sets trigger=("tokens", 4000) and keep=("messages", 20).[1]
Reading and writing memory from tools and prompts
A tool reads state through the runtime parameter, typed as ToolRuntime. The parameter is hidden from the tool signature, so the model never sees it.[1] Tools can also write state: return a Command with updates, such as a user name, and a ToolMessage added to the history.[1] Middleware can build dynamic prompts from state or runtime context, for example addressing the user by name.[1]
Limits and common questions
- Short-term memory is scoped to one thread. Cross-conversation data needs long-term memory.[1]
- The in-memory checkpointer is for experiments. Use a database-backed checkpointer in production.[1]
- Long conversations can exceed the context window, so trimming, deleting or summarizing is needed.[1]
- After deleting messages, the history must still be valid for the provider, or requests may fail.[1]
Next steps
For the full list of checkpointers, persistence details and the complete middleware options, see the corresponding sections of the LangChain documentation.[1]
Adapted from the LangChain documentation page *Short-term memory*[1], released under the MIT License.
Sources
- 1Short-term memoryLangChain docs (MIT)
Cite this page: Short-term memory in LangChain agents. TellWorld, https://www.tellworld.net/practice/practice-langchain-memory. Updated 2026-11-14