Upload a table. It cleans the data, decides what to predict, trains and ranks models, measures what actually drove the predictions — and when the data can't support the question, it says so instead of returning a confident number. A LangGraph agent that uses an LLM only where judgement is needed and keeps every metric deterministic.
Hand an LLM a dataset and ask it to "analyze this," and it will happily produce a confident, well-formatted, and wrong report — inflated accuracy from a leaked column, a conclusion the sample size can't justify, a chart that doesn't mean what its caption says. The failure mode isn't a lack of capability; it's a lack of restraint.
Autonomous Data Analyst is built around the opposite instinct: an agent that treats "I can't support that claim" as a first-class output. It runs a fixed, auditable pipeline on any CSV and gates every conclusion behind checks it can't talk its way past.
A LangGraph state machine. The LLM only makes judgement calls — choosing a target, deciding what to do about a weak result, writing the prose. Profiling, training, explanation and the quality gate are pure Python, so a hallucinated plan can never reach a pipeline or invent a number.
Most silent ML failures are leakage — a feature that quietly encodes the target. One detector isn't enough, so the agent runs two independent ones and drops a column before training if either fires:
Mutual information caught Titanic's alive column — it determines survived exactly (MI = 1.000). Left in, it would have produced a perfect score and learned nothing. Linear reconstruction caught the bike-share cnt target, which is just casual + registered (R² = 1.000): any model scores perfectly by doing arithmetic, so the run was abandoned rather than reported.
alive flagged (MI 1.000) and removed before a model was trained.
cnt = casual + registered (R² 1.000). The agent reflects, marks it weak, and abandons.A real but meaningless score is worse than no score. The whole point is that the agent recognises the difference.
When reflection judges a result unsound — a reconstructed target, too little data, an unstable metric — the run ends in abandon and the report says why. "No supportable finding" is a valid, intended output.
Profiling, training, explanation and the quality gate are deterministic Python. The model chooses the target and writes prose, but every metric on the page was measured, not narrated.
The reflect → train loop caps at three attempts, and a retry only counts if it beats the incumbent by 0.02 on the gate metric — with no-op detection so it can't churn on noise. No infinite agent spirals, no runaway token spend.
CSV contents are data, never instructions. Directives smuggled through column names or cell values never reach the planning prompt.
A React + Vite front end over a FastAPI backend, PostgreSQL for durable run history, and LangSmith traces that mirror the graph's real execution including retries. Every project keeps its datasets, runs and reports; you can also just ask the data a question in plain language.
The hard part of an autonomous analyst isn't getting an answer — it's getting it to withhold one. The abstain path is what makes the confident outputs trustworthy.
Mutual information and linear reconstruction each miss cases the other catches. Redundant, independent detectors are cheaper insurance than a single clever one.
Letting the model judge but never measure kept the whole system debuggable — same CSV, same profile, same numbers, every time.