For AI agents
Memory your agent can query.
Give your AI agent a real database as its memory — created from a goal, not a schema. It writes typed rows as it learns, and later runs actual SQL over them: counts, top-N, averages per group.
A vector store returns the k most similar chunks. It can
recall, but it can’t answer “how many times did this user
ask about pricing this month?” — that’s a
GROUP BY, and a similarity index has no query planner.
nlqdb does, because the memory is a database.
What is analytical agent memory? A real, queryable
database an agent uses to remember — typed rows it can
GROUP BY, JOIN, and aggregate over in plain
English, not an opaque vector blob it can only search. The agent asks
in natural language; nlqdb compiles the question to SQL through a
typed-plan trust boundary, runs it on Postgres, and returns rows plus
the exact SQL it ran. Recall is table stakes — analysis
over memory is the part a vector store structurally can’t do.
Retrieval is not analytics.
A vector store gives your agent
- top-k similar chunks for a query
- semantic recall of past text
- no query planner — rollups become the model doing arithmetic over search hits
nlqdb gives your agent
- the same recall plus real SQL over its memory
GROUP BY/JOIN/HAVING, top-N, per-period — run in Postgres- the SQL it ran, every time — auditable, not a black box
Honest scope: similarity search over an embedding column
(pgvector) is an opt-in slice still landing — today the wedge is
the analytical side. nlqdb is a database, not a vector store
(manifesto).
See it: a GROUP BY over agent memory.
Your agent writes typed rows as it learns. Later it asks a question in English — nlqdb compiles it to SQL through the typed-plan boundary and runs it on Postgres. Here is the whole round-trip.
-
1 · What the agent remembered —
agent_memoryid category content created_at 412 user_preference prefers metric units 2026-06-19 408 task_outcome shipped the invoice export 2026-06-18 401 user_preference timezone is Asia/Jerusalem 2026-06-17 396 entity_fact primary repo is nlqdb/nlqdb 2026-06-15 390 task_outcome fixed the flaky e2e test 2026-06-14 Typed rows, not an opaque vector blob — 412 facts and counting.
-
2 · The question, in English
“Count of facts my agent logged per category this month, highest first”
Compiled SQL (the exact statement nlqdb ran)
SELECT category, COUNT(*) AS facts FROM agent_memory WHERE created_at >= date_trunc('month', now()) GROUP BY category ORDER BY facts DESC; -
3 · The answer — one
GROUP BY, run in Postgrescategory facts user_preference 38 task_outcome 24 entity_fact 19 tool_result 11 correction 5 A vector store would return the rows most similar to your query text. It can’t count them per category — that needs a query planner, which an index doesn’t have.
Rendered from a fixture through the same typed-plan compile path the
product runs — the public demo doesn’t hit an open
/v1/ask (pre-alpha). Numbers are illustrative.
What can your agent actually DO with its memory?
| Capability | Mem0 | Zep | Letta | nlqdb |
|---|---|---|---|---|
| Remember a fact ("Alice has a $50k deal") Storing a fact is table stakes — every memory layer does this. | ||||
| Recall facts by similarity / relevance Retrieval is the job these tools are built for; nlqdb recalls via SQL filters. | ||||
| Top-N by value ("top 5 deals by size") Needs ORDER BY + LIMIT over the full set, not a top-k similarity search. | ||||
| Aggregate per group ("average deal size per stage") A vector/graph store returns matches; the LLM would have to do the arithmetic. nlqdb runs GROUP BY in Postgres. | ||||
| Time-window analytics ("deals closing this month") Zep tracks temporal validity for point-in-time recall, but cannot aggregate across a window. | ||||
| Full GROUP BY / JOIN / HAVING over memory The core wedge: a real query planner over the agent's own data. | ||||
| Agent designs its own schema nlqdb provisions Postgres from the agent's first goal; the others impose a fixed memory shape. | ||||
| Diff preview before destructive writes DDL/DML is previewed and confirmed before it applies (GLOBAL trust-UX). | ||||
| Self-hostable Mem0/Letta/LangMem are OSI-licensed; Zep self-hosts the Graphiti engine but the platform is hosted; nlqdb is source-available under FSL (GLOBAL-019, not yet OSI), with the container pull-forward tracked in WS-11. |
The moat: a typed-plan trust boundary.
Letting an agent write SQL against your database is how a coding agent wiped a production database during a code freeze with three guardrails active. So in nlqdb the LLM never emits SQL.
- LLM → typed JSON plan The model picks structure — tables, filters, aggregates — and returns it as a typed plan, not a SQL string.
- compiler → parameterised SQL nlqdb’s code emits the SQL. Every value is a bound parameter; identifiers come only from the schema allow-list.
- AST re-parse → allowlist
A SQL parser re-parses the compiled statement and checks it
against a verb + table allowlist before it touches your data
(the create path re-parses with the real Postgres parser,
libpg_query). - diff preview → confirm Destructive writes and schema changes show a diff you confirm first — and the exact SQL is returned in the trace, every time.
Same logic Snowflake’s Cortex Analyst uses to hit ~2× single-prompt accuracy: the model picks from a curated structure instead of writing raw SQL.
Your key, your infra, no per-call fees.
- BYO LLM at 0% markup. Paste an Anthropic, OpenAI, Gemini, or OpenRouter key — your agent’s memory routes through it with no markup, on the free tier, no card.
- Source-available, self-hostable. The engine, CLI, MCP server, and SDKs are under FSL-1.1-ALv2 — yours to read, fork, and self-host for any non-competing use. It auto-converts to Apache 2.0 two years after each release.
- No per-call fees, no “contact sales.” Memory you can query shouldn’t meter every recall.
Give your agent memory it can query.
Start from a goal, not a schema. nlqdb provisions the database, your agent writes typed rows as it learns, and you ask in English.
See the side-by-side comparisons or the manifesto.