--- title: "LangGraph" sidebarTitle: "LangGraph" description: "Add persistent memory to LangGraph agents with Supermemory" icon: "/images/langgraph.svg" --- Build stateful agents with LangGraph that remember context across sessions. Supermemory handles memory storage and retrieval while LangGraph manages your graph-based conversation flow. ## Overview This guide shows how to integrate Supermemory with LangGraph to create agents that: - Maintain user context through automatic profiling - Store and retrieve relevant memories at each node - Use conditional logic to decide what's worth remembering - Combine short-term (session) and long-term (cross-session) memory ## Setup Install the required packages: ```bash pip install langgraph langchain-openai supermemory python-dotenv ``` Configure your environment: ```bash # .env SUPERMEMORY_API_KEY=your-supermemory-api-key OPENAI_API_KEY=your-openai-api-key ``` Get your Supermemory API key from [console.supermemory.ai](https://console.supermemory.ai). ## Basic integration A minimal agent that fetches user context before responding and stores the conversation after: ```python from typing import Annotated, TypedDict from langgraph.graph import StateGraph, START, END from langgraph.graph.message import add_messages from langchain_openai import ChatOpenAI from langchain_core.messages import SystemMessage, HumanMessage from supermemory import Supermemory from dotenv import load_dotenv load_dotenv() llm = ChatOpenAI(model="gpt-4o") memory = Supermemory() class State(TypedDict): messages: Annotated[list, add_messages] user_id: str def agent(state: State): user_id = state["user_id"] messages = state["messages"] user_query = messages[-1].content # Fetch user profile with relevant memories profile_result = memory.profile(container_tag=user_id, q=user_query) # Build context from profile static_facts = profile_result.profile.static or [] dynamic_context = profile_result.profile.dynamic or [] search_results = profile_result.search_results.results if profile_result.search_results else [] context = f""" User Background: {chr(10).join(static_facts) if static_facts else 'No profile yet.'} Recent Context: {chr(10).join(dynamic_context) if dynamic_context else 'No recent activity.'} Relevant Memories: {chr(10).join([r.memory or r.chunk for r in search_results]) if search_results else 'None found.'} """ system = SystemMessage(content=f"You are a helpful assistant.\n\n{context}") response = llm.invoke([system] + messages) # Store the interaction memory.add( content=f"User: {user_query}\nAssistant: {response.content}", container_tag=user_id ) return {"messages": [response]} # Build the graph graph = StateGraph(State) graph.add_node("agent", agent) graph.add_edge(START, "agent") graph.add_edge("agent", END) app = graph.compile() # Run it result = app.invoke({ "messages": [HumanMessage(content="Hi! I'm working on a Python project.")], "user_id": "user_123" }) print(result["messages"][-1].content) ``` --- ## Core concepts ### User profiles Supermemory automatically builds user profiles from stored memories: - **Static facts**: Long-term information (preferences, expertise, background) - **Dynamic context**: Recent activity and current focus ```python result = memory.profile( container_tag="user_123", q="optional search query" # Also returns relevant memories ) print(result.profile.static) # ["User is a Python developer", "Prefers functional style"] print(result.profile.dynamic) # ["Working on async patterns", "Debugging rate limiting"] ``` ### Memory storage Content you add gets processed into searchable memories: ```python # Store a conversation memory.add( content="User asked about graph traversal. Explained BFS vs DFS.", container_tag="user_123", metadata={"topic": "algorithms", "type": "conversation"} ) # Store a document memory.add( content="https://langchain-ai.github.io/langgraph/", container_tag="user_123" ) ``` ### Memory search Search returns both extracted memories and document chunks: ```python results = memory.search.memories( q="graph algorithms", container_tag="user_123", search_mode="hybrid", limit=5 ) for r in results.results: print(r.memory or r.chunk, r.similarity) ``` --- ## Complete example: support agent A support agent that learns from past tickets and adapts to each user's technical level: ```python from typing import Annotated, TypedDict, Optional from langgraph.graph import StateGraph, START, END from langgraph.graph.message import add_messages from langgraph.checkpoint.memory import MemorySaver from langchain_openai import ChatOpenAI from langchain_core.messages import SystemMessage, HumanMessage from supermemory import Supermemory from dotenv import load_dotenv load_dotenv() class SupportAgent: def __init__(self): self.llm = ChatOpenAI(model="gpt-4o", temperature=0.3) self.memory = Supermemory() self.app = self._build_graph() def _build_graph(self): class State(TypedDict): messages: Annotated[list, add_messages] user_id: str context: str category: Optional[str] def retrieve_context(state: State): """Fetch user profile and relevant past tickets.""" user_id = state["user_id"] query = state["messages"][-1].content result = self.memory.profile( container_tag=user_id, q=query, threshold=0.5 ) static = result.profile.static or [] dynamic = result.profile.dynamic or [] memories = result.search_results.results if result.search_results else [] context = f""" ## User Profile {chr(10).join(f"- {fact}" for fact in static) if static else "New user, no history."} ## Current Context {chr(10).join(f"- {ctx}" for ctx in dynamic) if dynamic else "No recent activity."} ## Related Past Tickets {chr(10).join(f"- {m.memory}" for m in memories[:3]) if memories else "No similar issues found."} """ return {"context": context} def categorize(state: State): """Determine ticket category for routing.""" query = state["messages"][-1].content.lower() if any(word in query for word in ["billing", "payment", "charge", "invoice"]): return {"category": "billing"} elif any(word in query for word in ["bug", "error", "broken", "crash"]): return {"category": "technical"} else: return {"category": "general"} def respond(state: State): """Generate a response using context.""" category = state.get("category", "general") context = state.get("context", "") system_prompt = f"""You are a support agent. Category: {category} {context} Guidelines: - Match explanation depth to the user's technical level - Reference past interactions when relevant - Be direct and helpful""" system = SystemMessage(content=system_prompt) response = self.llm.invoke([system] + state["messages"]) return {"messages": [response]} def store_interaction(state: State): """Save the ticket for future context.""" user_msg = state["messages"][-2].content ai_msg = state["messages"][-1].content category = state.get("category", "general") self.memory.add( content=f"Support ticket ({category}): {user_msg}\nResolution: {ai_msg[:300]}", container_tag=state["user_id"], metadata={"type": "support_ticket", "category": category} ) return {} # Build the graph graph = StateGraph(State) graph.add_node("retrieve", retrieve_context) graph.add_node("categorize", categorize) graph.add_node("respond", respond) graph.add_node("store", store_interaction) graph.add_edge(START, "retrieve") graph.add_edge("retrieve", "categorize") graph.add_edge("categorize", "respond") graph.add_edge("respond", "store") graph.add_edge("store", END) checkpointer = MemorySaver() return graph.compile(checkpointer=checkpointer) def handle(self, user_id: str, message: str, thread_id: str) -> str: """Process a support request.""" config = {"configurable": {"thread_id": thread_id}} result = self.app.invoke( {"messages": [HumanMessage(content=message)], "user_id": user_id}, config=config ) return result["messages"][-1].content # Usage if __name__ == "__main__": agent = SupportAgent() # First interaction response = agent.handle( user_id="customer_alice", message="The API is returning 429 errors when I make requests", thread_id="ticket_001" ) print(response) # Follow-up (agent remembers context) response = agent.handle( user_id="customer_alice", message="I'm only making 10 requests per minute though", thread_id="ticket_001" ) print(response) ``` --- ## Advanced patterns ### Conditional memory storage Not everything is worth remembering. Use conditional edges to filter: ```python def should_store(state: State) -> str: """Skip storing trivial messages.""" last_msg = state["messages"][-1].content.lower() skip_phrases = ["thanks", "ok", "got it", "bye"] if len(last_msg) < 20 or any(p in last_msg for p in skip_phrases): return "skip" return "store" graph.add_conditional_edges("respond", should_store, { "store": "store", "skip": END }) ``` ### Parallel memory operations Fetch memories and categorize at the same time: ```python from langgraph.graph import StateGraph, START, END graph = StateGraph(State) graph.add_node("retrieve", retrieve_context) graph.add_node("categorize", categorize) graph.add_node("respond", respond) # Both run in parallel after START graph.add_edge(START, "retrieve") graph.add_edge(START, "categorize") # Both must complete before respond graph.add_edge("retrieve", "respond") graph.add_edge("categorize", "respond") graph.add_edge("respond", END) ``` ### Metadata filtering Organize memories by project, topic, or any custom field: ```python # Store with metadata memory.add( content="User prefers detailed error messages with stack traces", container_tag="user_123", metadata={ "type": "preference", "project": "api-v2", "priority": "high" } ) # Search with filters results = memory.search.memories( q="error handling preferences", container_tag="user_123", filters={ "AND": [ {"key": "type", "value": "preference"}, {"key": "project", "value": "api-v2"} ] } ) ``` ### Combining session and long-term memory LangGraph's checkpointer handles within-session state. Supermemory handles cross-session memory. Use both: ```python from langgraph.checkpoint.memory import MemorySaver # Session memory (cleared when thread ends) checkpointer = MemorySaver() app = graph.compile(checkpointer=checkpointer) # Long-term memory (persists across sessions) # Handled by Supermemory in your nodes ``` --- ## Next steps Deep dive into automatic user profiling Advanced search patterns and filtering Native OpenAI integration with memory tools Memory middleware for Next.js apps