v3 · SQL + MongoDB · multi-source

Ask your
data in
plain English.

You ask a question in natural language. The agent figures out the query, a firewall checks it's safe, a read-only connection runs it — and you get a plain-English answer back. The exact query and rows are there too, but only if you want to look.

See real examples ↓ GitHub
2
DB engines
SQL+NoSQL
together
2
Firewall layers
111
Tests passing
0
Write access
The whole idea

Natural language in → natural language out

You never touch a query. You ask like you'd ask a colleague; the machinery in the middle stays out of your way — but it's all there, on the record, if you want to audit it.

You
💬
Ask in English
"Which brand makes us the most money?" No schema knowledge, no query syntax.
System
⚙️
Writes the query
The agent reads the live schema and writes SQL — or a Mongo pipeline — to fetch exactly what's needed.
System
🛡️
Checks & runs it
Firewall validates the query, a read-only connection executes it, results come back as rows or numbers.
You
Get a real answer
A plain-English sentence with the actual figure in it — plus the query and raw data, one click away.
Real examples

You ask. You get a sentence back.

Each card leads with the question and the natural-language answer — exactly what you'd see. The query the system wrote and the raw rows it got are tucked under "show how it got there", because most of the time you don't need them.

SQL · simple lookupBasic
YouWhat's our total revenue from completed orders?
Your completed orders have brought in ₹30,06,110 in total revenue.
show how it got there
SQL the query the agent wrote
SELECT ROUND(SUM(oi.quantity * oi.unit_price), 2) AS total_revenue FROM order_items oi JOIN orders o ON oi.order_id = o.order_id WHERE o.status = 'completed' LIMIT 200
total_revenue
3006110.0
✓ firewall pass⛨ read-only 1 row1 turn
SQL · 3-table join, rankedHarder
YouWhich brand makes us the most money? Rank them.
Van Heusen is your top earner at ₹8.59L from completed orders, followed by Adidas (₹6.39L) and Puma (₹6.19L). Nike (₹5.24L) and Levi (₹3.65L) round out the five.
show how it got there
SQL the query the agent wrote
SELECT t.brand, ROUND(SUM(oi.quantity * oi.unit_price), 2) AS revenue FROM order_items oi JOIN orders o ON oi.order_id = o.order_id JOIN t_shirts t ON oi.t_shirt_id = t.t_shirt_id WHERE o.status = 'completed' GROUP BY t.brand ORDER BY revenue DESC LIMIT 200
brandrevenue
Van Heusen859065.0
Adidas638620.0
Puma619353.0
Nike524439.0
Levi364633.0
✓ firewall pass⛨ read-only 5 rows1 turn
MongoDB · aggregationNoSQL
YouHow are customers rating our products overall?
Across 102 reviews, products average a 3.46 out of 5 rating — decent, but with clear room to improve. The spread is wide, so it's worth looking at which products are dragging the average down.
show how it got there
MONGO the pipeline the agent wrote
db.product_reviews.aggregate([ { "$group": { "_id": null, "avg_rating": { "$avg": "$rating" }, "total": { "$sum": 1 } } }, { "$limit": 200 } ])
avg_ratingtotal
3.46102
✓ firewall pass⛨ read-only 1 doc1 turn
MongoDB · schemaless dataNoSQL
YouHow many support tickets are still open, and are any high priority?
You have 12 open tickets out of 40 total. Just one is flagged high priority — though note that not every ticket carries a priority field at all, so a few open ones are simply unclassified.
show how it got there
MONGO the pipeline the agent wrote
db.support_tickets.aggregate([ { "$match": { "status": "open" } }, { "$group": { "_id": "$priority", "count": { "$sum": 1 } } }, { "$limit": 200 } ])
prioritycount
high1
medium4
low3
∅ (none set)4
✓ firewall pass⛨ read-only 4 docs1 turn
Multi-source

One question. Two databases. One answer.

Your sales sit in SQL. Your reviews live in MongoDB. You shouldn't have to know that. Ask one question — a planner decides which sources it needs, queries them in parallel, and a synthesizer writes you a single answer that draws on both.

