demolab ships Python: the skeleton lays down a uv environment, plotting helpers, and a provenance stamp, and every worked reference is Python. But Python is the opinionated example, not a requirement. The contract that connects a tool to an experiment is file-based and language-neutral, so your science can stay in whatever language it already lives in.
A tool is never imported — it is reached by running its command line (a subprocess), and it communicates only through files: config.json, output.json, manifest.json, a run.sh reproducer, and its figure data (.csv, .json, .npz — your choice of format). Any executable that parses arguments and writes those files is a valid tool. The write-up reads numbers.json, and Typst typesets from that plus the rendered figures — neither cares what produced them. So the only language-bound layer is the tool itself. See The contract for the full boundary.
Pick the smallest change that gets your language into the lab.
tools/<tool>/tool.<ext> in MATLAB, Julia, R, or another language; the runner still shells out to it and keeps a minimal uv environment purely to stage figures and plot. Least churn, and the recommended path — you rewrite only the science.numbers.json, then run that runner on the new runtime and drop the Python dependencies. Julia is the natural fit here: it can own the tool, the runner, and the plotting. Take this only if you want Python out entirely.Either path leaves the rest of demolab untouched: the engine (Typst, inside the demolab-cli package), your writings/*.typ (they read numbers.json exactly as before), artifacts/, and the numbers.json schema. Provenance still holds — you reimplement the stamp (git commit, dirty flag, UTC timestamp) in the new language’s run-setup helper.
Three primitives, which every mainstream scientific language has: run non-interactively from a command line, write JSON for the contract files, and write figure data (a CSV is enough) — plus a unit-test runner. MATLAB (matlab -batch), Octave, Julia (JSON3, CSV), and R (jsonlite, write.csv) all qualify.
Say MIGRATE-STACK (or “use Julia instead of Python”) and your agent follows the MIGRATE-STACK runbook: it confirms the runtime, ports the tool contract into your language, wires the runner entry point and demolab test to it, and offers the hybrid path first.