v3 · Demo Showcase

Ask your
database
anything.

Plain English in. Guarded SQL out. Real answers — always with the exact query that ran, every guardrail stamp, and every agent step visible.

See 9 live demos ↓ GitHub
5
Tables
4,091
Total rows
2
Firewall layers
36
Tests passing
0
Write access
How it works

Question → guarded SQL → answer

01
You ask
Plain English. No SQL. Multi-turn conversation keeps context across questions.
02
Claude writes SQL
Live schema + 7 few-shot examples guide the agent to the correct join paths and formulas.
03
AST firewall
sqlglot parses every statement. Single SELECT? No forbidden nodes? Tables in schema? LIMIT enforced?
04
Read-only session
Even if something passes the AST, the DB session is physically read-only at the driver level.
05
You see everything
Answer, exact SQL, row count, guardrail stamps, agent turns, elapsed time. Full transparency.
06
SSE streaming
Real-time events per agent step — thinking, SQL attempt, firewall result — no silent waiting.
Demo conversations

9 real questions. Real data. Real SQL.

Every number below came from an actual query against the demo database. Nothing is mocked.

Demo 1 — Simple AggregateBasic
YouWhat is the total revenue from completed orders?
◌  turn 1 — calling model…
✓  executed  SELECT ROUND(SUM(oi.quantity * oi.unit_price), 2) AS total_revenue FROM order_items oi JOIN orders o…
Total revenue from completed orders is ₹30,06,110.00.
✓ firewall pass⛨ read-only 1 row1 turn42ms
SQL executed
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
Demo 2 — Revenue by Brand (Ranked)3-table JOIN
YouWhich brands are generating the most revenue from completed orders? Rank all of them.
◌  turn 1 — calling model…
✓  executed  SELECT t.brand, ROUND(SUM(oi.quantity * oi.unit_price),2) AS revenue FROM order_items oi JOIN orders o JOIN t_shirts t…
Van Heusen leads with ₹8,59,065, followed by Adidas (₹6,38,620) and Puma (₹6,19,353). Nike and Levi round out the ranking.
✓ firewall pass⛨ read-only 5 rows1 turn38ms
SQL executed
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
Demo 3 — Monthly Revenue 2025Date handling
YouShow me monthly revenue and order count for all of 2025.
◌  turn 1 — calling model…
✓  executed  SELECT strftime('%Y-%m', o.order_date) AS month, ROUND(SUM(oi.quantity*oi.unit_price),2) AS revenue…
Revenue grew consistently through 2025, peaking in July at ₹2,84,268 across 101 orders. December was the highest month by year-end at ₹3,31,791.
✓ firewall pass⛨ read-only 12 rows1 turn51ms
SQL executed
SELECT strftime('%Y-%m', o.order_date) AS month, ROUND(SUM(oi.quantity * oi.unit_price), 2) AS revenue, COUNT(DISTINCT o.order_id) AS orders FROM orders o JOIN order_items oi ON oi.order_id = o.order_id WHERE o.status = 'completed' AND o.order_date BETWEEN '2025-01-01' AND '2025-12-31' GROUP BY month ORDER BY month LIMIT 200
monthrevenueorders
2025-0110183.05
2025-0239439.022
2025-0335947.015
2025-0492551.047
2025-05117309.052
2025-06134971.056
2025-07284268.0101
2025-08168032.077
2025-09212876.095
2025-10248800.0106
2025-11266560.0109
2025-12331791.0148
Demo 4 — Discounted Stock ValueLEFT JOIN + COALESCE
YouWhat is the total stock value of each brand after applying their discounts?
◌  turn 1 — calling model…
✓  executed  SELECT t.brand, ROUND(SUM(t.price*(1-COALESCE(d.pct_discount,0)/100)*t.stock_quantity),2)…
Adidas holds the highest discounted stock value at ₹6,94,394. Nike is lowest at ₹4,78,471. Note: only 29 of 89 shirts carry a discount (averaging 14.1%), so most use COALESCE to default to 0%.
✓ firewall pass⛨ read-only 5 rows1 turn29ms
SQL executed
SELECT t.brand, ROUND( SUM(t.price * (1 - COALESCE(d.pct_discount, 0) / 100) * t.stock_quantity), 2 ) AS discounted_stock_value FROM t_shirts t LEFT JOIN discounts d ON t.t_shirt_id = d.t_shirt_id GROUP BY t.brand ORDER BY discounted_stock_value DESC LIMIT 200
branddiscounted_stock_value
Adidas694393.9
Puma626331.4
Van Heusen616120.1
Levi493588.35
Nike478470.55
Demo 5 — Top Customers by Spend4-table JOIN
YouWho are our top 5 customers by total spend on completed orders? Include their city.
◌  turn 1 — calling model…
✓  executed  SELECT c.name, c.city, ROUND(SUM(oi.quantity*oi.unit_price),2) AS total_spend FROM customers c JOIN orders o JOIN order_items…
Anita Nair from Mumbai leads with ₹35,645, followed by Priya Patel (Chennai ₹34,654) and Pooja Patel (Hyderabad ₹32,651).
✓ firewall pass⛨ read-only 5 rows1 turn44ms
SQL executed
SELECT c.name, c.city, ROUND(SUM(oi.quantity * oi.unit_price), 2) AS total_spend FROM customers c JOIN orders o ON o.customer_id = c.customer_id JOIN order_items oi ON oi.order_id = o.order_id WHERE o.status = 'completed' GROUP BY c.customer_id, c.name, c.city ORDER BY total_spend DESC LIMIT 5
namecitytotal_spend
Anita NairMumbai35645.0
Priya PatelChennai34654.0
Pooja PatelHyderabad32651.0
Kavya ReddyBengaluru30862.0
Karan VermaMumbai28857.0
Demo 6 — Quarter ComparisonCASE WHEN + date ranges
YouCompare Q4 2025 vs Q1 2026 — orders and revenue side by side.
◌  turn 1 — calling model…
✓  executed  SELECT CASE WHEN o.order_date BETWEEN '2025-10-01' AND '2025-12-31' THEN 'Q4-2025'…
Q4 2025 had higher revenue (₹8,47,151) while Q1 2026 had more orders (371 vs 363). Average order value was slightly lower in Q1 2026.
✓ firewall pass⛨ read-only 2 rows1 turn33ms
SQL executed
SELECT CASE WHEN o.order_date BETWEEN '2025-10-01' AND '2025-12-31' THEN 'Q4-2025' WHEN o.order_date BETWEEN '2026-01-01' AND '2026-03-31' THEN 'Q1-2026' END AS quarter, COUNT(DISTINCT o.order_id) AS orders, ROUND(SUM(oi.quantity * oi.unit_price), 2) AS revenue FROM orders o JOIN order_items oi ON oi.order_id = o.order_id WHERE o.status = 'completed' AND o.order_date BETWEEN '2025-10-01' AND '2026-03-31' GROUP BY quarter ORDER BY quarter LIMIT 200
quarterordersrevenue
Q1-2026371825663.0
Q4-2025363847151.0
Demo 7 — Agent Self-Corrects After Block2 turns · auto-retry
YouWhat are the top 5 best-selling sizes by units sold across completed orders?
◌  turn 1 — calling model…
⊘  blocked — SELECT INTO not permitted (tried to write temp table)
◌  turn 2 — reads rejection reason, rewrites query…
✓  executed  SELECT t.size, SUM(oi.quantity) AS units_sold FROM order_items oi JOIN t_shirts t JOIN orders o…
XS is the best seller at 1,103 units, followed by S (1,033) and XL (963). M is the slowest at 838 units.
✓ firewall pass ⊘ 1 blocked ⛨ read-only 5 rows2 turns3.2s
⊘ Turn 1 — blocked by AST firewall
SELECT t.size, SUM(oi.quantity) AS units INTO #temp FROM …
Forbidden operation in query: Into. Only pure read-only SELECTs permitted. → Agent retried.
SQL executed (turn 2 — self-corrected)
SELECT t.size, SUM(oi.quantity) AS units_sold FROM order_items oi JOIN t_shirts t ON oi.t_shirt_id = t.t_shirt_id JOIN orders o ON oi.order_id = o.order_id WHERE o.status = 'completed' GROUP BY t.size ORDER BY units_sold DESC LIMIT 5
sizeunits_sold
XS1103
S1033
XL963
L853
M838
Demo 8 — Write Attempt + RecoverySecurity demo
YouDelete all cancelled orders from the database.
◌  turn 1 — calling model (sees system prompt: read-only)…
I can't do that — this connection is strictly read-only. DELETE, INSERT, UPDATE, and all DDL are blocked by the SQL firewall before they reach the database. I can show you the cancelled orders if that helps.
✓ no tool call made ⛨ read-only 1 turn890ms
YouOK, show me the cancelled orders breakdown by month then.
◌  turn 1 — calling model…
✓  executed  SELECT strftime('%Y-%m', order_date) AS month, COUNT(*) AS cancelled FROM orders WHERE status='cancelled'…
There are 78 cancelled orders total. August 2025 had the most (9). Most months averaged 2–3.
✓ firewall pass⛨ read-only 12 rows1 turn37ms
SQL executed
SELECT strftime('%Y-%m', order_date) AS month, COUNT(*) AS cancelled FROM orders WHERE status = 'cancelled' GROUP BY month ORDER BY month LIMIT 200
monthcancelled
2025-011
2025-022
2025-033
2025-042
2025-052
2025-062
2025-072
2025-089
+ 4 more months not shown
Demo 9 — Low Stock AlertOps query
YouWhich t-shirts are critically low on stock — under 10 units? I need to reorder.
◌  turn 1 — calling model…
✓  executed  SELECT brand, color, size, stock_quantity, price FROM t_shirts WHERE stock_quantity < 10 ORDER BY stock_quantity…
5 SKUs are critically low. Levi Blue XL and Van Heusen Blue XS are most urgent at just 5 units each. Nike White XL (₹299) is the cheapest item at risk.
✓ firewall pass⛨ read-only 5 rows1 turn22ms
SQL executed
SELECT brand, color, size, stock_quantity, price FROM t_shirts WHERE stock_quantity < 10 ORDER BY stock_quantity LIMIT 200
brandcolorsizestock_qtyprice
LeviBlueXL5799.0
Van HeusenBlueXS5399.0
PumaBlueXS5599.0
LeviRedXS7599.0
NikeWhiteXL8299.0
Security

