DEV Community

Cover image for MongoDB as the Brain of Modern AI Applications
Alex Bevilacqua
Alex Bevilacqua

Posted on • Originally published at alexbevi.com

MongoDB as the Brain of Modern AI Applications

Production agents need two persistence layers: thread-scoped state and cross-session memory. Google ADK Sessions stores events and state for a single conversation, while MemoryService handles recall across sessions. LangGraph memory makes the same split with a checkpointer for short-term memory and a store for long-term memory, and LangChain long-term memory builds on LangGraph stores that persist JSON documents by namespace and key. The memory architecture has already converged.

Durable memory is not raw chat replay. Vertex AI Memory Bank is built for identity-scoped, cross-session personalization and LLM-driven knowledge extraction, and Google’s ADK memory write-up describes Memory Bank as extracting key information from session data rather than replaying every turn. LangChain’s memory model is equally explicit: long-term memory can be semantic (facts), episodic (past actions), or procedural (rules and prompts).

Agent memory should be structured data, not opaque blobs. LangChain stores persist long-term memory as JSON documents, and ADK’s DatabaseSessionService migration moved session serialization from pickle-based storage to JSON-based storage in v1.22.0. MongoDB’s document model matches that reality directly.

MongoDB is a strong fit because retrieval lives in the same system as the memory. MongoDB Vector Search supports both approximate and exact nearest-neighbor search, and the default index type is HNSW. Vector search pre-filters let you scope recall by fields like user_id, tenant_id, or memory_type before embeddings are compared. Hybrid search combines vector and full-text retrieval with reciprocal rank fusion, which is exactly what memory needs when the data mixes natural language with exact identifiers like invoice IDs, feature flags, or product SKUs.

Memory also needs retention and update policy. TTL indexes automatically expire session artifacts, scratchpads, or short-lived summaries. Change streams give you a real-time feed of inserts and updates, which is the right trigger for summarization, entity extraction, or memory distillation jobs. When the data is relationship-heavy instead of chunk-heavy, GraphRAG on MongoDB uses entities, edges, and $graphLookup for relationship-aware, multi-hop retrieval.

This is not a narrow LangChain story. MongoDB publishes integrations for LangGraph, LangChain, LlamaIndex, Semantic Kernel, Haystack, Spring AI, CrewAI, and Vertex AI. LlamaIndex can use MongoDB for the vector store, document store, and index store. Mem0 also supports MongoDB as a memory backend. MongoDB fits the storage contract these frameworks keep converging on: structured documents plus semantic, lexical, and graph-based retrieval.

LangGraph: short-term checkpoints and long-term memory in one database

MongoDB’s LangGraph integration exposes MongoDBSaver for checkpoints and MongoDBStore for durable memory, with optional vector indexing and TTL-based expiry. That maps directly to LangGraph’s own split between thread persistence and store-backed recall.

from pymongo import MongoClient
from langgraph.checkpoint.mongodb import MongoDBSaver
from langgraph.store.mongodb import MongoDBStore, create_vector_index_config
from langchain_openai import OpenAIEmbeddings

MONGODB_URI = "<connection-string>"

# Short-term memory: thread checkpoints
client = MongoClient(MONGODB_URI)
checkpointer = MongoDBSaver(client)

# Long-term memory: semantic store with metadata filters
index_config = create_vector_index_config(
    embed=OpenAIEmbeddings(model="text-embedding-3-small"),
    dims=1536,
    fields=["content"],
    filters=["user_id", "memory_type"],
)

# Assume `builder` is an existing LangGraph StateGraph
with MongoDBStore.from_conn_string(
    conn_string=MONGODB_URI,
    db_name="agent_memory",
    collection_name="memories",
    index_config=index_config,
    ttl_config={
        "default_ttl": 60 * 60 * 24 * 30,   # 30 days
        "refresh_on_read": True,
    },
) as store:
    graph = builder.compile(checkpointer=checkpointer, store=store)

    store.put(
        namespace=("user-42", "memories"),
        key="pref:vegan:soho",
        value={
            "content": "User prefers vegan restaurants near SoHo.",
            "user_id": "user-42",
            "memory_type": "semantic",
        },
    )

    results = store.search(
        ("user-42", "memories"),
        query="Where should I book dinner tonight?",
        limit=3,
    )

    for result in results:
        print(result.value)
Enter fullscreen mode Exit fullscreen mode

This is the clean MongoDB story: checkpoints for working memory, a store for long-term memory, vector retrieval for recall, metadata filters for isolation, and TTL for automatic cleanup. The same database handles all of it.

LangChain: chat history plus hybrid recall

At the LangChain layer, MongoDB covers both conversation state and retrieval. MongoDBChatMessageHistory persists per-session message history, MongoDBAtlasVectorSearch stores semantic memories, and MongoDBAtlasHybridSearchRetriever fuses lexical and semantic recall.

from langchain_core.documents import Document
from langchain_mongodb.chat_message_histories import MongoDBChatMessageHistory
from langchain_mongodb.retrievers.hybrid_search import MongoDBAtlasHybridSearchRetriever
from langchain_mongodb.vectorstores import MongoDBAtlasVectorSearch
from langchain_openai import OpenAIEmbeddings

MONGODB_URI = "<connection-string>"

history = MongoDBChatMessageHistory(
    session_id="user-42:thread-7",
    connection_string=MONGODB_URI,
    database_name="agent_memory",
    collection_name="chat_history",
    history_size=20,
)

vector_store = MongoDBAtlasVectorSearch.from_connection_string(
    connection_string=MONGODB_URI,
    namespace="agent_memory.user_memories",
    embedding=OpenAIEmbeddings(model="text-embedding-3-small"),
    index_name="vector_index",
)

vector_store.add_documents(
    [
        Document(
            page_content="User prefers vegan restaurants near SoHo.",
            metadata={"user_id": "user-42", "memory_type": "semantic"},
        ),
        Document(
            page_content="Invoice 8419 was disputed last month.",
            metadata={"user_id": "user-42", "memory_type": "episodic"},
        ),
    ]
)

history.add_user_message("Find dinner options for me in SoHo.")

retriever = MongoDBAtlasHybridSearchRetriever(
    vectorstore=vector_store,
    search_index_name="search_index",
    top_k=5,
    fulltext_penalty=50,
    vector_penalty=50,
)

docs = retriever.invoke("Find dinner options for a vegan user in SoHo")
for doc in docs:
    print(doc.page_content, doc.metadata)
Enter fullscreen mode Exit fullscreen mode

Hybrid retrieval is not optional in a serious memory system. "Prefers vegan restaurants in SoHo" is semantic. "Invoice 8419" is lexical. MongoDB’s hybrid retriever exists because production memory contains both.

Google ADK: the Sessions-and-Memory model maps cleanly to MongoDB

Google ADK makes the architecture explicit. SessionService manages session objects, applies state_delta, and appends event history. MemoryService manages long-term semantic memory across sessions. The Sessions docs currently list InMemorySessionService, VertexAiSessionService, and DatabaseSessionService, so MongoDB is not a built-in backend today. But ADK exposes base session and memory service abstractions, which makes MongoDB a natural implementation target rather than a workaround. Memory Bank then adds the identity-scoped memory semantics on top.

A MongoDB-backed ADK deployment should separate sessions, events, and distilled memories into dedicated collections. That mirrors ADK’s documented split between mutable session state, append-only event history, and searchable long-term memory.

from datetime import datetime, timezone
from pymongo import ASCENDING, MongoClient

MONGODB_URI = "<connection-string>"

client = MongoClient(MONGODB_URI)
db = client["agent_memory"]

sessions = db["adk_sessions"]
events = db["adk_events"]
memories = db["adk_memories"]

