Welcome to the fourth part of our series on building intelligent agents with LangMem. In our previous lesson, we enhanced our email assistant by giving it semantic memory, allowing it to recall facts and context from past conversations. Now, we’re taking another leap forward by introducing episodic memory.
Episodic memory, in the context of AI agents, refers to the ability to recall specific events or experiences. We will implement this by using “few-shot examples” — past instances of how specific emails were handled. This will allow our agent to learn from user feedback and refine its decision-making over time, particularly in the crucial first step: triaging incoming emails.
Let’s dive in and see how we can make our email assistant smarter and more personalized.
Table of Contents:
Setting the Working Environment & A Quick Recap
Introducing Episodic Memory with Few-Shot Examples
Searching and Formatting Examples
Integrating Episodic Memory into the Triage Logic
Building and Testing the Full Agent
Building agents with memory capabilities requires the right tools — and that’s where LangChain’s ecosystem comes in. LangChain provides the open-source foundation for LLM-powered applications, while LangGraph gives developers the framework to design stateful, multi-actor systems.
Now, with the addition of the Langmem SDK, there’s a dedicated library to handle the logic of creating, updating, and retrieving different types of long-term memory inside LangGraph.
Langmem is both flexible and modular: you can start with simple in-memory storage or connect to a production-ready database, depending on your needs.
In this series, we’ll explore how to put these ideas into practice by building an email assistant that demonstrates how semantic, episodic, and procedural memory can be effectively managed with LangGraph and Langmem.
Writing Assistant with Semantic & Episodic Memory [You are here!!]
Writing Assistant with Semantic, Episodic & Procedural Memory [Coming Soon!]
Get All My Books, One Button Away With 40% Off
I have created a bundle for my books and roadmaps, so you can buy everything with just one button and for 40% less than the original price. The bundle features 8 eBooks, including:
1. Setting the Working Environment & A Quick Recap
As before, we begin by setting up our environment. This involves loading our API keys, defining a user profile for our assistant to serve, and establishing the initial rules for email triage.
import os
from dotenv import load_dotenv
_ = load_dotenv()
profile = {
"name": "Youssef",
"full_name": "Youssef Hosni",
"user_profile_background": "Senior Data Scientist leading a team of 5 developers",
}
prompt_instructions = {
"triage_rules": {
"ignore": "Marketing newsletters, spam emails, mass company announcements",
"notify": "Team member out sick, build system notifications, project status updates",
"respond": "Direct questions from team members, meeting requests, critical bug reports",
},
"agent_instructions": "Use these tools when appropriate to help manage John's tasks efficiently."
}
2. Introducing Episodic Memory with Few-Shot Examples
The core of this lesson is to teach the agent how to handle emails based on past examples. To do this, we first need a place to store these “memories.” We’ll use LangMem’s InMemoryStore, the same component we used for semantic memory, but we’ll organize our data differently.
from langgraph.store.memory import InMemoryStore
store = InMemoryStore(
index={"embed": "openai:text-embedding-3-small"}
)
Now, let’s create our first “memory” or few-shot example. This consists of two parts: the input (the email itself) and the output (the desired action, or “label”). By providing examples of emails and how they should be classified, we can guide the agent’s future behavior.
import uuid
# The input email
email = {
"author": "Jack Oliver <Jack.Oliver@ToDataBeyond.com>",
"to": "Youssef Hosni <Youssef.Hosni@ToDataBeyond.com>",
"subject": "Quick question about API documentation",
"email_thread": """Hi Youssef,
I was reviewing the API documentation for the new authentication service and noticed a few endpoints seem to be missing from the specs. Could you help clarify if this was intentional or if we should update the docs?
Specifically, I'm looking at:
- /auth/refresh
- /auth/validate
Thanks!
Oliver""",
}
# We package the email with the desired label
data = {
"email": email,
"label": "respond"
}
# We store this example in a specific namespace
store.put(
("email_assistant", "lance", "examples"),
str(uuid.uuid4()),
data
)
Notice the namespace we’re using: (“email_assistant”, “lance”, “examples”). The tuple structure allows for multi-tenant isolation.
email_assistant: Identifies the application.
lance: Represents a unique user ID. This ensures that the agent’s learned preferences are specific to each user.
examples: This is the key part. We use this to separate our episodic few-shot examples from the semantic memories, which we might store under a “collection” namespace.
Let’s add a second example. This one is an informational update that doesn’t require a direct response, so we’ll label it as ignore.
data = {
"email": {
"author": "Emma Liam <Emma.Liam@ToDataBeyond.com>",
"to": "Youssef Hosni <Youssef.Hosni@ToDataBeyond.com>",
"subject": "Update: Backend API Changes Deployed to Staging",
"email_thread": """Hi Youssef,
Just wanted to let you know that I've deployed the new authentication endpoints we discussed to the staging environment. Key changes include:
- Implemented JWT refresh token rotation
- Added rate limiting for login attempts
- Updated API documentation with new endpoints
All tests are passing and the changes are ready for review. You can test it out at staging-api.company.com/auth/*
No immediate action needed from your side - just keeping you in the loop since this affects the systems you're working on.
Best regards,
Emma
""",
},
"label": "NOTIFY"
}
store.put(
("email_assistant", "lance", "examples"),
str(uuid.uuid4()),
data
)
3. Searching and Formatting Examples
When a new email arrives, the agent needs to recall relevant past experiences. We can simulate this by searching the store for examples that are semantically similar to the new email.
First, let’s create a helper function to format the retrieved examples into a clean, readable string. This formatted string will be injected directly into the agent’s prompt.
Keep reading with a 7-day free trial
Subscribe to To Data & Beyond to keep reading this post and get 7 days of free access to the full post archives.