Skip to main content

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.
MemberConnectionCan doCannot do
AnalystRead-only role on publicIntrospect, SELECT, answerAny write, anywhere
EngineerRead on public, read-write on an agent-owned schemaBuild views in its own schemaTouch public
LeaderNo direct database accessRoute the request, compose the answerRun 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

LayerEnforced by
Read-only answersDatabase role with no write grant
Write isolationSchema-scoped grant on a separate connection
Irreversible actionsHuman approval via requires_confirmation
AuditabilityThe Decision Log records what changed and why

Next steps

TaskGuide
Let the Engineer build reusable viewsMaterialization
Approve sensitive actionsHuman approval

Developer Resources