The ad-hoc analysis bottleneck
Every company has data tasks that fall between "simple enough for a spreadsheet" and "complex enough for a data engineering team." Clean this CSV and deduplicate the entries. Join these two datasets and find the mismatches. Pull the numbers from a web dashboard that doesn't have an export button. Generate a monthly report with 6 charts. Run a sentiment analysis on 10,000 customer reviews.
These tasks land on a data analyst or a product manager who opens a Jupyter notebook, spends 2 hours wrangling pandas, and produces a result that's correct but not reproducible. Next month, they do it again from scratch. The notebook is in someone's personal folder. The dependencies aren't recorded. The logic is in a cell that was run out of order.
The Jupyter notebook pattern
- 2–4 hours per ad-hoc analysis
- Notebook lives on one person's laptop
- Dependencies: "I think I pip installed something"
- Cells run out of order, state is unclear
- Charts are screenshots pasted into Google Docs
- Not reproducible — next month, start over
The AI data agent pattern
- 5–15 minutes from data to finished report
- Runs in an isolated sandbox — clean every time
- Agent installs its own dependencies explicitly
- Linear execution, no hidden state
- Charts exported as PNG/SVG, report as Markdown/PDF
- Fully reproducible — same input, same output
The three data sources, three container types
Data comes from three places. Each needs a different container type:
(CSV, JSON, Excel) APIs
(REST, database) Web dashboards
(no export button)
File + API processing Browser container
Dashboard extraction GPU sandbox
ML inference
Pattern 1: File-based processing (terminal sandbox)
The simplest pattern. Upload a file to the sandbox, tell the agent what to do with it, download the result.
# 1. Create sandbox SANDBOX=$(curl -s -X POST "$SANDBOX_URL/api/sandboxes" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"data-pipeline","instance_type":"ab0t.medium","auto_stop_minutes":30}') SID=$(echo "$SANDBOX" | jq -r '.sandbox_id') # 2. Upload the dataset curl -s -X POST "$SANDBOX_URL/api/sandboxes/$SID/upload" \ -H "Authorization: Bearer $API_KEY" \ -F "file=@sales_data_2025.csv" # 3. Tell the agent what to do curl -s -X POST "$SANDBOX_URL/api/sandboxes/$SID/execute" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "command": "cd /workspace && pip install pandas matplotlib seaborn && python -c \"\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\ndf = pd.read_csv(\"sales_data_2025.csv\")\n\n# Clean\ndf = df.dropna(subset=[\"amount\", \"date\"])\ndf[\"date\"] = pd.to_datetime(df[\"date\"])\ndf[\"amount\"] = pd.to_numeric(df[\"amount\"], errors=\"coerce\")\n\n# Analyze\nmonthly = df.groupby(df[\"date\"].dt.to_period(\"M\"))[\"amount\"].sum()\ntop_products = df.groupby(\"product\")[\"amount\"].sum().nlargest(10)\n\n# Visualize\nfig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 6))\nmonthly.plot(kind=\"bar\", ax=ax1, title=\"Monthly Revenue\")\ntop_products.plot(kind=\"barh\", ax=ax2, title=\"Top 10 Products\")\nplt.tight_layout()\nplt.savefig(\"charts.png\", dpi=150)\n\n# Report\nwith open(\"report.md\", \"w\") as f:\n f.write(f\"# Sales Report 2025\\n\\n\")\n f.write(f\"Total revenue: ${df[\"amount\"].sum():,.2f}\\n\\n\")\n f.write(f\"Transactions: {len(df):,}\\n\\n\")\n f.write(f\"## Monthly Breakdown\\n\\n\")\n f.write(monthly.to_markdown())\nprint(\"Done.\")\"" }' # 4. Download results curl -s "$SANDBOX_URL/api/sandboxes/$SID/download" \ -H "Authorization: Bearer $API_KEY" \ -o results.tar.gz
Pattern 2: Let Claude Code do the analysis
For exploratory analysis where you don't know what you're looking for, SSH into the sandbox and hand the task to Claude Code in plain English.
> I uploaded sales_data_2025.csv to /workspace. It has columns: > date, product, region, amount, customer_id, channel. > > Analyze this dataset: > 1. Clean: remove duplicates, handle missing values, fix date formats > 2. Summary stats by region and by channel > 3. Find the top 10 customers by total spend > 4. Monthly revenue trend chart (line) with year-over-year comparison > 5. Revenue by region (choropleth or bar chart) > 6. Cohort analysis: when did each customer make their first purchase? > What % are still active 6 months later? > 7. Save everything to /workspace/output/: > - report.md (narrative with embedded chart references) > - charts/ directory with PNGs > - cleaned_data.csv > - summary_stats.json
Claude Code reads the CSV, inspects the schema, installs dependencies (pandas, matplotlib, seaborn, geopandas if needed), writes and executes the analysis scripts, generates charts, and saves everything to /workspace/output/. You download the tarball when it's done.
When you don't know the schema, don't know the data quality, and don't know what questions to ask, Claude Code excels. It reads the first 100 rows, identifies data quality issues, proposes cleaning steps, and iterates. It's the equivalent of a senior data analyst's first hour with a new dataset, compressed into 3 minutes.
Pattern 3: Browser extraction for dashboard-trapped data
Some data lives in web dashboards with no export button. Google Analytics custom reports, Salesforce dashboards, Shopify analytics, internal admin panels. The data is right there on the screen but there's no API to get it out.
A browser container logs in, navigates to the dashboard, and extracts the data — either via DOM queries or Claude's vision.
async def extract_dashboard_data(cdp_url, dashboard_url, credentials): """Log into a web dashboard and extract the visible data.""" async with async_playwright() as p: browser = await p.chromium.connect_over_cdp(cdp_url) page = await browser.contexts[0].new_page() # Login await page.goto(credentials["login_url"], wait_until="networkidle") await page.fill(credentials["username_field"], credentials["username"]) await page.fill(credentials["password_field"], credentials["password"]) await page.click(credentials["submit_button"]) await page.wait_for_load_state("networkidle") # Navigate to dashboard await page.goto(dashboard_url, wait_until="networkidle") await page.wait_for_timeout(3000) # Try DOM extraction first table_data = await page.evaluate("""() => { const tables = document.querySelectorAll('table'); return Array.from(tables).map(table => { const rows = table.querySelectorAll('tr'); return Array.from(rows).map(row => { const cells = row.querySelectorAll('td, th'); return Array.from(cells).map(c => c.innerText.trim()); }); }); }""") if table_data and any(len(t) > 1 for t in table_data): return {"method": "dom", "tables": table_data} # Fallback: vision extraction screenshot = await page.screenshot(full_page=True) b64 = base64.b64encode(screenshot).decode() response = claude.messages.create( model="claude-sonnet-4-6-20250514", max_tokens=4096, messages=[{ "role": "user", "content": [ {"type": "image", "source": { "type": "base64", "media_type": "image/png", "data": b64}}, {"type": "text", "text": "Extract all data visible in this dashboard screenshot as a JSON array of objects. Include every number, label, and metric you can read."}, ], }], ) return {"method": "vision", "data": json.loads(response.content[0].text)}
Pattern 4: GPU sandboxes for ML inference
For tasks that need ML models — sentiment analysis, classification, embeddings, image recognition — create a GPU sandbox. The ab0t.gpu gives you a T4 GPU with 16 GB of VRAM.
# Create a GPU sandbox for ML inference curl -s -X POST "$SANDBOX_URL/api/sandboxes" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "ml-inference", "instance_type": "ab0t.gpu", "auto_stop_minutes": 30 }' # Cost: $0.526/hour — use for 30 min of inference, then stop
> I uploaded customer_reviews.csv (10,000 rows, column: "review_text"). > > Run sentiment analysis on every review: > 1. Install transformers and torch > 2. Load a sentiment model (distilbert-base-uncased-finetuned-sst-2-english) > 3. Classify each review as positive/negative with a confidence score > 4. Add sentiment and confidence columns to the CSV > 5. Generate a sentiment distribution chart > 6. Find the 20 most negative reviews for manual follow-up > 7. Save: enriched_reviews.csv, sentiment_chart.png, negative_reviews.json
Putting it together: the complete pipeline
A real data processing task often combines all three patterns. Here's a monthly revenue reconciliation that uses browser extraction, file processing, and API calls.
async def monthly_reconciliation(): async with httpx.AsyncClient(timeout=300) as client: # Step 1: Extract Stripe dashboard data (browser container) stripe_data = await extract_from_dashboard( client, "https://dashboard.stripe.com/reports/revenue", {"login_url": "https://dashboard.stripe.com/login", "...": "..."}) # Step 2: Download internal sales data (API call in terminal) internal_data = await execute_on_sandbox(sandbox_id, "curl -s https://api.internal.com/sales?month=2026-03 -H 'Authorization: Bearer $INTERNAL_KEY' > /workspace/internal_sales.json") # Step 3: Upload partner revenue spreadsheet await upload_file(sandbox_id, "partner_revenue_march.xlsx") # Step 4: Reconcile everything (terminal sandbox) await execute_on_sandbox(sandbox_id, """ cd /workspace && pip install pandas openpyxl && python -c " import pandas as pd, json # Load all three sources stripe = pd.DataFrame(json.load(open('stripe_data.json'))) internal = pd.DataFrame(json.load(open('internal_sales.json'))) partner = pd.read_excel('partner_revenue_march.xlsx') # Reconcile: match by transaction ID, flag discrepancies merged = internal.merge(stripe, on='transaction_id', how='outer', suffixes=('_internal', '_stripe')) discrepancies = merged[merged['amount_internal'] != merged['amount_stripe']] # Generate report discrepancies.to_csv('discrepancies.csv', index=False) print(f'Total discrepancies: {len(discrepancies)}') print(f'Total delta: ${(merged[\"amount_internal\"].sum() - merged[\"amount_stripe\"].sum()):,.2f}') " """) # Step 5: Download results await download_workspace(sandbox_id, "reconciliation_march.tar.gz")
Running pipelines on a schedule
Most data pipelines are recurring. The monthly report, the weekly KPI dashboard update, the daily data quality check. Wrap the pipeline in a cron-triggered script:
#!/bin/bash set -euo pipefail # Create sandbox, run pipeline, download, cleanup SID=$(curl -s -X POST "$SANDBOX_URL/api/sandboxes" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"daily-data","instance_type":"ab0t.small","auto_stop_minutes":15}' \ | jq -r '.sandbox_id') sleep 45 # Wait for boot curl -s -X POST "$SANDBOX_URL/api/sandboxes/$SID/execute" \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"command":"cd /workspace && git clone https://github.com/your-org/data-pipelines.git . && pip install -r requirements.txt && python daily_pipeline.py"}' curl -s "$SANDBOX_URL/api/sandboxes/$SID/download" \ -H "Authorization: Bearer $API_KEY" -o /tmp/daily-data.tar.gz # Upload to S3 / email / Slack aws s3 cp /tmp/daily-data.tar.gz s3://data-outputs/$(date +%Y-%m-%d).tar.gz curl -s -X DELETE "$SANDBOX_URL/api/sandboxes/$SID" \ -H "Authorization: Bearer $API_KEY"
What it costs
| Task type | Instance | Duration | Compute cost | Model cost | Total |
|---|---|---|---|---|---|
| CSV cleaning + chart | ab0t.micro | ~5 min | $0.001 | $0.05 | $0.05 |
| Full analysis + report | ab0t.medium | ~15 min | $0.01 | $0.10 | $0.11 |
| Dashboard extraction | ab0t.micro + 1 browser | ~5 min | $0.003 | $0.02 | $0.02 |
| ML inference (10K rows) | ab0t.gpu | ~20 min | $0.18 | $0.00 | $0.18 |
| Monthly reconciliation (3 sources) | ab0t.medium + 2 browsers | ~20 min | $0.02 | $0.15 | $0.17 |
Production tips
Use ab0t.micro for everything that isn't ML
Pandas, matplotlib, and data cleaning run fine on 1 GB of RAM for datasets under 500 MB. Only upgrade to ab0t.medium (4 GB) for large joins or memory-heavy operations. GPU instances are only for model inference.
Upload data via the file API, not SSH
The POST /api/sandboxes/{id}/upload endpoint handles multipart uploads cleanly. For large datasets (>100 MB), upload to S3 first and have the sandbox download from there.
Version your pipeline scripts in git
Store your data processing scripts in a repo. The sandbox clones it on startup. When the business logic changes, you update the repo — the pipeline picks up the change on the next run.
Save intermediate outputs
Write the cleaned data, the analysis results, and the charts as separate files. When something looks wrong in the report, you can trace it back to the intermediate step without rerunning the whole pipeline.
What's next
Data in, report out
Upload a CSV. Describe the analysis. Download the result. Terminal, browser, and GPU — whatever the data needs.
Get Started Free