Build on Sortia
@sortia/engine 0.1.0sortia-mcp 0.1.0spec v1
Sortia gives spreadsheet decisions their odds inside Google Sheets: estimates in, odds out, plus optimization, decision trees, forecasting, statistics and machine learning. The engine behind the odds, the optimizer and the statistics also runs headless in plain Node, driven by a small JSON document, so your scripts and your AI agents can use it too.
The engine: @sortia/engine
@sortia/engine (version 0.1.0) is a plain Node library with zero runtime
dependencies. It needs Node 18 or newer. It is not a rewrite of the add-on: the package
loads the exact engine source files the Sheets add-on ships and wraps them with a thin
spec layer. The numbers you get in Node are the numbers the add-on produces, by
construction, and the whole thing is covered by the same automated test suite.
The public API is small:
validate(spec)checks a spec without running it. It reports every problem at once, each with a stable machine code (E_PARAM_UNKNOWN,E_CIRCULAR, and so on), a JSON Pointer path to the offending field, and usually a hint with the fix.run(spec)validates, runs, and returns a typed result for the spec's kind.describeCapabilities()returns the machine-readable feature list: every distribution family and its parameters, the formula function whitelist, optimization engines, limits, and the determinism contract.- The spec's JSON Schema (draft-07) ships in the package as
spec.schema.json, so external validators and CI can check specs without running the engine.
The model spec
One versioned JSON document describes a model. specVersion is always 1
today, and kind selects the tool family: simulation,
optimization, riskOptimization, decisionTree,
goalSeek, dataTable, statTest, fit,
regression, or forecast.
Everything is referenced by declared names, never by spreadsheet cell addresses. Formulas are ordinary spreadsheet expressions over those names, the same functions you would type into a cell. Unknown fields, unknown parameters, and undeclared names are rejected with precise errors instead of being silently ignored, which makes the format friendly to code generation: a wrong spec fails loudly with a pointer to the exact field.
A worked example: startup runway
How many months of runway does a startup have when burn, growth, and starting revenue are all estimates? This is a complete, runnable spec:
{
"specVersion": 1,
"kind": "simulation",
"name": "Startup runway",
"trials": 20000,
"seed": 42,
"sampling": "lhs",
"inputs": [
{ "name": "monthly_burn", "dist": "triangular",
"params": { "min": 70000, "mode": 90000, "max": 120000 } },
{ "name": "revenue_growth", "dist": "triangular",
"params": { "min": 0.03, "mode": 0.12, "max": 0.20 } },
{ "name": "starting_mrr", "dist": "pert",
"params": { "min": 15000, "mode": 22000, "max": 30000 } }
],
"correlations": [
{ "a": "monthly_burn", "b": "starting_mrr", "rho": 0.35 }
],
"model": [
{ "name": "starting_cash", "formula": "1800000" },
{ "name": "revenue_12mo", "formula":
"starting_mrr * ((1 + revenue_growth)^12 - 1) / revenue_growth" },
{ "name": "cash_at_month_12", "formula":
"starting_cash - 12 * monthly_burn + revenue_12mo" },
{ "name": "runway_months", "formula":
"IF(monthly_burn <= starting_mrr, 60, starting_cash / (monthly_burn - starting_mrr))" }
],
"outputs": ["runway_months", "cash_at_month_12"]
}
Run it:
const { run } = require('@sortia/engine');
const result = run(spec);
const runway = result.outputs[0];
runway.stats.mean; // 25.7516 months
runway.stats.median; // 25.4867
runway.percentiles; // p5 20.4849, p95 32.1179, full ladder p1 to p99
runway.stats.minimum; // 17.9854
runway.stats.maximum; // 40.0267
runway.sensitivity; // tornado, by |Spearman|: monthly_burn -0.961,
// starting_mrr -0.097, revenue_growth -0.001
result.outputs[1].stats.mean; // cash_at_month_12: 1216196.59
Those numbers are not illustrative. They are what version 0.1.0 returns for this spec, every time, on every machine. Each output also carries the raw per-trial values, and the result echoes a reproducibility receipt: spec version, kind, engine version, and seed.
The determinism contract
run(spec) is a pure function of the spec document. That means:
- The same spec with the same seed produces bit-identical results. Not close, identical.
- The seed lives in the spec for every stochastic kind (simulation, riskOptimization, and optimization with the evolutionary engine). There is no run-time seed override and no unseeded random mode.
- Nothing on the run path reads the clock or calls
Math.random. All termination budgets are iteration or generation counts, never wall-clock time. - Kinds that consume no randomness (decision trees, goal seek, data tables, statistical tests, fitting, regression, forecasting, and lp or nonlinear optimization) are bit-identical with no seed at all.
- The order of the
inputsarray is part of the contract: reordering inputs changes the random stream, so it changes the results. - Version numbers take numeric output seriously: any change that alters the numbers for an existing spec and seed is a major version bump, even when the change is a bug fix. To reproduce results exactly, pin the spec, the seed, and the engine major version.
SIPmath libraries
Where a distribution leaves or enters Sortia it travels as a SIPmath 3.0 library, the
open standard from
probabilitymanagement.org
for passing uncertain quantities between tools as one small JSON file. In the add-on,
Risk Analysis, the Monte Carlo panel, Project schedule risk and Distribution Fitting
save their results as a .SIPmath library, validated against the Standard's
published schema; a Risk run saves every output, or the model's full input set with its
correlation matrix. Risk Analysis reads a library back as inputs: Metalog 1.0 entries on
an HDR generator are used, and GeneralizedMetalog and Metalog 2.0 entries, stored arrays
and lookup tables are listed but skipped with a reason. A coherent run option draws each
SIPmath input from the file's own seeds, so its trials are the ones any other SIPmath
tool reads from the same file; an input the file correlates through a copula, or one
you truncate or correlate in Sortia, shares the shape rather than the trial order.
Because the file is a published standard, your own scripts can read it with nothing
from Sortia installed.
MCP quickstart
sortia-mcp (version 0.1.0) wraps the engine as a local Model Context
Protocol server over stdio. Everything runs in-process on your machine; the server
makes no network calls. Until it reaches npm, run it from the repository:
cd sortia/packages/mcp
npm install
Then point your MCP client at it. For clients configured with JSON:
{
"mcpServers": {
"sortia": {
"command": "node",
"args": ["/absolute/path/to/sortia/packages/mcp/bin/sortia-mcp.js"]
}
}
}
For Claude Code:
claude mcp add sortia -- node /absolute/path/to/sortia/packages/mcp/bin/sortia-mcp.js
The server exposes one tool per engine, 21 in all as of 2026-09-26. The ones most agents reach for first:
run_simulation: estimates in, odds out. A seeded run over named formulas with 36 distribution families, optional rank correlations, percentile ladders, and tornado sensitivity.optimize: constrained optimization with three engines (simplex, Nelder-Mead, differential evolution) and a per-constraint satisfaction report.evaluate_decision_tree: expected-value rollback with the optimal policy and the full risk profile.goal_seek: solve one constant so a formula hits a target value.fit_distribution: fit candidate distributions to data, ranked by Kolmogorov-Smirnov, each with a fragment ready to paste intorun_simulation.run_stat_test: t, z, F, ANOVA, chi-square, rank-based tests, and two-proportion A/B tests.describe_capabilities: the engine's full accepted vocabulary. Have your agent call this first before authoring complex specs.
Validation errors pass through to the agent verbatim, with stable codes, JSON Pointer paths, and hints, so a model that emits a bad spec can read the error and fix its own output.
Pointing an assistant at Sortia without the server? llms.txt is the plain-text summary of the product and this page, written for a model to read.
Questions
Building something on Sortia, or want the packages before they land on npm? Email hello@sortia.io.