Architecture
How the CLI orchestrator and the inference engines fit together.MCMC.js is a thin TypeScript orchestrator over inference subprocesses. The orchestrator owns everything that does not strictly require a probabilistic-programming runtime; it starts a runtime process, Julia or CmdStan, only to run the model itself.
A thin orchestrator
The TypeScript side, running on Node.js (>= 22), owns argument parsing, spec validation, file I/O, diagnostics, plotting, the run store, environment bootstrap, and end-to-end command sequencing. This keeps the surface that humans and agents interact with fast, portable, and dependency-light.
The subprocess boundary
Only inference needs the live model, so only inference enters a runtime.
Four commands touch one: setup (provision the toolchain), fit and predict (run the model), and run (which sequences a fit).
The runtime follows the spec’s backend: Turing and JuliaBUGS models run a Julia subprocess, Stan models run CmdStan executables.
Everything else is pure TypeScript and never starts a runtime process: diagnose, summary, samples, plot, convert, doctor, engines, the runs/show/export store commands, and the julia version and stan version management groups.
Those commands operate on files, not on live models, so they have no reason to pay a runtime’s startup or installation cost.
This split is the central design decision. It also explains why the orchestrator lives outside the runtimes: a bootstrapping tool cannot be written in the runtime it bootstraps, because it must be able to install Julia or CmdStan on a machine that has neither.
When inference does run, the TypeScript side starts the runtime through node:child_process (no shell), crossing a single, narrow boundary.
Spec in, samples out
The whole system is organized around two file contracts.
spec file (TOML/JSON) -> [ fit / predict in Julia or CmdStan ] -> samples file (MCMCChains JSON)
|
[ diagnose / summary / plot in TS ] <-+
- The spec file declares the model, data, and sampler. It is validated with zod and carries a
schema_version. - The samples file is MCMCChains JSON, capturing draws plus sampler statistics. Alongside it, a fit writes a run record (its own
schema_version) capturing the spec hash, seed, backend, resolved runtime and package versions, and provenance hashes.
Because the output of one stage is the input to the next, the commands compose.
Insulation from the chain-type change
Turing.jl is changing its default in-memory chain type. MCMC.js defines its on-disk contract as a cross-ecosystem standard, not a Turing-internal type dumped to disk: the Julia driver requests a stable chain type and converts whatever the sampler returns. The conversion happens in exactly one place, inside the Julia bridge, so the chain-type change is absorbed there and never reaches the diagnostics, plotting, or CLI layers.
Where to go next
- The package map: Packages.
- The pluggable engine contract: Engine contract.
- The Julia bridge and driver: Julia driver.
- The native Stan engine: Stan engine.
- The plotting stack: Plotting internals.