Start deploying
·5 min read·a2a cloud

Add memory and RAG to an AI agent with Postgres and pgvector

Agent memory is three different things — thread state, long-term facts, and retrieval. You can run all three in one Postgres database. Here's how, with LangGraph checkpoints and pgvector, on a database your agent gets at deploy time.

agent memoryragpgvectorpostgreslanggraphtutorial

"Add memory to my agent" usually hides three separate problems:

  1. Thread state — the agent should pick up a conversation where it left off, even after a restart or a redeploy.
  2. Long-term facts — things the agent learned about a user or task that should outlive a single thread.
  3. Retrieval (RAG) — pull the right chunk of your documents or past records into context when a question comes in.

Most stacks solve these with three different systems: a checkpoint store, a key-value or document store, and a vector database. Each one is another credential, another bill, another thing to back up, and another place data can leak from.

You don't need three. Plain Postgres with the pgvector extension handles all of them — and on a2a cloud, every agent already gets its own managed Postgres at deploy time. This post walks through the pattern.

Step 0: the database

Declare a database in a2a.yaml and it is provisioned for the agent and injected as DATABASE_URL:

YAML
resources:
  databases:
    - name: app
      engine: postgres
      access_mode: read_write
      env:
        url: DATABASE_URL

The database belongs to that agent, is scoped to that agent's role, and is created and torn down with it. (Full background: Give your AI agent a Postgres database.)

1. Thread state: LangGraph checkpoints in Postgres

LangGraph's checkpointer interface is exactly the right abstraction for thread state. Point PostgresSaver at the agent's database and every step of every thread is persisted:

Python
import os
from langgraph.checkpoint.postgres import PostgresSaver

with PostgresSaver.from_conn_string(os.environ["DATABASE_URL"]) as checkpointer:
    checkpointer.setup()  # creates checkpoint tables once
    graph = builder.compile(checkpointer=checkpointer)

    graph.invoke(
        {"messages": [("user", "Where did we leave the migration plan?")]},
        config={"configurable": {"thread_id": "customer-42"}},
    )

The in-memory MemorySaver from the tutorials is fine on a laptop and wrong in production: the moment your process restarts, every conversation is gone. With Postgres, a redeploy is invisible to the user.

If you're on CrewAI or the OpenAI Agents SDK the idea is identical — persist sessions to DATABASE_URL instead of process memory.

2. Long-term facts: just a table

Long-term memory is usually over-engineered. Start with a table:

SQL
create table if not exists memories (
  id          bigserial primary key,
  subject     text not null,          -- user id, account, project
  fact        text not null,
  source      text,                   -- which run or tool produced it
  created_at  timestamptz default now()
);
create index on memories (subject);

The agent writes facts through a tool ("remember that this customer deploys on Fridays only") and reads them back by subject at the start of a thread. Because it's relational, you can also *delete* a fact — which matters the first time a user asks you to forget something.

3. Retrieval: pgvector in the same database

Now add embeddings. Enable the extension and give chunks a vector column:

SQL
create extension if not exists vector;

create table if not exists chunks (
  id         bigserial primary key,
  doc_id     text not null,
  content    text not null,
  embedding  vector(1536) not null
);
create index on chunks using hnsw (embedding vector_cosine_ops);

Retrieval is a single query:

Python
import os
import psycopg
from pgvector.psycopg import register_vector

def retrieve(query_embedding, k: int = 5):
    with psycopg.connect(os.environ["DATABASE_URL"]) as conn:
        register_vector(conn)
        return conn.execute(
            "select doc_id, content from chunks "
            "order by embedding <=> %s limit %s",
            (query_embedding, k),
        ).fetchall()

Wrap retrieve as a tool, call your embedding model of choice to produce query_embedding, and the agent has RAG.

Why one database beats three systems

Joins instead of glue code. The real questions agents get are hybrid: *"what did we tell this customer about pricing last month?"* That's a vector search filtered by subject and a date range. In one database it's one query with a where clause. Across a vector DB and a relational DB it's two round-trips and a merge in Python.

One blast radius. A prompt-injected agent can only reach the database its role was provisioned for. With three external services, you're managing three sets of credentials inside the agent's environment. (More on the security model: Give AI agents database access without handing over production.)

Forgetting is a `delete`. Deletion requests and retention policies are much easier when memory, facts, and embeddings live side by side and share keys.

Memory you can audit. Memory is where an agent's behavior quietly changes over time — a stored "fact" steers every future answer. On a2a cloud each run emits a signed Ed25519 receipt, so when a memory write turns out to be wrong you can answer *which run wrote it, and under whose authority* instead of grepping logs. See signed receipts vs trace logs.

When to reach for something bigger

Postgres + pgvector comfortably covers the memory needs of most agents. Reach for a dedicated vector or graph system when you have very large corpora with heavy concurrent retrieval, or when you need structure-aware recall across relationships — we wrote up an early investigation into graph-shaped dynamic memory for that end of the spectrum. Start simple; you can always move the chunks table later.

Expose memory to other agents

Once remember and retrieve are tools, they're also exposed over the agent's MCP server — so an IDE client or another agent can query the same memory without a second integration. See wiring an MCP server to your agent.

Try it

Start from a framework quickstart, then add the three tables above:

Shell
pip install a2a-pack
a2a deploy

DATABASE_URL is already there. Your agent's memory can be too.

discussion

0 comments

Likes0Dislikes0
No comments yet.