Skip to content

Data Analyst

An agent that ingests CSV datasets, produces publication-quality charts using a dark-theme matplotlib style, and writes year-over-year comparison reports. This cookbook walks through the agent config, then shows how to drive it from TypeScript — attaching files, streaming output, and downloading generated artifacts.

This cookbook assumes you've already installed the CLI and set up an API key.

1. Scaffold the Agent

bash
swarmlord init data-analyst \
    -m "google/gemini-3-flash-preview"

2. Configure Tools

Replace swarmlord.jsonc — this agent needs bash (for Python/matplotlib), file I/O, and the skill tool for its chart style guide. No browser or web search needed:

jsonc
{
    "$schema": "https://swarmlord.ai/config.json",
    "name": "data-analyst",
    "description": "Analyze CSV data and produce styled charts and reports",
    "model": "google/gemini-3-flash-preview",
    "tools": {
        "bash": true,
        "read": true,
        "write": true,
        "edit": true,
        "glob": true,
        "skill": true,
        "websearch": false,
        "browser": false,
    },
    "permission": { "*": "allow" },
}

Key choices:

  • bash: true — runs Python scripts for pandas and matplotlib.
  • skill: true — loads the chart-style skill on demand so every chart looks consistent.
  • browser: false / websearch: false — pure data analysis, no web access needed.

3. Write the Instructions

Edit SOUL.md:

markdown
# Data Analyst

You analyze datasets and produce publication-quality charts and written reports.

## Workflow

1. Read the dataset(s) — CSV files in `/workspace/data/` or attached by the user
2. Load the **chart-style** skill before any plotting
3. Explore the data with pandas: shape, dtypes, summary stats, missing values
4. When comparing datasets (e.g. year-over-year), align them on the shared axis
   and plot both series on the same chart with a legend
5. Save all charts as PNG to `/workspace/` (e.g. `/workspace/revenue-comparison.png`)
6. Write a Markdown report to `/workspace/report.md` summarizing findings

## Chart Guidelines

- Always load the chart-style skill before creating any matplotlib figures
- Use descriptive filenames: `revenue-comparison.png`, `user-growth.png`
- Include axis labels, titles, and legends
- Save at 150 dpi

## Report Format

Structure reports with:

- **Summary** — 2-3 sentence overview of the key findings
- **Charts** — reference each chart by filename with a description
- **Details** — deeper analysis, trends, anomalies, recommendations

4. Add the Chart Style Skill

Create skills/chart-style/SKILL.md:

markdown
---
name: chart-style
description: Dark-theme matplotlib style guide with brand color palette
---

# Chart Style Guide

Apply this style to every matplotlib figure for consistent, publication-quality output.

## Setup

```python
import matplotlib.pyplot as plt
import matplotlib

matplotlib.rcParams.update({
    "figure.facecolor": "#0F172A",
    "axes.facecolor": "#0F172A",
    "axes.edgecolor": "#334155",
    "axes.labelcolor": "#E2E8F0",
    "axes.grid": True,
    "grid.color": "#1E293B",
    "grid.alpha": 0.6,
    "text.color": "#E2E8F0",
    "xtick.color": "#94A3B8",
    "ytick.color": "#94A3B8",
    "legend.facecolor": "#1E293B",
    "legend.edgecolor": "#334155",
    "legend.labelcolor": "#E2E8F0",
    "savefig.dpi": 150,
    "savefig.bbox": "tight",
    "savefig.facecolor": "#0F172A",
})
```

## Color Palette

Use these colors in order for data series:

| Variable  | Hex       | Use               |
| --------- | --------- | ----------------- |
| `primary` | `#4F46E5` | First series      |
| `green`   | `#10B981` | Second series     |
| `amber`   | `#F59E0B` | Third series      |
| `red`     | `#EF4444` | Fourth / negative |

## Spine Removal

Remove top and right spines on every axes:

```python
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
```

## Example

