Your laptop is not a sandbox. Stop pretending it is.
You’ve seen this movie before. A stakeholder drops a 2GB parquet in Slack at 4:55pm. You pip install three things, paste in code an LLM wrote, and run it against your actual machine — the one with your SSH keys and your ~/.aws. It works. You get the number.
Then weeks later you feel a small cold dread about what that notebook actually touched.
That’s the trap. “Just run it locally” assumes your laptop is a safe place to execute untrusted code. It isn’t. And in 2026, when half the code you run was generated by a model thirty seconds ago, that assumption went from sloppy to reckless.
So I built smolduck: a data analyst in a box. One command boots a disposable microVM with DuckDB and a browser workbench inside it. You analyze, you close it, the box evaporates. Your host was never touched.
Point it at a folder
smolduck run ./sales
Boots a smolvm microVM in a couple hundred milliseconds, mounts the folder, and opens a workbench. Every CSV, Parquet, and JSON file is already a queryable DuckDB view — no import wizard. The folder is the database.
Write SQL, hit ⌘/Ctrl+Enter, get a typed, sortable grid with the query time in the corner:
SELECT region, count(*) AS n FROM customers GROUP BY 1 ORDER BY 2 DESC;
Click two columns in the chart builder for a bar chart — and because a chart you can’t reproduce is a chart you can’t trust, copy as code drops the equivalent Python in a new cell:
df = sql("SELECT region, count(*) n FROM customers GROUP BY 1 ORDER BY 2 DESC")
px.bar(df, x="region", y="n")
The clicks and the code are the same thing. You can always cross the bridge.
Where does the generated code run?
That’s the uncomfortable question every AI data tool has to answer. In smolduck the answer is: in a subprocess, inside the VM, that I can kill.
df = sql("SELECT amount, tenure_days FROM orders")
from sklearn.ensemble import RandomForestRegressor
RandomForestRegressor().fit(df[["tenure_days"]], df["amount"])
px.scatter(df, x="tenure_days", y="amount", trendline="ols")
The kernel shares DuckDB but lives in its own process with a wall-clock timeout — infinite loop, killed, restarted clean. And it only ever turns on inside the VM; on your host it’s off by default and stays off. The sandbox isn’t a feature I bolted on. It’s the precondition for everything else.
The ML runner is the same idea one rung up: pick a target and features, get baseline models scored against a dumb baseline with feature importance, every run logged to experiments.jsonl. The boring infrastructural work is the work.
Wire up a model and an “Ask” bar appears: ask “which region grew fastest last quarter?” and it explores the schema, writes SQL, checks it against your data, and proposes a cell — it does not run it at you. I’m allergic to AI that auto-executes; a model that’s confidently wrong with a run() button is just a faster way to be wrong.
Bring your own model: an Anthropic key, or a local Ollama daemon so the analyst is as private as the rest of the box — your schema and questions never leave your machine, they just hop to a model on your own loopback. No model configured, no bar. It’s the same posture as everything else here: the VM is offline by default and opens only the narrowest hole the chosen provider needs — your own localhost for Ollama, a single allowed host for Anthropic — so turning on the analyst doesn’t quietly turn your sandbox into an open socket.
Let an agent drive it
Here’s the payoff of putting the kernel in a box: once the whole analysis surface is sandboxed, handing it to an external agent is safe by construction. smolduck speaks MCP, so Claude, Cursor, or your own agent can list sources, run SQL, execute Python, fit a model, and export a report — every bit of it landing in the same killable, host-isolated VM a human gets. The agent brings the brain; smolduck keeps the blast radius. “Let the AI run code on my data” stops being the scary sentence.
Throw it away, get it back
Everything that isn’t your data is a plain file in .smolduck/ — manifest, notebooks, charts, experiments, the DuckDB store. smolduck stop and the VM is gone with zero processes left on your host; the next smolduck run rebuilds your notebooks and charts exactly. Export a notebook to one self-contained HTML file and it opens offline — no “you had to be there.”
Where I reach for it:
- The 5pm investigation — messy folder in, HTML report out, nothing installed.
- Running code you don’t trust — agent output, anything off the internet, with your dotfiles out of scope.
- The reproducible handoff — hand over the folder (or the HTML); zero setup on the other side.
The design is in the constraints
This is a designisinthecode post, so the quiet part: smolduck’s behavior is a direct readout of a few stubborn constraints.
- Untrusted code only in the VM → the kernel is a killable subprocess, off on the host. The safety model is the architecture.
- No build step → a pinned ESM import map and a tiny Preact UI, vendored so it runs with the network off.
- Disposable → state is plain files, so “throw away” and “reopen tomorrow” are one code path.
- Offline by default → the box has no network until something earns it. The analyst is the only thing that asks, so it gets exactly one hole the width of its provider — and “use a local model” becomes the obvious private default instead of a footnote.
- A sandboxed tool surface → exposing it to agents over MCP needed no new safety work. The box that protects you from your own pasted code protects you from theirs.
None of those are UI decisions. All of them are felt by the user. The design was in the code the whole time.
smolduck run ./your-data. Then close the laptop without the cold dread.
smolduck is open source (MIT): https://github.com/jjoaquim/smolduck.