"Hand it a CSV, tell it what you want in plain English, get the aggregation and chart back" — once we started using that kind of agent internally, a monthly report that used to take thirty minutes in Excel started taking three. Antigravity agents plus pandas get you there with surprisingly little glue code. Build it naively, though, and you will hit empty DataFrames that raise exceptions, columns that silently mix numbers and strings, and other real-world papercuts. This article walks through the CSV analysis agent I actually use day-to-day, along with the potholes I discovered in the first few weeks.
Why Antigravity agents fit CSV work
CSV analysis is fundamentally about resolving ambiguity as you go. Ask someone for "the sales trend" and a human analyst will pause to ask: daily or weekly? Broken down by store? What do we do with blank rows? Antigravity closes that loop inside the editor — the agent writes code, runs it, shows you the result, and revises. That is faster than opening Jupyter or hand-writing one-off scripts, and it is far friendlier to non-engineers on your team.
I started out preferring to drive pandas myself. But the moment a non-coder on my team wanted to generate the same reports, the bar became too high. With an agent in between, they get the same output from plain-language prompts without ever touching Python. The interesting side effect is that I started using the agent too, even for tasks I could have coded faster. The cognitive cost of switching contexts — "now I am in analyst mode" — disappears when the conversation stays inside the editor.
The minimal skeleton
Let us set up the foundation. Create agents/csv_analyst/ in your project root and pin the dependencies.
# Save these in requirements.txt
pip install pandas==2.2.2 matplotlib==3.9.0Now define the execution functions — the tools — that the agent will be allowed to call from Antigravity's Agent Manager.
# agents/csv_analyst/tools.py
import pandas as pd
import matplotlib.pyplot as plt
from pathlib import Path
OUTPUT_DIR = Path("output")
OUTPUT_DIR.mkdir(exist_ok=True)
def load_csv(path: str) -> dict:
"""Read a CSV and return basic structure so the agent understands the shape
before writing any aggregation code."""
df = pd.read_csv(path)
return {
"columns": list(df.columns),
"dtypes": {c: str(df[c].dtype) for c in df.columns},
"rows": len(df),
"head": df.head(5).to_dict(orient="records"),
}
def aggregate_and_plot(path: str, group_by: str, value: str, output_name: str) -> str:
"""Group by a column, sum the value column, and save a bar chart as PNG.
Returns the saved path and the number of rows dropped during coercion."""
df = pd.read_csv(path)
# Drop rows that cannot be coerced to numeric — keep the count so the agent
# can surface it to the user rather than silently losing data.
df[value] = pd.to_numeric(df[value], errors="coerce")
dropped = df[value].isna().sum()
df = df.dropna(subset=[value])
agg = df.groupby(group_by)[value].sum().sort_values(ascending=False)
fig, ax = plt.subplots(figsize=(10, 6))
agg.plot(kind="bar", ax=ax)
ax.set_title(f"Sum of {value} by {group_by}")
ax.set_ylabel(value)
plt.tight_layout()
out = OUTPUT_DIR / f"{output_name}.png"
fig.savefig(out, dpi=150)
plt.close(fig)
return f"saved={out} dropped_rows={dropped}"The most important detail here is returning dropped_rows. When the agent sees that thirty rows were coerced away, it can surface that to the user on the next turn: "I dropped 30 rows that couldn't be parsed as numbers — want me to show them first?" Silent data loss is the scariest failure mode in analytics work, so making it visible is worth the extra line of code.
Notice also that load_csv returns a structured preview rather than a raw DataFrame. Agents reason better over JSON-shaped data than over opaque objects, and the structured return means the model can include column names directly in follow-up reasoning without having to introspect Python internals.
The agent instructions
Antigravity lets you drop an AGENTS.md file at the project root to shape the agent's overall behavior. For the CSV analyst, mine looks something like this:
# CSV Analyst Agent
## Your role
Take a CSV the user provides, read it with pandas, and produce the
aggregation or visualization they ask for. Always follow this order:
1. Call load_csv first to inspect columns and dtypes
2. If instructions are ambiguous, ask ONE clarifying question before writing code
3. Call aggregate_and_plot and save the PNG
4. Report the output path and how many rows were dropped
## Do not
- Modify the original CSV file (treat it as read-only)
- Rename columns without asking
- Change how missing values are handled without flagging it to the userThe "ask one clarifying question" rule takes some tuning. Ask too often and the UX tanks; never ask and you get quietly-wrong aggregations. My current heuristic is "only clarify if there are three or more plausible interpretations," and that has been the least frustrating setting in practice. If the ambiguity is binary — daily versus weekly, say — the agent makes a reasonable default choice and notes it in the response, so the user can correct in the next turn rather than stopping the flow.
One subtle point: keep the AGENTS.md short. When I first wrote it I included fifteen rules and the agent started missing half of them. Trimming to five or six sharp rules with the most dangerous failure modes called out explicitly worked far better than a long document.
Common pitfalls I ran into
After a couple of weeks of real use, the same handful of issues kept appearing.
Encoding surprises from Excel exports. Spreadsheets exported from older Excel installations (especially on Japanese or Korean Windows) still ship as Shift_JIS or CP932, and pd.read_csv(path) blows up with a UnicodeDecodeError. I added a fallback inside load_csv that tries UTF-8 first, then asks charset-normalizer to detect, then falls back to cp932 as a last resort. This alone eliminated maybe 40% of the confused support pings I got in the first week. The fix is boring, but the absence of it burns a surprising amount of time.
Date columns come in as strings. If the CSV uses ISO 8601, parse_dates=["date"] is enough. Slash-separated formats like 2026/04/23 can fail silently depending on locale. This is why I force the agent to inspect dtypes before writing any time-based aggregation — it catches the issue before the groupby happens rather than after, which would produce a chart with one giant bar and no useful signal.
Grouping by month ignores the year. If you run df["date"].dt.month without dt.to_period("M"), rows from April 2025 and April 2026 collapse together. The agent will not catch this on its own unless you tell it to. I added a rule in AGENTS.md that says: "When the user asks for monthly aggregation, always ask whether to group by year-month or month-only if the year is not specified." It reads like a small nuance in writing, but in practice this saved me from at least one embarrassing report.
Chart fonts go missing for non-ASCII labels. If your categories contain Japanese, Korean, or other non-Latin characters, Matplotlib will render them as empty squares unless a CJK-capable font is installed. The fix is to install a font like noto-fonts-cjk on the system and set matplotlib.rcParams["font.family"] explicitly before plotting. Do not assume the default font will work. This is one of those issues where the chart looks fine to the agent — it saved a PNG, the tool returned success — but is broken for the human reader.
Security and data boundaries
Most of the CSV files you will throw at this agent contain something sensitive — customer IDs, revenue numbers, internal headcount. A few rules I enforce:
Copy the input CSV into a scoped working directory like /tmp/csv_analyst/ before processing, and delete it when done. The agent should never hold references to the original location. This also gives you a natural audit point: you know exactly which files the agent touched today because they all passed through the same staging directory.
Never include raw cell values in the agent's log output. The load_csv return value contains a head preview, which is exactly the shape of data you do not want surfacing in telemetry. I wrap that return through a redaction function in production that masks any column matching common PII patterns — emails, phone numbers, names written in the likely format for the locale.
If you are sending samples to Gemini for interpretation (say, to generate column descriptions), limit the payload to a small random sample rather than the top of the file. The top of a sorted CSV is often the part that reveals the most sensitive rows — VIP customers, highest-revenue accounts, or the alphabetically first employees.
For the broader picture around API keys, timeouts, and retry policy once this agent runs unattended, I lean on the patterns from the Antigravity Python API production safeguards guide. Those become essential the moment you move from "run it when I ask" to "run it every night at 3 AM."
Next steps
Start with one real CSV you already look at regularly — a monthly sales export, a user signup log, whatever you have handy. Run load_csv followed by one aggregate_and_plot call. Once that round-trip feels smooth, the natural extensions are: a merge_csv tool for joining multiple files, a forecast tool that wraps statsmodels or Prophet for time-series projections, and an anomaly_scan tool that flags outliers before you even ask.
If you also run a scraping agent that pulls data from the web, chain the two together and you get a single chat interface that collects, cleans, and visualizes data end-to-end. That combination is where the Antigravity agent model really pays off — each tool is small and boring on its own, but composed together they replace entire categories of ad-hoc scripts.
Antigravity agents shine the moment you hand them a real tool like pandas. Engineers get a time-saver, non-engineers get a new entry point into data work, and both groups use the same infrastructure. That overlap is what makes this pattern worth the setup effort.