Skip to content

← Blog#ai-agents

Next.js Agent Memory: mem0 vs pgvector - Which I Would Ship

Long-term memory for Next.js AI agents, built both ways: mem0's managed extraction vs pgvector inside your own Postgres. The code, the costs, my pick.

Muhammad Zain Ul Abidin13 min read
On this page
Every agent demo has the same amnesia: a user explains their stack, their preferences and their constraints on Monday, and on Tuesday the agent cheerfully asks again. Long-term memory is the fix, and in a Next.js app it comes down to two honest architectures: rent the whole pipeline from mem0, or build the storage half on pgvector inside the Postgres you already run. I built both behind one API route in a scratch Next.js app, ran the pgvector side live against a real Postgres with pgvector 0.8.7, and read mem0's current pricing page with my own eyes - which is where this comparison gets interesting, because most of the internet is quoting mem0 prices it no longer sells. Here is the code, the mechanics and the pick I would ship.

The disclosure, up front, in the open: the pgvector build in this post ran for real - a local Postgres 16.2 with pgvector 0.8.7, seeded, searched, user-isolation-tested and index-tested at 20,000 rows, using small hand-built test vectors so the geometry is checkable by eye. The mem0 build is written against the real mem0ai SDK (3.3.1) and typechecked against its types, but I have no mem0 API key in this environment, so it did not run end to end. Where a claim rests on mem0's docs rather than my run, I say so. Nothing here invents a latency number.

The short version

  • Memory is two jobs, not one: extracting facts from conversations, and storing plus retrieving them. mem0 sells you both. pgvector is only the second job - extraction becomes a prompt you own.
  • If you already run Postgres, pgvector is my default. One extension, one table, one index; no new vendor, no quotas, and your users' facts live in your database under your backup and deletion policies.
  • mem0 wins on time and on not owning extraction. One add() call replaces a pipeline. Its free tier is genuinely usable (50,000 adds a month once your own LLM key is connected), but graph memory is now a paid platform feature, and the $19/$249 tiers in most comparison posts are legacy pricing the buy page no longer offers.
  • The deciding question is not features. It is whether you want memory as infrastructure you operate or as a service you rent - and how much the data's home matters to you.

What "memory" means here, and what it does not

Three different things get called memory, and comparisons fall apart when they blur:

  • Session memory is the current conversation - the messages in the context window. Every chat framework has it; it dies with the session.
  • Document RAG retrieves from content you published: docs, tickets, a knowledge base. The write path is an indexing job, done once.
  • Long-term agent memory - this post's subject - is facts about a specific user, learned from that user's own conversations, written continuously and retrieved per user. The write path is the hard part: raw transcripts are noisy, contradictory and far too long to stuff into a context window. Somebody has to decide that "I deploy to production on Fridays" is a fact worth keeping, phrase it cleanly, notice when it changes, and forget the small talk around it.

That deciding is called extraction, and it is the real product in this comparison. Storage and similarity search are solved problems. Keep that framing and every other difference between mem0 and pgvector falls out of it.

The same conversation, two pipelines

Before the code, walk the claim. This is the exact three-message conversation from my scratch app, stepped through both stacks - extraction, storage, retrieval a week later, and the final tally of who did the work. The pgvector similarity scores shown in step 4 are from the live run described below.

mem0: you rent the extraction

The mem0 pitch in one line: send conversations, get facts. The platform's pipeline runs an LLM over the transcript, extracts the salient facts, resolves them against what it already knows about that user, stores them, and later ranks them for a query using semantic, keyword and entity signals. The entire integration surface in TypeScript is two calls:

ts
import MemoryClient from "mem0ai";

let client: MemoryClient | null = null;

function mem0(): MemoryClient {
  if (!client) {
    const apiKey = process.env.MEM0_API_KEY;
    if (!apiKey) throw new Error("MEM0_API_KEY is not set");
    client = new MemoryClient({ apiKey });
  }
  return client;
}

// Write path: hand over the transcript. Extraction is Mem0's job.
export async function addConversation(userId: string, messages: Message[]) {
  return mem0().add(messages, { userId });
}

