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.
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.
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.
| total_revenue |
|---|
| 3006110.0 |
| brand | revenue |
|---|---|
| Van Heusen | 859065.0 |
| Adidas | 638620.0 |
| Puma | 619353.0 |
| Nike | 524439.0 |
| Levi | 364633.0 |
| avg_rating | total |
|---|---|
| 3.46 | 102 |
| priority | count |
|---|---|
| high | 1 |
| medium | 4 |
| low | 3 |
| ∅ (none set) | 4 |
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.
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.
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.
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.