nlqdb

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. 1 · What the agent remembered — agent_memory

    idcategorycontentcreated_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. 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. 3 · The answer — one GROUP BY, run in Postgres

    categoryfacts
    user_preference38
    task_outcome24
    entity_fact19
    tool_result11
    correction5

    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.

  1. LLM → typed JSON plan The model picks structure — tables, filters, aggregates — and returns it as a typed plan, not a SQL string.
  2. compiler → parameterised SQL nlqdb’s code emits the SQL. Every value is a bound parameter; identifiers come only from the schema allow-list.
  3. 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).
  4. 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.

No sign-in. The anonymous database lasts 72 hours; adopt it with one click if you keep it. Or start with your own goal.

See the side-by-side comparisons or the manifesto.