Documentation Index
Fetch the complete documentation index at: https://docs.agno.com/llms.txt
Use this file to discover all available pages before exploring further.
A prompt that says “only run SELECT” is a suggestion. A connection that physically cannot write is a guarantee. A production data agent answers from a read-only engine and isolates any writes to a schema it owns. The boundary holds even when the model goes off-script.
from agno.agent import Agent
from agno.models.openai import OpenAIResponses
from agno.tools.sql import SQLTools
analyst = Agent(
name="Analyst",
model=OpenAIResponses(id="gpt-5.5"),
tools=[
SQLTools(db_url="postgresql+psycopg://readonly@warehouse/analytics")
],
instructions="Answer questions from the public schema. You cannot write.",
)
A connection bound to a read-only database role means a generated DROP TABLE fails at the database, before it reaches your data. Defense does not depend on the model behaving.
Split the roles
Most data-agent questions are read-only. Writes (building a summary table, recording a correction) are rarer and riskier. Separate them into different agents on different connections.
| Member | Connection | Can do | Cannot do |
|---|
| Analyst | Read-only role on public | Introspect, SELECT, answer | Any write, anywhere |
| Engineer | Read on public, read-write on an agent-owned schema | Build views in its own schema | Touch public |
| Leader | No direct database access | Route the request, compose the answer | Run SQL itself |
The Engineer’s writes are scoped to a schema (for example dash) that the Analyst never touches. Even prompted into DROP TABLE public.users, its engine refuses, because the connection has no write grant on public.
Gate the writes that remain
For writes you do allow, add a human in the loop. requires_confirmation pauses the run until someone approves the action.
from agno.tools import tool
@tool(requires_confirmation=True)
def materialize_view(name: str, sql: str) -> str:
"""Create a view in the agent-owned schema after human approval."""
...
Approval everywhere kills adoption. Gate the irreversible actions, not the reads.
Layers of defense
| Layer | Enforced by |
|---|
| Read-only answers | Database role with no write grant |
| Write isolation | Schema-scoped grant on a separate connection |
| Irreversible actions | Human approval via requires_confirmation |
| Auditability | The Decision Log records what changed and why |
Next steps
| Task | Guide |
|---|
| Let the Engineer build reusable views | Materialization |
| Approve sensitive actions | Human approval |
Developer Resources