The firewall catches everything harmful

Every statement the LLM produces is parsed to an AST by sqlglot before touching the DB. 10 attack vectors tested and blocked in CI.

🗑
DELETE
DELETE FROM orders
⊘ Only SELECT queries are allowed. Got: Delete.
💣
DROP TABLE
DROP TABLE t_shirts
⊘ Only SELECT queries are allowed. Got: Drop.
✏️
UPDATE
UPDATE t_shirts SET price=0
⊘ Only SELECT queries are allowed. Got: Update.
Multi-statement
SELECT 1; DROP TABLE orders
⊘ Exactly one statement allowed, got 2.
🔌
load_extension
SELECT load_extension('evil.so')
⊘ Function 'load_extension' blocked by policy.
👁
Schema exfil
SELECT * FROM sqlite_master
⊘ Table 'sqlite_master' not in allowed schema.
📋
SELECT INTO
SELECT * INTO stolen FROM customers
⊘ Forbidden operation in query: Into.
🔧
PRAGMA
PRAGMA table_info(t_shirts)
⊘ Only SELECT queries are allowed. Got: Pragma.
😴
pg_sleep
SELECT pg_sleep(30)
⊘ Function 'pg_sleep' blocked by policy.
Layer 2: Even if something slips the AST, the DB session is physically read-only at the driver level. INSERT/UPDATE/DELETE fail at the connection. 36/36 CI tests verify both layers hold on every push.
Demo database

What's in the demo DB

Indian e-commerce t-shirt store. 4,091 rows, 5 tables. Swap in your own DB via one line in config.yaml.

t_shirts · 89 rows
t_shirt_id PK brand TEXTcolor TEXT size TEXTprice REAL stock_quantity INT
5 brands · 5 sizes · multiple colors
customers · 200 rows
customer_id PK name TEXTcity TEXT signup_date TEXT
10 Indian cities
orders · 1,500 rows
order_id PK customer_id FK order_date TEXT status TEXT
completed / cancelled / returned
order_items · 2,214 rows
order_item_id PK order_id FK t_shirt_id FK quantity INT unit_price REAL
revenue lives here
discounts · 29 rows
discount_id PK t_shirt_id FK pct_discount REAL
29 of 89 shirts · 5–25% off