```python
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(months, revenue_2025, color="#4F46E5", linewidth=2, label="2025")
ax.plot(months, revenue_2026, color="#10B981", linewidth=2, label="2026")
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax.set_title("Revenue Comparison")
ax.set_ylabel("Revenue ($)")
ax.legend()
fig.savefig("/workspace/revenue-comparison.png")
```

5. Add Historical Data

Create files/data/sales-2025.csv — this lands at /workspace/data/sales-2025.csv in the sandbox, giving the agent baseline data to compare against:

csv
month,revenue,users,churn_rate
Jan,38000,980,4.2
Feb,39500,1020,4.0
Mar,41200,1080,3.8
Apr,43000,1150,3.5
May,44800,1200,3.3
Jun,46500,1290,3.1
Jul,48000,1350,3.0
Aug,50200,1420,2.8
Sep,52800,1500,2.6
Oct,55000,1580,2.4
Nov,58500,1700,2.2
Dec,62000,1850,2.0

6. Test with swarmlord run

bash
cd data-analyst
swarmlord run "Load the 2025 sales data and create a summary report with a revenue trend chart and a churn rate chart."

The agent will read the CSV, load the chart-style skill, generate styled charts, and write a report — all inside the sandbox.

7. Deploy

bash
swarmlord deploy
✓ Bundled data-analyst (1 skill, 1 file)
✓ data-analyst deployed

8. Use from the SDK

This is where the agent shines — you can attach new data at runtime via the SDK, stream the agent's output, and download generated artifacts. Create src/quickstart.ts:

typescript
import { createClient } from "swarmlord";
import { writeFileSync, mkdirSync } from "fs";

const client = createClient();
const agent = client.agent("data-analyst");
const session = await agent.createSession();

const csv2026 = `month,revenue,users,churn_rate
Jan,42000,1200,3.2
Feb,45000,1350,3.0
Mar,48500,1500,2.8
Apr,52000,1680,2.5
May,56000,1850,2.3
Jun,60000,2000,2.1
Jul,65000,2200,1.9
Aug,70000,2450,1.7
Sep,76000,2700,1.5
Oct,83000,3000,1.4
Nov,91000,3400,1.2
Dec,102000,3900,1.1`;

const csvBase64 = Buffer.from(csv2026).toString("base64");

const result = await session.send(
    {
        parts: [
            {
                type: "text",
                text: "I've attached our 2026 sales data. Last year's data is already in your workspace at /workspace/data/sales-2025.csv. Compare the two years — create a year-over-year revenue comparison chart, a user growth chart, and write a report highlighting what changed.",
            },
            {
                type: "file",
                url: `data:text/csv;base64,${csvBase64}`,
                mime: "text/csv",
                filename: "sales-2026.csv",
            },
        ],
    },
    {
        onText: delta => process.stdout.write(delta),
        onToolStart: part => console.log(`\n[${part.tool}]`),
    }
);

mkdirSync("output", { recursive: true });

// The agent saves artifacts to /workspace/ but we don't know exact filenames
// upfront — shell("ls") discovers whatever the agent produced.
const artifacts = await session.shell("ls /workspace/*.png /workspace/*.md 2>/dev/null || true");
const files = artifacts.stdout.trim().split("\n").filter(Boolean);

for (const path of files) {
    const name = path.split("/").pop()!;
    try {
        const buf = await session.getFileBuffer(path);
        writeFileSync(`output/${name}`, Buffer.from(buf));
        console.log(`Downloaded ${name}`);
    } catch {
        // skip if download fails
    }
}

await session.end();

Key patterns demonstrated:

  • File attachment — encode CSV as base64 data URI in a file part alongside the text prompt.
  • StreamingonText prints tokens as they arrive; onToolStart logs tool usage.
  • Artifact discoverysession.shell("ls ...") finds whatever files the agent produced, since filenames aren't known ahead of time.
  • File downloadsession.getFileBuffer() pulls binary content (PNGs, Markdown) out of the sandbox.

Next Steps