Issue 25 | Long-Term Memory: Storing Long-Term Preferences in Graph

Updated on 4/16/2026

Hello, future AI architects! I am your old friend, the AI technical mentor. Welcome to Issue 25 of the "LangGraph Multi-Agent Expert Course".

We have built a quite powerful "Universal AI Content Creation Agency". The Planner strategizes, the Researcher reads extensively, the Writer produces brilliant prose, and the Editor is the last line of defense for our content. But have you ever encountered this situation: for the same client, you have to re-explain their brand tone, preferred terminology, or even their personal definition of "sense of humor" every single time? It's like having to explain what hairstyle you want from scratch every time you go to the barbershop—highly inefficient and a terrible user experience.

Today, we are going to solve this pain point—equipping our AI Editor Agent with "long-term memory" so it can remember the user's historical preferences and truly "understand you". This will be a crucial step for your AI agency to evolve from "capable of working" to "proactive and considerate".

🎯 Learning Objectives in This Issue

In this issue, you will not only learn technology but also understand how to use technology to solve actual business problems. Specifically, you will gain:

  1. Understand the core value of long-term memory: Why short-term memory (Graph State) is insufficient to support complex user interactions in multi-agent systems, and how long-term memory improves user experience and system efficiency.
  2. Master the construction paradigm of long-term memory in LangGraph: Learn how to cleverly combine VectorStore to store unstructured user preferences, and utilize LangGraph's Checkpointers to manage session states and memory pointers.
  3. Practical personalized memory for the AI Editor Agent: Build a workflow for our Editor Agent that can "remember" the user's specific tone, style, and historical feedback, making it a truly considerate "personal editor".
  4. Identify and avoid common pitfalls of long-term memory: Delve into advanced issues such as memory bloat, memory conflicts, and retrieval accuracy, and provide practical solutions.

Ready? Let's install a "memory chip" into our AI together!

📖 Principle Analysis

In previous lessons, we mainly relied on LangGraph's Graph State to maintain short-term dialogue and task context between agents. This is like human "working memory," processing the current ongoing task. However, once a session ends, or when it's necessary to span multiple sessions to remember a user's personalized needs, short-term memory falls short.

Imagine our AI Editor Agent. One client might like a humorous tone, while another prefers a rigorous and professional style. If the Editor Agent has to re-"learn" these preferences at the start of every editing task, it will never become a truly efficient and personalized service provider. This is why we need to introduce "long-term memory".

Long-term memory, as the name suggests, is information that can be persistently stored and retrieved and utilized when needed. In the context of LangGraph, we usually implement it through a combination of two mechanisms:

  1. VectorStore: This is a database that stores unstructured data (such as text, images, audio, etc.) and enables semantic retrieval. We embed the user's personalized preferences, brand style guides, historical feedback, etc., into vectors in text format, and then store them in the VectorStore. When needed, we can perform semantic similarity searches based on the current task or user ID to retrieve the most relevant preference information and inject it as context into the LLM's prompt. This solves the problem of "what to remember" and "how to retrieve".

  2. Checkpointers: LangGraph's built-in Checkpointer mechanism allows us to persist the current state of the Graph into a database (like SQLite). It is mainly used to resume interrupted sessions or maintain state continuity across multiple iterations. In this issue, we will use the Checkpointer to save each user's session ID (thread_id), along with indices or summaries that might point to specific memory entries in the VectorStore. This solves the problem of "where the memory is" and "how to associate it".

The core idea is: The Checkpointer is responsible for remembering "whose session this is, and where they left off last time," while the VectorStore is responsible for storing "all the long-term preferences and historical experiences of this user." When an editing task arrives, the Graph first identifies the user through the Checkpointer, then uses this user ID to retrieve their exclusive long-term preferences from the VectorStore, and finally uses these preferences as additional context to guide the Editor Agent in personalized editing. If the user is unsatisfied with the editing result and provides feedback, this feedback can be added to the VectorStore in a structured or unstructured way, thereby "teaching" the AI Editor Agent to do better next time.

Long-Term Memory Workflow of the AI Editor Agent:

  1. Task Reception: The AI Content Agency receives a new content editing request, accompanied by user_id and content_to_edit.
  2. Retrieve Preferences:
    • The Graph first identifies the user_id corresponding to the current thread_id via the Checkpointer.
    • Using user_id as the query condition, it retrieves all historical preferences, style guides, and previous feedback of the user from the VectorStore.
    • Integrates the retrieved preference information into a clear set of instructions.
  3. Personalized Editing (Editor Agent):
    • Injects the original content and retrieved personalized preferences into the Editor Agent's LLM prompt.
    • The LLM polishes and optimizes the content based on these instructions.
  4. Process Feedback:
    • If the user provides feedback on the editing result (e.g., "Too formal, make it livelier!").
    • The Graph receives the feedback and processes it (can be summarized by another LLM, or stored directly).
    • Adds the processed feedback as a new memory entry to the VectorStore and associates it with the user_id.
  5. Memory Update: The Checkpointer automatically saves the latest state of the Graph, including any potentially updated memory pointers or summaries.

