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:

MemoryExampleAccess pattern
Episodic (history)Messages and tool calls in a threadAppend-only, read by thread in order
Structured stateTasks, plans, tickets, user settings, tool resultsRead and update by key, transactions, joins
Semantic (recall)"User prefers metric units", summaries of old threads, knowledge chunksNearest-neighbor search plus filters
Work queueJobs the agent or its sub-agents must runClaim 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

  1. Mixed data models. Rows, JSON, vectors and full text, ideally queryable together ("memories of this user, about billing, similar to this question").
  2. Transactions. Agents run tools concurrently and retry. Updating a task and recording the tool result must succeed or fail together.
  3. 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.
  4. Scoped, revocable credentials. Each agent gets only what its job needs. Losing or leaking one key must not expose everything.
  5. Audit and undo. You need to see what an agent ran and reverse it without restoring the whole database.

#How the common options compare

OptionEpisodicStructured stateSemanticIsolationScoped credentials, audit, undo
Postgres with pgvectorYesYes (SQL, transactions)Yes (HNSW index)Row Level SecurityRoles and grants built in; audit and undo need tooling
Dedicated vector databasePossible, awkwardLimitedYes, the core strengthNamespaces or tenant filtersDepends on the product
Key-value storeYes (lists)Simple keys onlyVaries by productBy key prefixUsually coarse
Document databaseYesYesSome offer vector searchPer collection or filterVaries
SQLite file per agentYesYesThrough extensionsOne file per agentFile-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.

SQL
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:

SQL
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:

JSON
{
  "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)

PlanAgent keysQuery log keptUndo windowRows recorded per session
Free17 days24 hours10,000
Pro ($15.99/mo)2590 days7 days100,000
Scale ($49/mo)100180 days14 days1,000,000
Enterprise ($329/mo)100180 days30 days10,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.