If you write Python in Antigravity, you have probably felt a quiet pull: the analysis part wants to live in Jupyter, but the productivity gains from the agent pull everything else toward Antigravity's editor, and before you know it the notebook sits unused. I spent a stretch caught in exactly that pendulum while working on a personal analytics project. Lean too hard on Antigravity and I lost the tactile feel of exploratory work; lean too hard on Jupyter and nothing ever crystallised into reusable modules.
This post shares three workflows I now use to stop that pendulum. It is not a "how to open a notebook in Antigravity" guide. It focuses on how to split the work between the agent and hands-on exploration, with rules and code you can adopt today.
Workflow 1 — Grow a Notebook Into a .py Module
When I start an analysis, I give myself a rule: the first 30 to 60 minutes are Jupyter-only. No agent calls, no generation. I write cells by hand, run df.head() several times, and let the shape of the data sink in. Skipping this step and letting the agent take over too early tends to produce code that works on the surface but misses the quirks of the actual data.
Once the exploration settles, I bring the agent in for modularisation. A concrete example:
# From notebooks/eda_sales_2026q1.ipynb
# Cell 3: monthly summary logic
import pandas as pd
def summarize_monthly(df: pd.DataFrame, value_col: str = "amount") -> pd.DataFrame:
"""Return monthly totals, counts, and average ticket size.
The intent is to move this to src/analytics/sales.py after extracting it
in Antigravity.
"""
monthly = (
df.assign(month=df["date"].dt.to_period("M"))
.groupby("month")
.agg(total=(value_col, "sum"), count=(value_col, "count"))
)
monthly["avg_ticket"] = monthly["total"] / monthly["count"]
return monthly.reset_index()
# Expected output: a DataFrame with month / total / count / avg_ticket
summarize_monthly(df).head()With that cell selected, I ask the agent something like:
"Move this cell into
src/analytics/sales.py, add type hints, and write a single pytest case for it. In the notebook, replace the cell withfrom analytics.sales import summarize_monthly."
The trick is to keep the cell selected when making the request. Antigravity prioritises the active selection as context, so you rarely need to spell out which cell you mean. I regularly turn a notebook with ten or so exploratory cells into a clean .py module in half a day using this pattern.
One habit that matters: do not leave the empty shell of a migrated cell behind. I add a magic comment like # moved-to: analytics.sales.summarize_monthly to migrated cells and make it a morning routine to delete them. Otherwise exploratory notebooks balloon forever.
Workflow 2 — Have the Agent Rewrite Cells One at a Time
The second workflow flips the direction. Instead of moving notebook code out, I ask the agent to write or rewrite a single cell while I stay in the notebook. Letting the agent generate an entire notebook tends to erase the traces of exploration, but cell-level collaboration preserves them.
The pattern I use: put the intent at the top of the cell as a comment, then use that comment as the prompt.
# TODO(agent): Use statsmodels.tsa.seasonal.STL to decompose the past 12 months of
# sales into seasonal / trend / residual components. Return a DataFrame
# with columns season / trend / resid. Assume period=12.
# Also print the standard deviation of the residual.
import pandas as pd
from statsmodels.tsa.seasonal import STL
def decompose(series: pd.Series) -> pd.DataFrame:
stl = STL(series, period=12, robust=True).fit()
result = pd.DataFrame({
"season": stl.seasonal,
"trend": stl.trend,
"resid": stl.resid,
})
print(f"resid std: {result['resid'].std():.3f}")
return result
# Expected output: one line with "resid std: ..." plus a 3-column DataFrame
decomposed = decompose(df.set_index("date")["amount"])
decomposed.tail()The nice side effect of this style is that when you revisit the notebook later, it is immediately obvious which cells were agent-written. A quick grep "# TODO(agent):" surfaces every generated block, which helps a lot during code review and verification.
One warning: Antigravity's agent often overlooks Jupyter magic commands such as %timeit or %load_ext. If you ask it to rewrite a performance-measurement cell, be explicit: "keep the %%time line." I lost a few measurement lines early on and spent longer than I would like figuring out why numbers looked off.
Workflow 3 — Feed Execution Results Into the Agent's Context
The third workflow is where the biggest productivity gains live. Instead of asking the agent to reason about data in the abstract, you show it the result of a quick cell and let it make decisions from there.
Correlation matrices are a great example:
# Compute correlation, then feed the text to the agent
corr = df[["age", "income", "score", "ltv"]].corr().round(2)
print(corr)
# Example output:
# age income score ltv
# age 1.00 0.12 0.08 0.31
# income 0.12 1.00 0.45 0.67
# score 0.08 0.45 1.00 0.58
# ltv 0.31 0.67 0.58 1.00Copy that printed matrix into the agent chat and say: "Write a linear regression model with ltv as the target. Here is the correlation matrix." The agent will typically suggest income and score as features because it can read the numbers directly. The important bit is to pass the matrix as text rather than as a screenshot.
I deliberately avoid sending plot images to the agent when the decision is a statistical one. Antigravity's multimodal capabilities have improved, but numeric tables are still more reliable than charts for quantitative reasoning. Getting into the habit of sharing describe(), value_counts(), and corr() output as text noticeably accelerates model selection.
Writing an AGENTS.md That Fits Data Analysis
To keep these three workflows consistent across sessions, I keep a data-analysis-oriented AGENTS.md at the project root. Antigravity reads this on startup, and the following kinds of rules have repeatedly saved me from small disasters.
# Data Analysis Project — Agent Guidelines
## Notebook Policy
- Notebooks under `notebooks/` are for exploration only. Final logic must live in `src/analytics/`.
- When editing existing notebook cells, only touch cells that start with `# TODO(agent):`.
- Never remove magic commands (`%%time`, `%matplotlib inline`, etc.) without being asked.
## Data Handling
- Never write into `data/raw/`. Processed outputs belong under `data/interim/`.
- Hash columns containing PII (`email`, `phone`, `address`) before any aggregation.
- Every function that returns a DataFrame must have a docstring describing the expected columns.
## Testing
- Any function placed in `src/analytics/` requires a pytest test.
- Minimal fixture CSVs go under `tests/fixtures/`.
- Use `pandas.testing.assert_frame_equal` so both types and values are verified.This file also works for other agents, so if your team mixes Antigravity with Claude Code or similar tools, the same rules apply everywhere. For a deeper look at writing this kind of guideline, I wrote A Practical Guide to AGENTS.md.
A Trade-off Worth Acknowledging
When you run all three workflows, the hardest question is always the same: when do you leave code in the notebook, and when do you promote it to a module? There is no universal answer.
A rule of thumb that has served me well: the moment you notice you have copy-pasted the same cell three times, it is time to modularise. Data analysis sits between "throwaway" and "reusable" by nature, and you need a feel for when to cross the line. The agent, usefully, makes a decent second opinion. Asking "should this cell be a function, or is it fine to leave as is?" often produces a sensible take.
For editor-side customisations that complement these workflows, I cover more ground in Advanced Customisation Guide for the Antigravity Editor. Pairing that with the notebook practices here gives you a fairly complete Python analysis setup.
One Thing To Try Tomorrow
Open one of your current analysis notebooks and look for a single function you have copy-pasted three or more times. When you find one, select that cell and ask the Antigravity agent to "move this into a new file under src/analytics/ and write one minimal test." That is Workflow 1 in miniature, and it is enough to start feeling where the agent ends and where your own exploration belongs.
Migrate one small function at a time, and the role split between Jupyter and the agent starts to feel natural. The pendulum quiets down once you have walked the loop a few times yourself.