Add Persistent Memory to LangChain Agents
Give your LangChain agents long-term memory that persists across sessions, users, and deployments.
Table of contents
Prerequisites
- A HippoDid account with an API key (
hd_key_...) - Python 3.8+
- A character created in HippoDid (see Quick Start)
Install
pip install hippodid langchain langchain-openai
HippoDidMemory as BaseChatMessageHistory
HippoDid plugs into LangChain’s memory system by implementing BaseChatMessageHistory. Every message the agent sends or receives is stored as a structured memory, and relevant memories are retrieved automatically at the start of each conversation.
from langchain_core.chat_history import BaseChatMessageHistory
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
from hippodid import HippoDid
class HippoDidMemory(BaseChatMessageHistory):
"""LangChain chat history backed by HippoDid persistent memory."""
def __init__(self, api_key: str, character_id: str):
self.client = HippoDid(api_key=api_key)
self.character_id = character_id
@property
def messages(self) -> list[BaseMessage]:
results = self.client.search_memories(
character_id=self.character_id,
query="recent conversation history",
limit=20,
)
msgs = []
for mem in results["memories"]:
role = mem.get("category", "")
if role == "user_messages":
msgs.append(HumanMessage(content=mem["content"]))
else:
msgs.append(AIMessage(content=mem["content"]))
return msgs
def add_message(self, message: BaseMessage) -> None:
self.client.add_memory(
character_id=self.character_id,
content=message.content,
category="user_messages" if isinstance(message, HumanMessage) else "agent_responses",
)
def clear(self) -> None:
pass # memories persist by design
Working example: agent with persistent memory
This creates a LangChain agent that remembers user preferences across sessions in about 15 lines of setup code.
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
# 1. Set up the LLM and prompt
llm = ChatOpenAI(model="gpt-4o")
prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful customer support agent. Use the conversation "
"history to personalize your responses. {context}"),
MessagesPlaceholder(variable_name="history"),
("human", "{input}"),
])
chain = prompt | llm
# 2. Wrap with HippoDid-backed history
agent = RunnableWithMessageHistory(
chain,
lambda session_id: HippoDidMemory(
api_key="hd_key_...",
character_id="YOUR_CHARACTER_ID",
),
input_messages_key="input",
history_messages_key="history",
)
# 3. Invoke — memories persist across sessions
response = agent.invoke(
{"input": "I'd like to reorder my usual — dark roast, no sugar", "context": ""},
config={"configurable": {"session_id": "customer-42"}},
)
print(response.content)
The next time this customer calls, the agent already knows they prefer dark roast with no sugar.
Feeding assemble_context into a LangChain prompt
Instead of raw message history, you can use HippoDid’s assemble_context() to build a structured context block that includes the character’s profile, relevant memories, and personality. This gives the agent richer context than plain chat history.
from hippodid import HippoDid
client = HippoDid(api_key="hd_key_...")
# Build a structured context block for the current query
context = client.assemble_context(
character_id="YOUR_CHARACTER_ID",
query="customer asking about their usual order",
strategy="concierge",
max_tokens=2000,
)
# Inject into the system prompt
prompt = ChatPromptTemplate.from_messages([
("system", "You are a customer support agent.\n\n{context}"),
("human", "{input}"),
])
chain = prompt | llm
response = chain.invoke({
"input": "What did I order last time?",
"context": context,
})
The context variable contains a formatted block with the customer’s profile, preferences, and recent interaction history, ready to paste into any prompt template.
Assembly strategies for different agent types
Different agents need different context formats. Use the strategy parameter to control how assemble_context() structures the output.
# Customer support agent — service-oriented, customer context first
support_context = client.assemble_context(
character_id=customer_id,
query=user_message,
strategy="concierge",
max_tokens=2000,
)
# Coding assistant — structured sections for task execution
dev_context = client.assemble_context(
character_id=project_id,
query=task_description,
strategy="task_focused",
max_tokens=3000,
)
# Conversational companion — personality-forward, chat-optimized
chat_context = client.assemble_context(
character_id=companion_id,
query=user_message,
strategy="conversational",
max_tokens=1500,
)
See the Assembly Strategies guide for all five strategies and their formatted output.
Example: customer support agent that remembers preferences
A complete example showing a support agent that builds up knowledge about each customer over time.
from hippodid import HippoDid
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
client = HippoDid(api_key="hd_key_...")
llm = ChatOpenAI(model="gpt-4o")
def handle_support_ticket(customer_id: str, message: str) -> str:
# 1. Retrieve the customer's context with concierge strategy
context = client.assemble_context(
character_id=customer_id,
query=message,
strategy="concierge",
max_tokens=2000,
)
# 2. Generate the response
prompt = ChatPromptTemplate.from_messages([
("system",
"You are a support agent for Acme Corp. Use the customer context below "
"to personalize your response. If you learn new preferences, mention them "
"naturally.\n\n{context}"),
("human", "{input}"),
])
chain = prompt | llm
response = chain.invoke({"input": message, "context": context})
# 3. Store the interaction as a new memory
client.add_memory(
character_id=customer_id,
content=f"Customer said: {message}\nAgent responded: {response.content}",
)
return response.content
# First interaction
print(handle_support_ticket("cust-42", "I need to return a blender. I always have trouble with your small appliances."))
# Next week — the agent remembers the appliance issues
print(handle_support_ticket("cust-42", "I want to buy a toaster. Any recommendations?"))
On the second call, the agent’s context includes the previous return and the customer’s note about small appliances, so it can proactively address reliability concerns.
Next steps
- Assembly Strategies — all five strategies with example output
- Memory Modes — EXTRACTED vs VERBATIM vs HYBRID
- Quick Start — create your first character
- API Reference — full endpoint documentation