SQL + MongoDB · synthesizedMulti-source
YouOur top-earning brand — are customers actually happy with it?
Not as much as you'd hope. Van Heusen is your highest-revenue brand (₹8.59L), but its 3.4-star average rating is only middle-of-the-pack. The real outlier is Levi — lowest revenue and the worst reviews at 2.33 stars. Nike and Puma quietly have your happiest customers (3.65) despite earning less.
show how it got there — two sources, in parallel
shop_sql · revenue by brand
SQL
SELECT t.brand, SUM(oi.quantity*oi.unit_price) AS revenue FROM order_items oi JOIN orders o ON oi.order_id = o.order_id JOIN t_shirts t ON oi.t_shirt_id = t.t_shirt_id WHERE o.status = 'completed' GROUP BY t.brand
Van Heusen ₹8.59L · Adidas ₹6.39L · Puma ₹6.19L · Nike ₹5.24L · Levi ₹3.65L
shop_reviews · ratings by brand
MONGO
db.product_reviews.aggregate([ { "$group": { "_id": "$t_shirt_id", "avg": { "$avg": "$rating" }, "n": { "$sum": 1 } } } ]) // joined to brand via t_shirt_id // by the synthesizer
Nike 3.65 · Puma 3.64 · Adidas 3.55 · Van Heusen 3.40 · Levi 2.33
✓ both sources firewalled⛨ read-only queried in parallel2 sources1 synthesis
How the hub stays honest
Each source is queried by its own independent agent, in its own thread, behind its own firewall — they don't even know they're part of a group. If one source is down, you still get an answer from the rest, with a clear note about what was missing. And the synthesizer only runs when more than one source actually returned data, so a single-source question costs nothing extra. Live progress streams in as it happens, every event tagged with which database it came from.
Security

The model proposes. The firewall disposes.

The language model is treated as an untrusted query author — for both SQL and Mongo. Every query it writes is checked in code before it touches a database, and the connection itself physically can't write. Here's what gets stopped, on each engine.

SQL firewall · sqlglot AST
🗑
DELETE / UPDATE
DELETE FROM orders
⊘ Only SELECT queries are allowed.
💣
DROP TABLE
DROP TABLE t_shirts
⊘ Only SELECT queries are allowed.
Multi-statement
SELECT 1; DROP TABLE orders
⊘ Exactly one statement allowed, got 2.
👁
Schema exfil
SELECT * FROM sqlite_master
⊘ Table not in allowed schema.
Mongo firewall · pipeline allowlist
📤
$out / $merge
[{ "$out": "stolen_copy" }]
⊘ Write stages are never allowed.
$where (arbitrary JS)
{ "$where": "function(){…}" }
⊘ Code-execution operators blocked.
🧬
$function, nested
$project → { "$function": … }
⊘ Caught at any nesting depth.
👁
Unlisted collection
find on users_pii
⊘ Collection not in allowed schema.
Layer 2 — the connection itself can't write. SQL sessions are opened read-only at the driver level (PRAGMA query_only, default_transaction_read_only). For Mongo, the connection object is capability-narrowed — insert / update / delete / drop methods aren't reachable on it at all, not merely permission-checked — plus a startup probe that warns if your credentials carry write rights. 111 CI tests verify both layers hold on every push, on both engines.
The bundled demo

Two databases, zero setup

A seeded SQLite store and an in-memory MongoDB collection of reviews & tickets — both ship with the repo, so you can try every example above (single-source or multi-source) without connecting anything. Swap in your own with one line of config.

shop_sql · SQLite · 5 tables
t_shirts · 89customers · 200 orders · 1,500order_items · 2,214 discounts · 29
Indian e-commerce t-shirt store · structured, relational, joinable
shop_reviews · MongoDB · 2 collections
product_reviews · 102 support_tickets · 40
Semi-structured, schema-varying — the honest reason you'd reach for Mongo. References the same product & customer IDs as the SQL store, so the two genuinely join.