Vanna is archived. What to use instead

Vanna's repository was archived on 29 March 2026 (https://github.com/vanna-ai/vanna) and is read-only. It had 23.8k stars and an MIT licence when it stopped.

This page is written by the author of one of the tools mentioned on it, so here is the conflict of interest up front: schemagate is not a Vanna replacement, and if you are looking for one, two of the three projects below are a better place to start than this one. Vanna was end to end — train, ask, generate SQL, run it, draw a chart. schemagate is one step of that pipeline. What follows is the honest version of where each thing fits.

First: do you actually need to migrate?

Archived does not mean broken. The licence is MIT and the code is still there, so the options that cost nothing are real ones:

Migrate when you need something Vanna did not do, not because the badge on the repo changed.

The actual replacements

Stars and status as of September 2026, from each project's own repository:

what it is stars licence
WrenAI Closest to end-to-end Vanna: a GenBI platform with a semantic layer over your warehouse. The heaviest of the three, and the one with a modelling layer. 17.7k see repo
DB-GPT Agentic data assistant — connects to databases and files, writes and runs SQL, builds reports. Broader than text-to-SQL. 20.0k MIT
DataLine Self-hosted chat-with-your-data with charts. Much smaller and much simpler; the quickest to stand up. 1.6k GPL-3.0

If you want one recommendation: WrenAI if you have a warehouse and want a semantic layer, DB-GPT if you want an agent, DataLine if you want the short path to something running.

Where schemagate fits, and where it does not

schemagate does not generate SQL, does not run it, does not chat, and is not an agent. It decides which tables go in the prompt, per caller. You add it to whichever of the above you pick — or to a forked Vanna, or to a hand-rolled loop — and keep everything else.

The reason to bother is one thing none of them do, including Vanna.

What Vanna did with identity, and what it didn't

Vanna 2.0 resolves a User with group_memberships, gates tools on those groups, and applies row-level security when the SQL runs:

Execute SQL tool (user-aware) → Apply row-level security → Filtered results

That protects the data. It does not protect the answer. The model still sees the whole schema, still writes valid SQL against a table this user can't read, RLS strips every row, and the user is told "no records found" — a wrong answer delivered with confidence. Nothing in that chain can tell the difference between "there is no data" and "you are not allowed to see it."

schemagate works one step earlier. It decides which tables the model is shown, per caller, before any SQL exists. A restricted table is not de-ranked; it is absent from the prompt.

If you are keeping Vanna's shape: the mapping

Vanna schemagate
vn.train(ddl=...) (1.x) / tools reading a configured DB (2.x) cat = Catalog().bootstrap("postgresql://…") — reflects the schema once
vn.train(documentation=...) cat.hint("orders", "…") — a human note that outranks everything
User(id=…, group_memberships=[…]) Principal("okta:jdoe", roles={…})
a tool's access_groups cat.restrict("hr_compensation", ["payroll"]) — on the table, not the tool
vn.ask(question) / chat_sse sel = cat.select(question, principal=p); put sel.prompt_fragment() in your SQL prompt
vn.generate_sql(...) not schemagate's job — keep whatever model call you have

schemagate is not an agent, a chat server, or a SQL generator. It is the schema selection step. Keep your Vanna agent, LangChain chain, or hand-rolled loop; replace the part that decides what DDL goes in the prompt.

Minimal example

from schemagate import Catalog, Principal

cat = Catalog().bootstrap("postgresql://localhost/app")
cat.restrict("hr_compensation", ["payroll"])          # once, at startup

def build_prompt(question, user):                      # per request
    p = Principal(f"okta:{user.id}", roles=set(user.group_memberships))
    sel = cat.select(question, top_k=6, principal=p)
    return f"Schema:\n{sel.prompt_fragment()}\n\nQuestion: {question}"

Your User object drops straight in: id becomes the principal's subject, group_memberships become its roles. Whatever generated SQL before still does — it just never sees hr_compensation unless the caller holds payroll.

If you're using an agent framework

Cursor, Windsurf, or any MCP client: run python -m schemagate.mcp_server and the agent calls select_schema(question, principal, roles) as a tool. LangChain: SchemagateRetriever is a BaseRetriever. Both are in the README.

What you lose, honestly

Vanna trained on question/SQL pairs and learned from feedback. schemagate does not learn; it reflects and ranks. If your accuracy came from a large question–SQL memory, keep that memory and use schemagate only for the identity gate. If it came from schema documentation, cat.hint() and cat.describe() do the same job with less machinery.

GitHubTry the demo