Project: Data Analyst Agent

Capstone two: an agent that ingests CSVs, answers in DuckDB SQL, draws charts, and CANNOT write to your database — GA-19's theory as a working build.

▶ Watch this reel

What you'll learn

  1. Ingestion pipeline
  2. The SQL tool
  3. Charts & summaries
  4. Guardrails, evals & limits

Remember this

Ingest

SQL tool

Charts & narrative

Guardrails & evals

efficiency gates (cents/question), stated limits in product copy.

Build order: ingest → fenced tool → routing golden set → charts → honesty evals.

Code: run-sql: the whole tool

@mcp.tool()
def run_sql(query: str, session: str) -> SqlResult:
    """Execute read-only SQL over this session's tables (orders, …).
    Use for aggregation, joins, trends. Do NOT use for file ops —
    ATTACH/COPY/read_csv are rejected. Prefer explicit column lists.
    Results capped at 500 rows."""
    tree = sqlglot.parse_one(query)
    if not isinstance(tree, sqlglot.exp.Select) or \
       any(banned in tree.sql() for banned in
           ("ATTACH", "COPY", "read_csv", "write_")):
        raise ToolError("only plain SELECTs over session tables allowed")
    con = sessions.connect(session)          # structural isolation
    rows = con.execute(f"SELECT * FROM ({tree.sql()}) _q LIMIT 500")
    return SqlResult(columns=rows.columns, rows=rows.fetchall(),
                     row_count=rows.rowcount, query=tree.sql())