manikanta.
← All workNext project →
Agentic AI · 2026AI / ML

SQL Analyst Agent

A natural language interface to a PostgreSQL database that checks its own work. The database is an objective oracle, so the agent can tell whether its query was wrong and why, which is what makes the repair loop something other than a retry with a prayer.

Role
Solo — agent, MCP server, evaluation
Timeline
2026
Status
Built and tested, not yet deployed

No hosted demo yet. It runs locally, as a CLI, and as an MCP server over stdio for clients like Claude Desktop.

118
Passing tests
0
Unsafe queries executed
12
SQLSTATE repair strategies
3
Attempt cap before it stops
The problem

Text-to-SQL demos are common and mostly the same: a question goes in, a query comes out, and nobody checks it. They fail silently, they have nothing to measure, and there is no reason for the model to get a second try.

The database changes that. Syntax errors, missing columns, type mismatches and timeouts are all objectively detectable, so the agent can read the actual failure and rewrite. That is what justifies a loop. It also needs a stopping rule, because an agent that loops forever on an impossible question is worse than one that fails fast.

Architecture
01select_schema3 to 5 tables of 15, chosen semantically02generate_sqlre-entry point for every repair03validate_staticsqlglot AST: SELECT only, LIMIT forced04executeread-only role, 5s timeout, always rolls back05inspect_resultok, error, or emptyrepair, by error typeattempts < 3 ?42501 is never repairedgive_up, honestlyreturns what it triedanswer, with the SQL it usedempty results get one diagnostic, not a blind repairSAFETY, IN THE TOOL LAYER1. sql_agent_ro can only SELECT2. read-only txn, 5s timeout, ROLLBACK3. parser rejects writes and multi-statementHolds for the MCP client too, not justthe agent, because it lives in the tools.0 unsafe queries reached the databaseacross the 40-question evaluation.
STAGE 01

Select the schema

Hand-written table descriptions are embedded with bge-small-en-v1.5 and searched semantically, so 3 to 5 relevant tables from a 15-table schema reach the prompt instead of all of them. The descriptions say what each table is not for, which stops irrelevant joins.

STAGE 02

Validate before executing

sqlglot parses the query to an AST and rejects multiple statements, any write anywhere in the tree, unknown tables and deny-listed functions, then forces a LIMIT. Only then does it run, on a SELECT-only role inside a read-only transaction with a 5 second timeout that always rolls back.

STAGE 03

Route the error, or stop

Each SQLSTATE maps to its own repair: 42703 widens schema retrieval to 9 tables, 42702 forces column aliasing, 57014 simplifies the query, and 42501 is never repaired. State carries the full history so the model cannot regenerate a query that already failed.

Evaluation
MeasureResultMethod
Execution accuracy, 30 answerable73.3% → 76.7%Result sets, not SQL text
Hard and ambiguous subset5/10 → 6/10Same 40-question set
Rescued by the repair loop1 of 1Two strategies in sequence
Unsafe SQL reaching the database0Parser plus read-only role
Stack
  • Python 3.12
  • LangGraph 0.2
  • MCP
  • FastAPI
  • PostgreSQL 17
  • sqlglot
  • Groq
  • bge-small-en-v1.5
  • Langfuse v4
  • React 19
  • Vite
  • pytest
Deployment

Not hosted yet. It runs locally behind FastAPI, as a CLI, and as an MCP server over stdio, so the same four tools work from Claude Desktop or any MCP client. Safety lives in the tool layer rather than the agent, which means it holds no matter what calls it. Three separate database credentials enforce least privilege: the agent's role can only SELECT, a second role can only INSERT into the query log and cannot read it back, and admin rights exist only in setup scripts. Every question is traced in Langfuse with cost computed from real token counts, and a scheduled check diffs the live schema against a snapshot to catch drift.

What I'd do next

Want the walkthrough? I'll show you the repo.