Best Database for AI Agents: Memory, Safety and Undo
The best database for AI agents is one that can store all three kinds of agent memory (conversation history, structured state and semantic recall) and that can also contain the agent: per-agent credentials, limits on what it may change, a log of every statement and a way to undo a bad session. A relational database with vector support, such as Postgres with pgvector, covers all of that in one system, which is why it is the default choice for most agent builders. A vector-only store is a good addition when recall over a huge corpus is the main job, but on its own it handles only one of the three memory types and none of the safety needs.
#What an agent actually stores
Before comparing databases, list the data. A typical agent, whether it is a support bot, a coding assistant or a workflow runner, keeps:
| Memory | Example | Access pattern |
|---|---|---|
| Episodic (history) | Messages and tool calls in a thread | Append-only, read by thread in order |
| Structured state | Tasks, plans, tickets, user settings, tool results | Read and update by key, transactions, joins |
| Semantic (recall) | "User prefers metric units", summaries of old threads, knowledge chunks | Nearest-neighbor search plus filters |
| Work queue | Jobs the agent or its sub-agents must run | Claim one job at a time, retry on failure |
Plus the data the agent works on: your app's own tables. That last category is where most real damage happens, so a good agent database is not only a memory store but also a safe place for the agent to act.
#What the best database for AI agents needs
- Mixed data models. Rows, JSON, vectors and full text, ideally queryable together ("memories of this user, about billing, similar to this question").
- Transactions. Agents run tools concurrently and retry. Updating a task and recording the tool result must succeed or fail together.
- Isolation per user or tenant. One user's memories must never appear in another user's context. That rule belongs in the database, not in prompt instructions.
- Scoped, revocable credentials. Each agent gets only what its job needs. Losing or leaking one key must not expose everything.
- Audit and undo. You need to see what an agent ran and reverse it without restoring the whole database.
#How the common options compare
| Option | Episodic | Structured state | Semantic | Isolation | Scoped credentials, audit, undo |
|---|---|---|---|---|---|
| Postgres with pgvector | Yes | Yes (SQL, transactions) | Yes (HNSW index) | Row Level Security | Roles and grants built in; audit and undo need tooling |
| Dedicated vector database | Possible, awkward | Limited | Yes, the core strength | Namespaces or tenant filters | Depends on the product |
| Key-value store | Yes (lists) | Simple keys only | Varies by product | By key prefix | Usually coarse |
| Document database | Yes | Yes | Some offer vector search | Per collection or filter | Varies |
| SQLite file per agent | Yes | Yes | Through extensions | One file per agent | File-level only |
A few honest notes on this table:
- Dedicated vector databases (Pinecone, Qdrant, Weaviate, Milvus and others) are excellent at semantic recall at large scale. They are not designed for transactional state, so agents that use them almost always pair them with a second database. See best vector database for RAG for that comparison.
- Key-value stores are fast for short-term memory such as the last N messages of a session. They are a fine cache in front of a durable database.
- SQLite per agent is simple and gives strong isolation for local or single-user agents. It gets harder when many agents share data or you need central oversight.
For a multi-user product where agents read and write real data, Postgres is the strongest single choice, as long as you add the safety layer described below.
#A memory schema in Postgres
Here is a compact schema for the three memory types, with Row Level Security so each user only ever sees their own data. It assumes a Supabase-style setup where auth.uid() returns the signed-in user, as on StitchDB.
create extension if not exists vector with schema extensions;
-- Episodic: threads and messages
create table public.agent_threads (
id uuid primary key default gen_random_uuid(),
user_id uuid not null default auth.uid() references auth.users on delete cascade,
title text,
created_at timestamptz not null default now()
);
create table public.agent_messages (
id bigint generated always as identity primary key,
thread_id uuid not null references public.agent_threads on delete cascade,
role text not null check (role in ('user', 'assistant', 'tool')),
content jsonb not null,
created_at timestamptz not null default now()
);
create index on public.agent_messages (thread_id, id);
-- Semantic: long-term memories with embeddings
create table public.agent_memories (
id bigint generated always as identity primary key,
user_id uuid not null default auth.uid() references auth.users on delete cascade,
kind text not null check (kind in ('fact', 'preference', 'summary')),
content text not null,
embedding extensions.vector(1536) not null,
importance smallint not null default 1,
created_at timestamptz not null default now()
);
create index on public.agent_memories using hnsw (embedding extensions.vector_cosine_ops);
alter table public.agent_threads enable row level security;
alter table public.agent_messages enable row level security;
alter table public.agent_memories enable row level security;
create policy "own threads" on public.agent_threads for all to authenticated
using (user_id = auth.uid()) with check (user_id = auth.uid());
create policy "own messages" on public.agent_messages for all to authenticated
using (exists (select 1 from public.agent_threads t
where t.id = thread_id and t.user_id = auth.uid()))
with check (exists (select 1 from public.agent_threads t
where t.id = thread_id and t.user_id = auth.uid()));
create policy "own memories" on public.agent_memories for all to authenticated
using (user_id = auth.uid()) with check (user_id = auth.uid());Recall then blends similarity with importance and recency, in one query:
create or replace function public.recall(query_embedding extensions.vector(1536), k int default 8)
returns setof public.agent_memories
language sql stable
set search_path = public, extensions
as $$
select m.*
from (
select * from agent_memories
order by embedding <=> query_embedding
limit 40
) m
order by (1 - (m.embedding <=> query_embedding))
+ 0.05 * m.importance
- 0.01 * extract(day from now() - m.created_at)
desc
limit k
$$;The inner query uses the HNSW index for 40 candidates; the outer one re-ranks them. The weights are a starting point, not a rule. Because the function runs as the calling user, the own memories policy applies and recall cannot leak across users. Deleting a user removes their threads, messages and memories through the foreign keys.
For the work queue, Postgres has two good options: a jobs table claimed with select ... for update skip locked, or the pgmq extension, which StitchDB offers as Queues with send, read with a visibility timeout and archive.
#The safety layer
Memory is the easy part. The hard part is that agents also act on your production data, and a model that is confidently wrong can run delete from orders with no where. The controls that matter, and how StitchDB implements them:
A credential per agent. StitchDB agent keys each create their own Postgres role with grants only on the tables you allow. Presets are Read-only, App developer (writes rows, no schema changes) and Migrations (schema changes with approval for risky ones). Denied tables stay denied even if a pattern allows them. Row Level Security still applies unless you explicitly turn on the bypass.
A firewall that understands SQL. Every statement sent through the API or MCP is parsed with the real Postgres parser. Transaction control, SET ROLE, DO blocks, COPY, dynamic SQL and denied tables are refused with a reason the agent can read and correct (firewall docs).
A person for risky statements. Updates and deletes without a real where (including where true), changes to more rows than the key's limit, drops, RLS and privilege changes return 202 pending_approval until an owner or admin approves them in Studio (approvals).
A log and an undo. Every statement is logged with its verdict, rows changed and duration. Every row an agent changes is recorded with old and new values, per session, so you can undo one session after a dry run, while writes made by other people are left alone.
Connecting an agent is a config block:
{
"mcpServers": {
"stitchdb": {
"url": "https://stitchdb.com/platform/mcp",
"headers": { "Authorization": "Bearer sdb_agt_..." }
}
}
}The agent then gets tools such as execute_sql (through the firewall), list_tables, describe_table, get_approval and undo_session (MCP docs). For a deeper walk-through of these layers, see AI agent database safety.
#Plan limits for agents (as of November 2026)
| Plan | Agent keys | Query log kept | Undo window | Rows recorded per session |
|---|---|---|---|---|
| Free | 1 | 7 days | 24 hours | 10,000 |
| Pro ($15.99/mo) | 25 | 90 days | 7 days | 100,000 |
| Scale ($49/mo) | 100 | 180 days | 14 days | 1,000,000 |
| Enterprise ($329/mo) | 100 | 180 days | 30 days | 10,000,000 |
Current values are on pricing.
#What undo cannot do
Be clear with yourself about the edges. Undo reverses database changes. It does not unsend an e-mail, reverse an HTTP call made from SQL, or bring back a queue message another worker already consumed. Design agent tools so that external side effects happen in a separate, approved step, after the database change is final.
Also, direct Postgres connections for an agent key skip the firewall, approvals and the query log (changes are still recorded for undo). Prefer the API or MCP path for anything that writes.
#Trade-offs to weigh
- One database means shared resources. Heavy vector recall and app traffic compete for the same server. Watch query performance, and on StitchDB, Scale and Enterprise projects can move to a dedicated server.
- Very large semantic memory (hundreds of millions of vectors) is better served by a dedicated vector engine next to Postgres.
- Region. StitchDB runs only in EU data centers (Germany and Finland), which suits EU data residency but adds latency for agents running far away.
#FAQ
#What is the best database for AI agent memory?
For most products, Postgres with pgvector: it stores history, structured state and embeddings together, with transactions and per-user Row Level Security. Add a dedicated vector store only if recall volume outgrows one server.
#Should an AI agent have direct database access?
Only through a credential scoped to its job, with a firewall, approvals for risky statements and a way to undo its changes. Never give an agent the database owner password or a service key.
#How do I stop an agent from deleting data?
Use a read-only or row-writing key without schema rights, deny sensitive tables, set a low max-rows-per-statement, and require approval for unbounded writes. If something still goes wrong, undo the session.
#Can agents share memory?
Yes. Add an organization or team column and a policy that allows members to read shared memories, while personal memories stay owner-only.
Give your agent a safe home: create a free project and set up its first agent key.