Through this cycle, our AI Editor Agent will no longer be a "disposable" tool, but a "personal assistant" capable of continuously learning and adapting with each interaction.

Mermaid Diagram of Core Architecture

Let's intuitively understand this workflow through a Mermaid diagram:

graph TD
    A[User Submits Editing Request] --> B{Identify User/Session ID}
    B -- thread_id --> C(LangGraph Checkpointer)
    C -- user_id --> D[Retrieve User Long-Term Preferences]
    D -- Query --> E(VectorStore: Preferences/Feedback)
    E -- Retrieval Results --> D
    D -- Inject Context --> F[Editor Agent (LLM)]
    F --> G[Generate Edited Content]
    G --> H{User Review/Feedback?}
    H -- Yes --> I[Process User Feedback]
    I -- Update Memory --> E
    I --> G'[[Editing Complete/Re-edit]]'
    H -- No --> J[Complete Editing, Deliver]

    subgraph LangGraph Workflow
        B --- F
        F --- I
    end

    style C fill:#f9f,stroke:#333,stroke-width:2px
    style E fill:#ccf,stroke:#333,stroke-width:2px
    style D fill:#afa,stroke:#333,stroke-width:2px
    style I fill:#afa,stroke:#333,stroke-width:2px

Diagram Legend:

  • User Submits Editing Request (A): The starting point of the work, containing content and user identification.
  • Identify User/Session ID (B): LangGraph's internal mechanism, identifying the current session via configurable: thread_id.
  • LangGraph Checkpointer (C): Persists session state, saves user_id or its associated information, ensuring session continuity.
  • Retrieve User Long-Term Preferences (D): This is a LangGraph node responsible for fetching relevant memories from the VectorStore based on user_id.
  • VectorStore: Preferences/Feedback (E): Our long-term memory bank, storing vector representations of all users' personalized preferences, style guides, and historical feedback.
  • Editor Agent (LLM) (F): Our core editing agent, receiving the original content and retrieved personalized preferences for editing.
  • Generate Edited Content (G): The output of the Editor Agent.
  • User Review/Feedback? (H): Simulates the user's judgment on the editing result.
  • Process User Feedback (I): Another LangGraph node responsible for receiving user feedback, converting it into storable memory, and updating the VectorStore.
  • Complete Editing, Deliver (J): The endpoint of the workflow.

This architecture clearly demonstrates how the Checkpointer and VectorStore work together to provide powerful long-term memory capabilities for our agents.

💻 Practical Code Walkthrough (Specific Application in the Agency Project)

Alright, enough theory, let's roll up our sleeves and get to work. Now, we will translate the above principles into code to inject long-term memory into the Editor Agent in our AI Content Agency. We will use Python and some popular LangChain/LangGraph components.

1. Environment Preparation and Dependency Installation

First, ensure your environment has the necessary libraries installed:

pip install -U langchain-openai langgraph langchain_community chromadb tiktoken

2. Code Implementation

import os
import sqlite3
from typing import TypedDict, Annotated, List, Literal
from langchain_core.messages import BaseMessage, HumanMessage
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_text_splitters import RecursiveCharacterTextSplitter # Used for processing large texts
from langgraph.graph import StateGraph, END
from langgraph.checkpoint import MemorySaver, SqliteSaver # Used for persisting state

# Set your OpenAI API key
# Recommended to set via environment variables, e.g.: export OPENAI_API_KEY="sk-..."
# os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY" # Please replace with your actual key or set via environment variables

# Ensure the API key is set
if not os.getenv("OPENAI_API_KEY"):
    raise ValueError("Please set the OPENAI_API_KEY environment variable")

print("--- Environment preparation complete, API key loaded ---")

### 1. Define Graph State ###
# TypedDict is used to define the state schema of LangGraph, ensuring type safety and clarity
class AgentState(TypedDict):
    """
    Define the shared state in our multi-agent workflow.
    This state will be passed between all nodes.
    """
    content: str # Original content to be edited
    user_id: str # Unique user ID for retrieving preferences
    preferences: str # User preferences retrieved from VectorStore
    edited_content: str # Content after AI editing
    feedback: str # User feedback on the edited content
    # A list of historical messages can be added for short-term dialogue context, but this issue focuses on long-term memory
    # chat_history: List[BaseMessage]

print("--- AgentState definition complete ---")

### 2. Initialize VectorStore (Long-Term Memory Bank) ###
# We use ChromaDB as the vector database to store user preferences and historical feedback.
# It supports local persistence, which is convenient for demonstration.

# Initialize the OpenAI embedding model to convert text into vectors
embeddings = OpenAIEmbeddings()