sessions.create_index(
    [("app_name", ASCENDING), ("user_id", ASCENDING), ("session_id", ASCENDING)],
    unique=True,
)

events.create_index(
    [
        ("app_name", ASCENDING),
        ("user_id", ASCENDING),
        ("session_id", ASCENDING),
        ("timestamp", ASCENDING),
    ]
)

memories.create_index([("user_id", ASCENDING), ("memory_type", ASCENDING)])
# Create a MongoDB Vector Search index on memories.embedding
# Mark user_id and memory_type as filter fields in the index definition.

def persist_session_turn(app_name: str, user_id: str, session_id: str, event: dict, state: dict) -> None:
    now = datetime.now(timezone.utc)

    sessions.update_one(
        {"app_name": app_name, "user_id": user_id, "session_id": session_id},
        {
            "$set": {"state": state, "updated_at": now},
            "$setOnInsert": {"created_at": now},
        },
        upsert=True,
    )

    events.insert_one(
        {
            "app_name": app_name,
            "user_id": user_id,
            "session_id": session_id,
            "timestamp": event["timestamp"],
            "event": event,
        }
    )

def store_memory(user_id: str, content: str, embedding: list[float], source_session_id: str) -> None:
    memories.insert_one(
        {
            "user_id": user_id,
            "memory_type": "semantic",
            "content": content,
            "embedding": embedding,
            "source_session_ids": [source_session_id],
            "created_at": datetime.now(timezone.utc),
        }
    )

def search_memories(user_id: str, query_embedding: list[float]):
    pipeline = [
        {
            "$vectorSearch": {
                "index": "memory_vector_index",
                "path": "embedding",
                "queryVector": query_embedding,
                "numCandidates": 100,
                "limit": 5,
                "filter": {"user_id": user_id, "memory_type": "semantic"},
            }
        },
        {
            "$project": {
                "content": 1,
                "memory_type": 1,
                "score": {"$meta": "vectorSearchScore"},
            }
        },
    ]
    return list(memories.aggregate(pipeline))
Enter fullscreen mode Exit fullscreen mode

This is a design sketch, not an official ADK adapter. The point is that ADK’s abstractions already describe a storage model MongoDB handles well: mutable session state, append-only events, and searchable long-term memory. Once a MemoryService exists, ADK’s built-in PreloadMemory and LoadMemory tools can use it.

Dedicated memory layers also fit: Mem0 on MongoDB

MongoDB is not only useful when memory is native to the agent framework. Mem0’s MongoDB backend supports MongoDB directly as a vector database for memory storage and retrieval. That matters because it shows MongoDB works both as the application database and as the substrate beneath a dedicated memory layer.

from mem0 import Memory

config = {
    "vector_store": {
        "provider": "mongodb",
        "config": {
            "db_name": "mem0_db",
            "collection_name": "mem0_collection",
            "mongo_uri": "<connection-string>",
        },
    }
}

memory = Memory.from_config(config)

messages = [
    {"role": "user", "content": "I'm planning to watch a movie tonight."},
    {"role": "assistant", "content": "What genres do you like?"},
    {"role": "user", "content": "I love sci-fi, not thrillers."},
]

memory.add(messages, user_id="alice", metadata={"category": "movies"})
Enter fullscreen mode Exit fullscreen mode

The architectural value is the same as in LangGraph and LangChain: persistent memory objects, vector retrieval, and application data can live in one operational system instead of being spread across separate services.

Why MongoDB is the best choice here

MongoDB is the best choice when you want one system to hold agent state, long-term memory, retrieval data, and the application records the agent reasons over. The document model matches how current frameworks persist memory. Vector Search and Search cover recall. TTL indexes and change streams cover retention and event-driven memory extraction. GraphRAG covers relationship-heavy data. The result is not "a vector store with extra features." It is a memory layer that can also be the system of record. That is why MongoDB works as the brain of a modern AI application.

Top comments (0)