Skip to content

Commit 9dbc749

Browse files
committed
Add blog post: Adding Long-Term Memory to LangGraph and LangChain Agents
1 parent 72b6121 commit 9dbc749

3 files changed

Lines changed: 263 additions & 0 deletions

File tree

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
---
2+
title: "Adding Long-Term Memory to LangGraph and LangChain Agents"
3+
description: Learn how to add long-term memory to LangGraph and LangChain agents using three integration patterns — tools, nodes, and BaseStore — with per-user memory banks and semantic recall.
4+
authors: [DK09876]
5+
date: 2026-03-17
6+
tags: [langgraph, langchain, integrations, agents, memory]
7+
image: /img/blog/langgraph-longterm-memory.png
8+
hide_table_of_contents: true
9+
---
10+
11+
LangGraph agents are stateful by design — checkpointers save graph state between steps, and the Store API persists data across threads. But neither gives agents true long-term memory: the ability to extract meaning from conversations, build up knowledge over time, and recall it semantically when relevant.
12+
13+
That's what Hindsight adds. Hindsight is a memory layer for LLM applications that automatically extracts facts from conversations, builds entity graphs, and retrieves relevant context using four parallel recall strategies. The `hindsight-langgraph` package brings that to LangGraph — and since the memory tools are standard LangChain `@tool` functions, they work with plain LangChain too.
14+
15+
<!-- truncate -->
16+
17+
## The problem
18+
19+
LangGraph's built-in persistence is designed for graph state — checkpoints, intermediate values, cross-thread key-value storage. It's good at "what did this graph do last time?" but not at "what does this agent know about this user?"
20+
21+
Consider a support agent that talks to the same customer across dozens of sessions. With checkpointers alone, each new thread starts cold. With `InMemoryStore` or `PostgresStore`, you can manually store and retrieve facts, but you're responsible for:
22+
23+
- Deciding what to store (fact extraction)
24+
- Deciding what's relevant (semantic retrieval)
25+
- Handling contradictions and updates
26+
- Building knowledge graphs from raw conversations
27+
28+
Hindsight does all of this automatically. You retain conversations, and it extracts facts, builds entity graphs, and retrieves relevant memories using four parallel strategies: **semantic** (embedding similarity), **BM25** (keyword overlap), **graph traversal** (entity relationships), and **temporal** (recency weighting). Each strategy catches different things — semantic recall finds conceptually similar memories, graph traversal finds memories linked through shared entities, and temporal weighting surfaces recent context before older facts. Together they substantially outperform single-strategy retrieval.
29+
30+
## Three integration patterns
31+
32+
We built three ways to add Hindsight memory to LangGraph, at different abstraction levels.
33+
34+
### 1. Tools — the agent decides (LangChain & LangGraph)
35+
36+
Give the agent retain/recall/reflect tools and let it decide when to use memory. These are standard LangChain `@tool` functions, so they work with both LangGraph (via `create_react_agent`) and plain LangChain (via `bind_tools()`).
37+
38+
```python
39+
from hindsight_client import Hindsight
40+
from hindsight_langgraph import create_hindsight_tools
41+
from langchain_openai import ChatOpenAI
42+
from langgraph.prebuilt import create_react_agent
43+
44+
client = Hindsight(base_url="http://localhost:8888")
45+
tools = create_hindsight_tools(client=client, bank_id="user-123")
46+
47+
# With LangGraph
48+
agent = create_react_agent(ChatOpenAI(model="gpt-4o"), tools=tools)
49+
50+
# Or with plain LangChain
51+
model = ChatOpenAI(model="gpt-4o").bind_tools(tools)
52+
```
53+
54+
The agent gets three tools:
55+
56+
- **`hindsight_retain`** — stores the conversation and extracts facts from it
57+
- **`hindsight_recall`** — searches the memory bank for relevant context
58+
- **`hindsight_reflect`** — synthesizes across multiple memories to produce a summary or answer a question about what the agent knows (useful for questions like "what has this user told me about their stack?")
59+
60+
The agent calls these based on conversation context — storing facts when the user shares something important, recalling when asked about past context, and reflecting when it needs to synthesize accumulated knowledge.
61+
62+
**Best for**: ReAct agents that need to reason about when memory is relevant. Works with LangGraph for automatic tool execution loops or with plain LangChain if you manage the loop yourself.
63+
64+
### 2. Nodes — memory as graph steps
65+
66+
Add recall and retain as automatic nodes in your graph. No tool-calling required — memory runs on every turn.
67+
68+
```python
69+
from hindsight_langgraph import create_recall_node, create_retain_node
70+
from langgraph.graph import StateGraph, MessagesState, START, END
71+
72+
recall = create_recall_node(client=client, bank_id_from_config="user_id")
73+
retain = create_retain_node(client=client, bank_id_from_config="user_id")
74+
75+
builder = StateGraph(MessagesState)
76+
builder.add_node("recall", recall)
77+
builder.add_node("agent", agent_node)
78+
builder.add_node("retain", retain)
79+
builder.add_edge(START, "recall")
80+
builder.add_edge("recall", "agent")
81+
builder.add_edge("agent", "retain")
82+
builder.add_edge("retain", END)
83+
```
84+
85+
The recall node runs before the LLM, searches Hindsight for memories relevant to the user's message, and injects them as a `SystemMessage`. The retain node runs after, storing the conversation. Both resolve per-user bank IDs from `RunnableConfig` at runtime.
86+
87+
**Best for**: Agents where you always want memory context injected automatically, without relying on the LLM to decide when to use memory tools.
88+
89+
### 3. BaseStore — drop-in backend
90+
91+
Replace LangGraph's `InMemoryStore` with Hindsight as the storage backend. If your team already uses LangGraph's store patterns, this is the lowest-friction path.
92+
93+
```python
94+
from hindsight_langgraph import HindsightStore
95+
96+
store = HindsightStore(client=client)
97+
graph = builder.compile(checkpointer=checkpointer, store=store)
98+
```
99+
100+
Namespace tuples map to Hindsight bank IDs (`("user", "123")` → bank `user.123`), banks are auto-created, and `search()` uses Hindsight's full semantic recall instead of basic vector similarity.
101+
102+
**Best for**: Teams already using LangGraph's `store` patterns who want better retrieval without restructuring their graph.
103+
104+
---
105+
106+
### Which pattern fits your use case?
107+
108+
| | Tools | Nodes | BaseStore |
109+
|---|---|---|---|
110+
| Works with plain LangChain | Yes | No | No |
111+
| Memory runs automatically | No (LLM decides) | Yes | Yes |
112+
| Uses existing store interface | No | No | Yes |
113+
| LLM controls when to remember | Yes | No | No |
114+
| Lowest migration cost || Low | Lowest |
115+
116+
---
117+
118+
## Complete working example
119+
120+
Here's a full support agent that remembers each user across sessions using the nodes pattern. This is copy-pasteable and runnable against either self-hosted Hindsight or Hindsight Cloud.
121+
122+
```python
123+
import asyncio
124+
from hindsight_client import Hindsight
125+
from hindsight_langgraph import create_recall_node, create_retain_node
126+
from langchain_openai import ChatOpenAI
127+
from langchain_core.messages import HumanMessage, SystemMessage
128+
from langgraph.graph import StateGraph, MessagesState, START, END
129+
from langgraph.checkpoint.memory import MemorySaver
130+
131+
# --- Setup ---
132+
133+
client = Hindsight(base_url="http://localhost:8888")
134+
# For Hindsight Cloud:
135+
# client = Hindsight(base_url="https://api.hindsight.vectorize.io", api_key="...")
136+
137+
llm = ChatOpenAI(model="gpt-4o")
138+
checkpointer = MemorySaver()
139+
140+
# --- Memory nodes ---
141+
# bank_id_from_config pulls the user ID from RunnableConfig at runtime,
142+
# so one graph definition serves all users with isolated memory banks.
143+
144+
recall = create_recall_node(client=client, bank_id_from_config="user_id")
145+
retain = create_retain_node(client=client, bank_id_from_config="user_id")
146+
147+
# --- Agent node ---
148+
149+
async def agent_node(state: MessagesState):
150+
system = SystemMessage(content=(
151+
"You are a helpful support agent. "
152+
"Relevant memories about this user have been injected above. "
153+
"Use them to personalize your response."
154+
))
155+
response = await llm.ainvoke([system] + state["messages"])
156+
return {"messages": [response]}
157+
158+
# --- Graph ---
159+
160+
builder = StateGraph(MessagesState)
161+
builder.add_node("recall", recall)
162+
builder.add_node("agent", agent_node)
163+
builder.add_node("retain", retain)
164+
builder.add_edge(START, "recall")
165+
builder.add_edge("recall", "agent")
166+
builder.add_edge("agent", "retain")
167+
builder.add_edge("retain", END)
168+
169+
graph = builder.compile(checkpointer=checkpointer)
170+
171+
# --- Run ---
172+
173+
async def chat(user_id: str, thread_id: str, message: str):
174+
config = {
175+
"configurable": {
176+
"user_id": user_id,
177+
"thread_id": thread_id,
178+
}
179+
}
180+
result = await graph.ainvoke(
181+
{"messages": [HumanMessage(content=message)]},
182+
config=config,
183+
)
184+
return result["messages"][-1].content
185+
186+
187+
async def main():
188+
# Session 1: user shares context
189+
print("Session 1")
190+
print(await chat("user-42", "thread-1", "Hi! I'm running into issues with our Postgres connection pool. We're on SQLAlchemy 2.0."))
191+
print(await chat("user-42", "thread-1", "We're using async sessions with asyncpg. The pool keeps exhausting under load."))
192+
193+
# Session 2: new thread, same user — agent remembers
194+
print("\nSession 2 (new thread)")
195+
print(await chat("user-42", "thread-2", "Hey, back again. Still fighting the connection pool issue."))
196+
# Agent recalls SQLAlchemy 2.0, asyncpg, and the pool exhaustion context
197+
# without the user having to repeat themselves.
198+
199+
200+
asyncio.run(main())
201+
```
202+
203+
What Hindsight extracts from Session 1 and stores in `user-42`'s memory bank:
204+
205+
```
206+
- Uses SQLAlchemy 2.0 with async sessions
207+
- Uses asyncpg driver
208+
- Experiencing connection pool exhaustion under load
209+
- Running Postgres
210+
```
211+
212+
When Session 2 starts on a fresh thread, the recall node searches the memory bank for context relevant to "Still fighting the connection pool issue" and injects those facts as a `SystemMessage` before the LLM responds. The agent picks up exactly where the last session ended.
213+
214+
---
215+
216+
## Per-user memory in one line
217+
218+
All three patterns support dynamic bank IDs. Instead of hardcoding a bank, resolve it from the graph's config at runtime:
219+
220+
```python
221+
recall = create_recall_node(client=client, bank_id_from_config="user_id")
222+
223+
# Each invocation gets its own isolated memory bank
224+
await graph.ainvoke(
225+
{"messages": [...]},
226+
config={"configurable": {"user_id": "user-456"}},
227+
)
228+
```
229+
230+
One graph definition serves all users. Memory banks are created automatically and kept fully isolated.
231+
232+
## Getting started
233+
234+
```bash
235+
pip install hindsight-langgraph
236+
```
237+
238+
Works with both self-hosted Hindsight and [Hindsight Cloud](https://ui.hindsight.vectorize.io/signup). For cloud, pass your API key when creating the client:
239+
240+
```python
241+
client = Hindsight(base_url="https://api.hindsight.vectorize.io", api_key="your-key")
242+
# or
243+
from hindsight_client import configure
244+
configure(api_key="your-key") # defaults to the cloud URL
245+
```
246+
247+
## What to build with this
248+
249+
Long-term memory unlocks a different class of agent behavior. A few patterns we've seen work well:
250+
251+
- **Support agents** that remember each customer's history, preferences, and past issues across sessions
252+
- **Sales assistants** that accumulate context about prospects over multiple touchpoints
253+
- **Personal productivity agents** that build up a model of a user's work style, priorities, and decisions
254+
255+
In all three cases, the agent gets meaningfully better the longer it runs — not just because of a longer context window, but because Hindsight distills conversations into structured knowledge it can retrieve precisely when relevant.
256+
257+
Full docs: [LangGraph integration](/docs/sdks/integrations/langgraph) | [GitHub](https://github.com/vectorize-io/hindsight/tree/main/hindsight-integrations/langgraph)

hindsight-docs/blog/authors.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,9 @@ chrislatimer:
1919
title: Hindsight Team
2020
url: https://github.com/chrislatimer
2121
image_url: https://github.com/chrislatimer.png
22+
23+
DK09876:
24+
name: DK09876
25+
title: Hindsight Team
26+
url: https://github.com/DK09876
27+
image_url: https://github.com/DK09876.png
309 KB
Loading

0 commit comments

Comments
 (0)