// Read path: ranked memories for this user only.
export async function searchMemories(userId: string, query: string) {
  const { results } = await mem0().search(query, {
    filters: { user_id: userId },
    topK: 5,
  });
  return results.map((r) => ({ memory: r.memory ?? "", score: r.score }));
}

That is genuinely all of it - this is the write and read path of the mem0 side of my scratch app, and it typechecks against the SDK's shipped types. What you are renting is everything behind add(): the extraction prompt engineering, the update-versus-append decisions when a new fact contradicts an old one, the embedding pipeline, and the retrieval ranking. If you have ever maintained an extraction prompt against real user conversations, you know that is not nothing - real transcripts contain corrections, sarcasm and three facts tangled in one sentence.

Two structural facts about mem0 matter more than the API. First, the open-source build is real: an Apache-2.0 library and a self-hosted server where you bring your own LLM, embedder and vector store - and one of the twenty-plus supported vector stores is pgvector itself, which tells you where the boundary of the product actually sits. Second, the boundary moved recently: graph memory (entity linking, relationship-aware retrieval) was removed from the open-source SDK in v2.0.0 and is now a platform-only, Custom-plan feature. If a comparison post tells you mem0's open-source version includes graph memory, it is describing 2025.

What mem0 actually costs in November 2026

This is the part of the comparison the internet currently gets wrong, so I read the official pricing page directly instead of another blog's summary. The buy page today offers exactly two options:

mem0 Freemem0 Custom
Price$0Talk to sales
Memory adds10,000/month for 30 days, then 50,000/month with your own LLM keyCustom
Retrievals1,000/month for 30 days, then 10,000/month with your own LLM keyCustom
Extraction LLMMem0's models for 30 days, then your key, your bill-
Graph memoryNoYes
Consolidation (Dream)NoYes
SupportCommunityPrivate Slack, SLA

The $19 Starter and $249 Pro tiers that dominate comparison content are legacy: mem0's own FAQ still references them in places, and third-party directories still list them, but a price-history tracker records the ladder being restructured in late October 2026, with Starter and Pro closed to new accounts and existing customers grandfathered. If a buying decision matters here, price it from the pricing page, not from a roundup - including, if it ages, this one.

The honest cost model for the free tier: your extraction and embedding run on your own LLM key, so your marginal cost is model spend, which scales with conversation volume. The platform counts adds and retrievals. That is a fair trade for most early products, and it is also a meter you do not control the shape of - a chatty user costs retrievals every single turn you choose to search memory.

pgvector: you own the pipeline

The pgvector build is the same two functions, with the middle exposed. One table holds the facts:

sql
CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE IF NOT EXISTS agent_memories (
  id BIGSERIAL PRIMARY KEY,
  user_id TEXT NOT NULL,
  memory TEXT NOT NULL,
  embedding vector(1536) NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX IF NOT EXISTS agent_memories_embedding_hnsw
  ON agent_memories USING hnsw (embedding vector_cosine_ops);

And the write and read paths are the ones from my scratch app:

ts
import { Pool } from "pg";

const pool = new Pool({ connectionString: process.env.DATABASE_URL });

// Write path: you extracted the fact and embedded it. This just stores it.
export async function addMemory(userId: string, memory: string, embedding: number[]) {
  await pool.query(
    "INSERT INTO agent_memories (user_id, memory, embedding) VALUES ($1, $2, $3::vector)",
    [userId, memory, vectorLiteral(embedding)],
  );
}

// Read path: nearest facts for this user, by cosine distance.
export async function searchMemories(userId: string, queryEmbedding: number[], topK = 5) {
  const { rows } = await pool.query(
    `SELECT memory, 1 - (embedding <=> $1::vector) AS similarity
     FROM agent_memories
     WHERE user_id = $2
     ORDER BY embedding <=> $1::vector
     LIMIT $3`,
    [vectorLiteral(queryEmbedding), userId, topK],
  );
  return rows;
}

Everything mem0 hides lives just offstage from this listing: the extraction prompt that turned the transcript into those three clean facts runs before addMemory, on your LLM bill, with quality that is entirely your responsibility; and the embedding call happens once per fact on write and once per question on read. Neither is hard. Both are yours forever.

What pgvector itself gives you is mature and boring in the best way. The current release is 0.8.7 (October 2026). Search is exact by default - the extension's own README leads with "great at exact searches, with perfect recall" - and HNSW or IVFFlat indexes trade a little recall for speed when tables grow. The operator set covers cosine (<=>), inner product, L2, L1, Hamming and Jaccard distances, and storage types run from full float vectors down to binary vectors for very large collections. It is preinstalled or one extension away on the hosted Postgres providers a Next.js app typically already uses, Supabase and Neon included.

What my live run actually proved

Claims about my own build, so here is the evidence standard: I compiled PostgreSQL 16.2 from source in this sandbox (no system package was available), built pgvector 0.8.7 against it, and ran a test script that does five things:

  1. Creates the table with a vector(8) column and the HNSW index.
  2. Inserts four memories with hand-set 8-dimension embeddings - three for one user, one (a Tuesday deploy day) for a second user, so leakage would be visible.
  3. Searches with a "when do we deploy" query vector: the Friday memory ranks first at 0.9932 cosine similarity, ahead of the Postgres fact (0.2914) and the green-tea fact (0.0714). The geometry was set by hand, so this proves the ranking and scoping machinery, not embedding quality - real embeddings are the model's job.
  4. Asserts the second user's Tuesday memory never appears in the first user's results. It did not.
  5. Loads 20,000 filler memories and checks the HNSW index's top 5 against an exact scan's top 5: identical, with the standard caveat that HNSW recall is approximate in general and depends on build and search parameters (hnsw.ef_search was raised to 200 for the check).

What the run does not prove: anything about latency at production scale, or about how good real extraction is on either side. Those are your workload's questions. What it settles is that the pgvector layer is small, inspectable and behaves exactly as its SQL reads - there is no magic in it to debug later.

The honest comparison

Dimensionmem0 (platform)pgvector (your Postgres)
Time to first memoryUnder an hour - two API callsA day - schema, extraction prompt, embedding wiring
Extraction qualityTheir pipeline, tuned across many customersYour prompt, your eval problem
Contradictions and updatesHandled in their pipelineYour design decision (append, overwrite, supersede)
Where facts liveMem0's platform (or your server, OSS build)Your database, beside your users table
Cost shapeFree tier with your LLM key; usage past limits; Custom for graph/consolidationYour LLM + embedding calls; database you already pay for
QuotasAdds and retrievals counted monthlyNone - your infrastructure limits
Graph memoryPlatform, Custom planBuild it yourself (entity tables are a project)
Deleting one userAPI operationOne DELETE with a user_id predicate
Lock-inFacts and extraction logic live with the vendor; export discipline is on youNone beyond Postgres
Self-host escape hatchOSS library + server, BYO models and vector storeNot applicable - it is already yours

What each side costs at real volumes

No invented benchmarks - the cost structures, from the verified mechanics:

On mem0 free, a month with 40,000 conversations to extract and 8,000 memory searches fits inside the post-intro limits (50,000 adds, 10,000 retrievals) at $0 platform cost - but every one of those extractions ran an LLM call on your key, and every search is a retrieval whether or not it finds anything. Your real bill is your model's extraction spend, and it scales with conversation volume, not with users.

On pgvector, the same month costs the same extraction calls (your prompt now, possibly a smaller model since you control it), plus one embedding call per extracted fact and one per search, plus rows in a database already in your stack. Embedding calls are the cheapest calls in the AI stack by an order of magnitude; the storage is noise at this scale. There is no quota event in your future - the meter that runs out is your own database's capacity, on your own monitoring.

The crossover is not really about money at small scale. It is about which failure mode you prefer: a vendor quota and a pricing page that can be restructured (as mem0's just was) versus a pipeline whose extraction quality is your problem on your worst week.

Tools mentioned in this post

ToolWhat it is forLink
mem0Managed memory platform, plus the open-source library and serverhttps://mem0.ai
pgvectorThe Postgres extension for vector storage and similarity searchhttps://github.com/pgvector/pgvector
SupabaseHosted Postgres with pgvector preinstalled, common in Next.js stackshttps://supabase.com
NeonServerless Postgres with pgvector supporthttps://neon.tech

Before you ship either one

The checklist I would pin above the memory layer, whichever side you picked - every item is a production incident class, not a nicety:

My verdict: pgvector by default, mem0 when the calendar is the constraint

For a Next.js app that already runs Postgres - which is most of the apps I build and review - I would ship pgvector. The reasoning is not that mem0 is weak; its extraction pipeline is the better-engineered half of this comparison. It is that memory is core user data with a long memory of its own: it should live in the database you back up, under the deletion policies you already honor, with no quota and no pricing page between you and your users' facts. The extraction prompt is a day of work and a permanent small responsibility, and in exchange the whole layer is inspectable SQL that will still be running, unchanged, in five years.

The answer flips in three named conditions. If you do not run Postgres and adding it just for memory makes no sense, mem0 is the rational buy. If memory is blocking a launch this week, ship mem0 now and keep your raw conversation logs in your own database, so a later move to pgvector is a data migration instead of an excavation. And if your product genuinely needs relationship-aware memory - entities, how facts about them change over time - that is graph memory, a Custom-plan feature on mem0 and a real project on pgvector; buy it.

The answer flips back, too: if you start on mem0 and the retrieval meter, the data-residency conversation, or a pricing restructure starts to itch, the exit is well-defined precisely because the concepts are the same on both sides - facts, scoped to users, embedded and ranked. That portability is the quiet argument for learning the pgvector shape even if you rent first.

If you are adding memory to an agent product and want the architecture reviewed before the data piles up, that is the kind of system I build for clients - tell me about it here.

Frequently asked questions

What is long-term memory for an AI agent?

Long-term memory is the set of facts an agent keeps about a user across sessions - preferences, decisions, context like deploy days or database versions - extracted from past conversations and retrieved by relevance when a new conversation needs them. It is different from session memory (the current conversation's context window) and from RAG over documents: the source is the user's own history, and the write path matters as much as the read path.

What is mem0 and how does it store agent memory?

mem0 is a memory layer for AI agents. You send it conversation messages with a user id; its pipeline extracts the individual facts with an LLM, deduplicates and stores them per user, agent, app or run, and its search returns the relevant facts ranked for a new query. It is sold as a managed platform (free tier plus custom plans) and as an open-source library and self-hosted server where you bring your own LLM, embedder and vector store.

Is mem0 free?

There is a real free tier. For the first 30 days it includes 10,000 memory adds and 1,000 retrievals a month with extraction running on Mem0's models; after that, connecting your own LLM key raises the free limits to 50,000 adds and 10,000 retrievals a month, with usage-based billing past the limits under a cap you set. Graph memory, consolidation and analytics are Custom-plan features. Older $19 Starter and $249 Pro tiers still appear in third-party comparisons but are no longer sold to new accounts.

What is pgvector used for in agent memory?

pgvector is a Postgres extension that stores embedding vectors in ordinary tables and searches them by distance (cosine, inner product, L2 and more), with exact search by default and HNSW or IVFFlat indexes for approximate search at scale. In an agent memory layer it is the storage and retrieval half: your code extracts facts from conversations, embeds them, inserts them as rows scoped to a user, and retrieves the nearest rows to a question's embedding. It gives you storage and ranking - extraction, deduplication and updates remain your code's job.

mem0 vs pgvector: which is better for a Next.js app?

If you already run Postgres, pgvector is the better default for most Next.js apps: no new vendor, no per-call quotas, your users' facts stay in your own database, and the whole layer is one table, one index and one extraction prompt you control. mem0 is better when you do not run Postgres, need memory working this week, or do not want to own extraction quality and deduplication - and its graph memory is a platform feature you would otherwise have to build. The post works through both builds so you can judge the actual code difference.

Can I use pgvector with Supabase or Neon for agent memory?

Yes. pgvector ships preinstalled or one CREATE EXTENSION away on most hosted Postgres providers, including Supabase and Neon, and the code is identical: the same table, the same cosine operator, the same HNSW index. If your Next.js app already uses one of them as its database, your memory store is already provisioned - you only add the extension, the table and the extraction step.

Comments

No comments yet. Be the first.

Leave a comment

Comments are reviewed before they appear.