AI agent memory comes in two layers that solve different problems: short-term memory for what happened earlier in the current conversation, and long-term memory for facts that should survive after the session ends. This guide builds a personal finance coaching agent with LangChain's checkpointer for thread-scoped recall and Mem0 for persistent user facts like income and savings goals. Budget 30-45 minutes and an Anthropic API key.
| Fact | Value |
|---|---|
| Tool | Any LLM or framework |
| Difficulty | Intermediate |
| Time required | 30-45 min |
| Compatibility | Python 3.10+, langchain 1.4+, langgraph 1.2+, mem0ai, an Anthropic API key, any OS |
| Last updated | November 2026 |
What you'll build
You'll build a personal finance coaching agent a user talks to over several sessions, days apart. Within one session, it needs to remember what you said three messages ago ('what about that second scenario I mentioned') without you repeating it; that's short-term memory, scoped to the current thread. Across sessions, it needs to remember that you're saving for a house down payment and have told it your rough monthly income before, without you re-explaining your situation every time you open a new chat; that's long-term memory, scoped to you as a user, not to any one conversation.
These are genuinely different systems, and conflating them is the most common memory mistake. Short-term memory in this guide comes from LangGraph's checkpointer, which snapshots the full message history under a thread_id and restores it automatically. Long-term memory comes from Mem0, which extracts durable facts from conversations and lets you search them later with a user_id, independent of which thread they came from.
Use a checkpointer alone when the agent only needs to function within one sitting, like a support chat that resets between visitors. Add Mem0 or a similar long-term store once the same user is expected to come back and the agent should feel like it remembers them, not just the current exchange.
Prerequisites
- Python 3.10+ and a terminal
- An Anthropic API key (ANTHROPIC_API_KEY)
- pip install langchain langgraph mem0ai
- An OpenAI API key if you keep Mem0's default extraction model, or a Mem0 config pointed at Claude
Step-by-step guide
Install the packages and set up Mem0
Install langchain, langgraph, and mem0ai. Mem0 needs its own small LLM for fact extraction; the default uses an OpenAI model, so either keep an OpenAI key around for that one internal call or configure Mem0 to use Claude instead. Initialize a single Memory instance your whole app shares.
1pip install langchain langgraph mem0ai2export ANTHROPIC_API_KEY="sk-ant-..."3export OPENAI_API_KEY="sk-..." # used internally by Mem0 for fact extraction45from mem0 import Memory67long_term_memory = Memory()Expected result: Memory() initializes with no errors; it uses a local vector store by default, no external memory service needed for this guide.
Build the agent with a checkpointer for short-term memory
create_agent takes a checkpointer directly, which is all short-term memory requires: every invoke() call under the same thread_id reads and appends to the same message history automatically. No manual message-list management needed.
1from langchain.agents import create_agent2from langgraph.checkpoint.memory import InMemorySaver34agent = create_agent(5 model="anthropic:claude-sonnet-5-5",6 tools=[],7 checkpointer=InMemorySaver(),8)910config = {"configurable": {"thread_id": "session-2026-11-04-alex"}}11agent.invoke({"messages": [{"role": "user", "content": "I'm trying to save $20k for a down payment"}]}, config=config)12result = agent.invoke({"messages": [{"role": "user", "content": "How much should I save monthly for that in 2 years?"}]}, config=config)Expected result: The second response correctly references the $20k goal from the first message, within the same thread_id, with no manual history passing.
Write long-term facts to Mem0 after each session
At the end of a session, hand Mem0 the exchange and a user_id (not a thread_id, since this should outlive any one thread). Mem0's extraction step pulls out durable facts, not the raw transcript, so 'I'm trying to save $20k for a down payment' becomes a stored fact tied to that user, independent of which conversation it came from.
1def save_long_term_facts(user_id: str, messages: list[dict]):2 long_term_memory.add(messages, user_id=user_id)34save_long_term_facts(5 "alex",6 [7 {"role": "user", "content": "I'm trying to save $20k for a down payment"},8 {"role": "assistant", "content": "Got it, saving for a $20k down payment."},9 ],10)Expected result: long_term_memory.search('down payment', filters={'user_id': 'alex'}) now returns a stored memory even in a brand new Python process.
Retrieve long-term facts at the start of a new session
Before the agent's first turn in a new session, search Mem0 for relevant facts about this user and fold the results into the system prompt as a short block, not the raw search payload. This is the step that makes the agent feel like it remembers you: by the time it answers, it already knows the down-payment goal from a session days ago.
1def build_system_prompt(user_id: str, current_message: str) -> str:2 hits = long_term_memory.search(current_message, filters={"user_id": user_id})3 facts = "\n".join(f"- {m['memory']}" for m in hits.get("results", [])[:5])4 base = "You are a personal finance coach. Be specific with numbers."5 if facts:6 return f"{base}\n\nKnown facts about this user:\n{facts}"7 return baseExpected result: A new thread's system prompt includes 'Known facts about this user: - Saving for a $20k down payment' pulled from a prior session.
Wire both memory layers into one call
On every new session, build the system prompt from Mem0 first, then run the agent with a fresh thread_id and that prompt. Within the session, the checkpointer handles turn-to-turn recall; across sessions, the system prompt built from Mem0 search carries the context forward.
1import uuid23agent = create_agent(4 model="anthropic:claude-sonnet-5-5",5 tools=[],6 system_prompt=build_system_prompt("alex", "How's my savings plan looking?"),7 checkpointer=InMemorySaver(),8)9config = {"configurable": {"thread_id": str(uuid.uuid4())}}10result = agent.invoke({"messages": [{"role": "user", "content": "How's my savings plan looking?"}]}, config=config)11print(result["messages"][-1].content)Expected result: A brand new thread with no prior turns still answers with awareness of the down-payment goal from a previous session.
Harden it: bound what gets stored and retrieved
Two failure modes show up fast in production. First, Mem0 can accumulate contradictory facts over time ('saving $20k' then later 'saving $30k'); periodically review or let newer facts naturally outrank older ones in search ranking rather than storing every raw message. Second, cap how many search results you fold into the prompt (five is a reasonable default) so a user with months of history doesn't blow out your context window on every turn.
1def build_system_prompt(user_id: str, current_message: str, max_facts: int = 5) -> str:2 hits = long_term_memory.search(current_message, filters={"user_id": user_id})3 results = sorted(hits.get("results", []), key=lambda m: m.get("score", 0), reverse=True)[:max_facts]4 facts = "\n".join(f"- {m['memory']}" for m in results)5 return f"You are a personal finance coach.\n\nKnown facts:\n{facts}" if facts else "You are a personal finance coach."Expected result: Even a user with 50+ stored memories only ever contributes at most 5 facts to any single prompt.
Complete working example
1"""Personal finance coach with two memory layers: a checkpointer for2within-session recall and Mem0 for facts that persist across sessions.3Run: python finance_coach_memory.py4(pip install langchain langgraph mem0ai; set ANTHROPIC_API_KEY and OPENAI_API_KEY)5"""6import uuid78from langchain.agents import create_agent9from langgraph.checkpoint.memory import InMemorySaver10from mem0 import Memory1112long_term_memory = Memory()131415def save_long_term_facts(user_id: str, messages: list[dict]) -> None:16 long_term_memory.add(messages, user_id=user_id)171819def build_system_prompt(user_id: str, current_message: str, max_facts: int = 5) -> str:20 hits = long_term_memory.search(current_message, filters={"user_id": user_id})21 results = sorted(22 hits.get("results", []), key=lambda m: m.get("score", 0), reverse=True23 )[:max_facts]24 facts = "\n".join(f"- {m['memory']}" for m in results)25 base = "You are a personal finance coach. Be specific with numbers."26 return f"{base}\n\nKnown facts about this user:\n{facts}" if facts else base272829def run_turn(user_id: str, thread_id: str, user_message: str) -> str:30 agent = create_agent(31 model="anthropic:claude-sonnet-5-5",32 tools=[],33 system_prompt=build_system_prompt(user_id, user_message),34 checkpointer=InMemorySaver(),35 )36 config = {"configurable": {"thread_id": thread_id}}37 result = agent.invoke({"messages": [{"role": "user", "content": user_message}]}, config=config)38 return result["messages"][-1].content394041if __name__ == "__main__":42 user_id = "alex"4344 # Session 1: establish a goal, then ask a follow-up in the same thread45 thread_1 = str(uuid.uuid4())46 run_turn(user_id, thread_1, "I'm trying to save $20k for a down payment")47 print(run_turn(user_id, thread_1, "How much should I save monthly for that in 2 years?"))4849 # Persist what was learned for future sessions50 save_long_term_facts(51 user_id,52 [53 {"role": "user", "content": "I'm trying to save $20k for a down payment"},54 {"role": "assistant", "content": "Got it, saving for a $20k down payment."},55 ],56 )5758 # Session 2: a brand new thread, days later, still knows the goal59 thread_2 = str(uuid.uuid4())60 print(run_turn(user_id, thread_2, "How's my savings plan looking?"))61Common mistakes
Why it's a problem: Using only a checkpointer and expecting the agent to remember the user in a new session
How to avoid: A checkpointer is scoped to a thread_id, not a user. A new thread starts with zero history, no matter how many prior sessions that user had.
Why it's a problem: Storing the raw conversation transcript as 'long-term memory' instead of extracted facts
How to avoid: Raw transcripts grow unbounded and resurface stale or irrelevant detail. Let Mem0's extraction step distill durable facts, and search instead of replaying everything.
Why it's a problem: Folding every search result into the system prompt with no limit
How to avoid: Cap retrieved facts (5 is reasonable) and rank by relevance or recency. An unbounded fact list burns tokens and can bury the one fact that actually matters for this turn.
Why it's a problem: Reusing the same thread_id across unrelated sessions to fake long-term memory
How to avoid: This works until the transcript gets too long for the context window, and it mixes unrelated conversations together. Use a real long-term store keyed by user_id instead.
Why it's a problem: Never updating or expiring long-term facts, so contradictions pile up over months
How to avoid: Review stored facts periodically or lean on Mem0's relevance scoring to let newer facts outrank stale ones in search results.
Best practices
- Treat short-term memory (thread_id, checkpointer) and long-term memory (user_id, Mem0 or similar) as two separate systems with two separate keys.
- Extract and store facts, not full transcripts, for anything meant to survive past the current session.
- Cap how many long-term facts get injected into any one system prompt; rank by relevance to the current message, not just recency.
- Save long-term facts at natural checkpoints (end of session, after a goal is confirmed) rather than after every single message.
- Let users see and correct what the agent remembers about them; wrong persisted facts are worse than no memory at all.
- Use InMemorySaver for development and a persistent checkpointer (Postgres, Redis) before you rely on short-term memory surviving a process restart in production.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I have a LangChain agent with a checkpointer for short-term memory and Mem0 for long-term facts about a user, keyed separately by thread_id and user_id (code below). Add a function that lets the user ask 'what do you remember about me' and have the agent summarize its own stored Mem0 facts back to them. [paste complete_code]
In Claude Code, open finance_coach_memory.py and ask: 'Replace InMemorySaver with a Postgres-backed checkpointer so short-term memory survives a process restart, and show me the connection string format.'
Frequently asked questions
Do I need both short-term and long-term memory, or just one?
It depends on whether the same user returns. A single-session support chat only needs short-term memory from a checkpointer. Any agent meant to recognize a returning user, like this finance coach, needs long-term memory too.
Why does Mem0 need an OpenAI key if I'm building with Claude?
By default, Mem0 uses an OpenAI model internally for its own fact-extraction step, separate from whatever model powers your agent. You can configure Mem0 to use a different provider for extraction if you want to avoid the second API key.
Can I use a Postgres checkpointer instead of InMemorySaver?
Yes, and you should before production. InMemorySaver loses all short-term memory on process restart. LangGraph ships Postgres and other persistent checkpointer backends that work as a drop-in replacement in the same checkpointer= argument.
How is this different from just putting the whole chat history in the prompt every time?
That works for short-term memory within a context window's limit, but it doesn't survive a new session and gets expensive as history grows. Long-term memory with extraction and search solves both problems: it's bounded and it outlives the thread.
What happens if Mem0 extracts a wrong or outdated fact?
It stays in the store until corrected. Give users a way to review or clear what's remembered about them, and treat memory extraction the same way you'd treat any other automated write to user data: logged and correctable.
Can someone build this memory layer for me?
Yes. RapidDev builds short-term and long-term memory layers on LangGraph, Mem0, and the Claude Agent SDK, including the fact-review tooling most tutorials skip. A free 30-minute consultation is the fastest way to scope your version.
Talk to an Expert
Our team has built 1,000+ apps. Get personalized help with your project.
Book a free consultation