# Initialize ChromaDB. We persist the data to the local file system to maintain memory across sessions.
# persist_directory is the path to store the database files
VECTOR_DB_PATH = "./chroma_db_editor_memory"
vectorstore = Chroma(embedding_function=embeddings, persist_directory=VECTOR_DB_PATH)

# Simulate some initial user preference documents
# In a real scenario, this data might come from the client's brand guidelines, style manuals, or initial communications
user_preferences_data = [
    {"user_id": "client_A", "preference": "Tone: Lively, humorous, concise. Avoid lengthy and overly formal expressions.", "doc_id": "pref_A_1"},
    {"user_id": "client_A", "preference": "Keywords: Innovation, cutting-edge technology, future trends. Use metaphors and analogies frequently.", "doc_id": "pref_A_2"},
    {"user_id": "client_B", "preference": "Tone: Professional, rigorous, detail-rich. Emphasize data support and logical reasoning.", "doc_id": "pref_B_1"},
    {"user_id": "client_B", "preference": "Keywords: Market analysis, ROI, risk management, compliance. Avoid subjective judgments.", "doc_id": "pref_B_2"},
    {"user_id": "client_C", "preference": "Tone: Approachable, easy to understand. Avoid industry jargon and complex sentence structures. Target audience is the general public.", "doc_id": "pref_C_1"},
    {"user_id": "client_C", "preference": "Keywords: Healthy living, food, travel, parenting. Content should be positive and uplifting.", "doc_id": "pref_C_2"},
]

# Check if VectorStore is already populated to avoid duplicate additions
# This is a simple check; a more rigorous approach would be to check if doc_id exists
existing_docs = vectorstore.get()
if not existing_docs["ids"]: # If the database is empty, populate it
    for data in user_preferences_data:
        vectorstore.add_texts(
            texts=[data["preference"]],
            metadatas=[{"user_id": data["user_id"], "doc_id": data["doc_id"]}]
        )
    print(f"--- VectorStore initialized and populated with {len(user_preferences_data)} initial user preferences. ---")
else:
    print(f"--- VectorStore already contains {len(existing_docs['ids'])} records, skipping initial population. ---")


### 3. Define Agent (LLM) and Graph Nodes ###

# Initialize the ChatOpenAI model as our editing brain
llm = ChatOpenAI(model="gpt-4o", temperature=0.5) # Set temperature slightly lower for more stable editing

# --- Graph Node 1: Retrieve User Preferences ---
def retrieve_preferences(state: AgentState):
    """
    Retrieve the user's long-term preferences and historical feedback from VectorStore based on user_id.
    """
    user_id = state["user_id"]
    if not user_id:
        print("Warning: user_id not provided, will use general editing mode.")
        return {"preferences": "No specific user preferences. Please edit in a general, professional, and clear style."}

    # Construct a query to attempt retrieving preferences related to this user
    # More complex query logic can be added here, such as retrieving based on the current content type
    query = f"Content creation preferences, style requirements, and historical feedback for user {user_id}."
    
    # Execute similarity search and filter documents matching the current user_id
    # LangChain's Chroma does not support filtering metadata directly in similarity_search by default
    # So we retrieve first, then filter in code, or use Chroma's advanced query interface (if needed)
    # To simplify the demonstration, we assume the retrieval results contain enough relevant information and will be refined by the LLM
    
    # In actual production, a better approach is to use Chroma's `similarity_search_with_score` or `similarity_search_by_vector`
    # combined with the `where` parameter for filtering, but for demonstration purposes here, we retrieve first and filter manually
    
    # Retrieve all relevant documents, then filter manually
    results = vectorstore.similarity_search_with_score(query, k=5) # Retrieve the top 5 most relevant
    
    # Filter out the preferences that truly belong to the current user_id
    filtered_preferences = []
    for doc, score in results:
        if doc.metadata.get("user_id") == user_id:
            filtered_preferences.append(doc.page_content)
            # print(f"  - Matched preference (Score: {score:.2f}): {doc.page_content[:50]}...")
    
    preferences_str = "\n".join(filtered_preferences)
    if not preferences_str:
        preferences_str = "No specific user preferences. Please edit in a general, professional, and clear style."
        print(f"--- No specific preferences retrieved for user {user_id}, using general mode. ---")
    else:
        print(f"--- Successfully retrieved preferences for user {user_id}:\n{preferences_str}\n---")
    
    return {"preferences": preferences_str}

# --- Graph Node 2: AI Editor ---
def editor_agent_node(state: AgentState):
    """
    AI Editor node, edits based on the original content and retrieved user preferences.
    """
    content = state["content"]
    preferences = state["preferences"]
    user_id = state["user_id"]

    prompt = f"""
    You are an experienced and highly insightful senior content editor.
    Your task is to polish and optimize the following content for user {user_id}.
    
    Please ensure you strictly follow the user's personalized preferences and style requirements below for editing.
    These preferences are accumulated over the long term by the user and represent their brand voice and expectations.
    
    --- User Preferences and Style Requirements ---
    {preferences}
    ---
    
    Original