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, recommendations4. 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.06. 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 deployed8. 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
filepart alongside the text prompt. - Streaming —
onTextprints tokens as they arrive;onToolStartlogs tool usage. - Artifact discovery —
session.shell("ls ...")finds whatever files the agent produced, since filenames aren't known ahead of time. - File download —
session.getFileBuffer()pulls binary content (PNGs, Markdown) out of the sandbox.