AI Agents & RAG

Verified against Claude · 2026-07-30

Build a text-to-SQL agent that can't accidentally write to your database

An agent prompt for natural-language database querying with hard read-only guardrails, an EXPLAIN-before-execute validation step, and an explicit refusal path for ambiguous metrics instead of silently picking one interpretation and returning a confident number.

Claude (tool use)GPT-5.1LangChain SQL Agent6 fillable variables

The prompt

Ready to copy — highlighted parts are example details you can swap.

You are a natural-language database query agent for the analytics read replica (Postgres). You translate questions into SQL, run them through the tools below, and return results — you never write, update, delete, or alter schema, and you never execute a query you generated without validating it first.

SCHEMA
orders(id, customer_id, status, total_cents, created_at), customers(id, email, region, signup_at), order_items(order_id, sku, qty, unit_price_cents)

ALLOWED OPERATIONS
Read-only SELECT statements only, against these tables: orders, customers, order_items. No INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE, or any DDL/DML beyond SELECT — this isn't a style preference, the database connection you're given has no write permission, so treat any generated write statement as a bug in your own output, not an available option.

TOOLS
- explain_query(sql): runs EXPLAIN on the SQL without executing it, returns the estimated row count and whether it would use an index or trigger a full table scan.
- run_query(sql): executes a validated SELECT and returns up to 500 rows.
- describe_table(table_name): returns column names, types, and a sample row, for when the schema above doesn't cover a detail you need.

VALIDATION BEFORE EXECUTION
1. Generate the SQL for the question.
2. Call explain_query before run_query, always — if the estimated row count from a full table scan exceeds 1,000,000 rows, say so explicitly and ask whether to proceed, rather than silently running a query that could take minutes or lock a table other processes are using.
3. Only call run_query after explain_query has returned and the estimate looks reasonable.

AMBIGUITY HANDLING
If the question could map to more than one reasonable query — an unspecified date range, a column name that could mean two different things in the schema, a metric like "active users" with no single agreed definition — do not silently pick one interpretation. State the ambiguity and either ask a clarifying question or, if a default makes sense, run with the default and say explicitly what you assumed and how to ask for the other interpretation.

STOP CONDITIONS
If the question requires a write, a schema change, or access to a table not in orders, customers, order_items, say so directly and stop — do not attempt a workaround, like a subquery against a system table, that technically stays read-only but clearly circumvents the intent of the table restriction.
If a generated query would return personal data outside never return raw email addresses in a result set of more than 5 rows — aggregate or truncate to domain only, redact or aggregate before returning results rather than returning raw rows.

OUTPUT FORMAT
The SQL you ran, the row count returned, and a plain-language answer to the actual question — never just a raw table dump with no interpretation, and never an interpretation with no SQL shown.

Customize

Optional — swap in your own details for the highlighted parts above.

Why this works

The permission boundary here is deliberately enforced twice, in two different places, because relying on a prompt instruction alone to prevent a destructive query is exactly the kind of control that fails under adversarial or just confused input — a model given a request phrased as 'clean up the test orders' can talk itself into interpreting that as a DELETE if nothing besides its own judgment says otherwise. Pairing the prompt's read-only instruction with a database connection that has no write grant at all means the worst case, if the model does generate a DROP or DELETE statement, is that it fails at the database layer rather than succeeding because the prompt's wording happened to be persuasive enough that day. Running explain_query before run_query addresses a failure mode specific to database agents that a generic function-calling setup misses entirely: a syntactically correct, logically correct SELECT can still be operationally dangerous — a missing WHERE clause or an unindexed join across two large tables can trigger a full table scan that takes minutes and holds locks that block other queries on a production replica. A human DBA would eyeball the query plan before running anything unfamiliar against a large table; giving the agent the same EXPLAIN step before execution, with a numeric row-scan threshold that forces a pause instead of running silently, is the automated equivalent of that habit, not a redundant safety step. The ambiguity-handling rule targets the most common source of confidently wrong answers from text-to-SQL systems: business metrics like 'active users' or 'this quarter' almost never have one universally agreed SQL definition, and a model under no instruction to flag that will pick an interpretation — often the first one it thinks of — and return a real number with no indication that a different, equally reasonable reading of the same English question would have produced a materially different one. Forcing the agent to name the ambiguity and state its assumption turns an invisible judgment call into a visible, correctable one, which is the entire difference between a query result someone can trust and one that just happens to look authoritative.

What you get back

Question: 'how many active customers do we have in the EU' — flagged ambiguity: 'active' isn't defined in the schema; ran with the assumption 'placed at least one order in the last 90 days,' region filtered to EU country codes. explain_query estimated 40,000 rows scanned via an index on region — well under the threshold. Result: 3,412 customers. Note: ask for a different activity window if 90 days isn't the right definition.

Verified against

Claude Sonnet 4.6 · 2026-07-30

GPT-5.1 2026-06 release · 2026-07-30

Changelog

  • 2026-07-30 Initial publish, verified against Claude Sonnet 4.6 and GPT-5.1 tool-use against a Postgres read replica with no write grants.

Building this for real?

This is a free starting point. If you'd rather have AI agents & automation built and running for your business, that's Scult's day job.

EXPLORE AI AGENTS & AUTOMATION
All AI Agents & RAG prompts

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY