{
  "markdown": "# SimEngine\n\n**A domain-agnostic Monte Carlo simulation and decision intelligence system.**\n\nZero dependencies. Pure Python stdlib. Local web UI. Runs anywhere Python runs.\n\nSimEngine turns uncertainty into numbers you can act on. Define your variables, your logic, and your risk tolerances in a JSON config. The engine runs thousands of simulated futures, scores them, and — if you ask — evolves toward the best strategy that still respects your safety constraints.\n\nThink of it as a calculator for uncertainty.\n\n---\n\n## Quick Start\n\n```bash\n# 1. Clone the repository\ngit clone <repo-url> ~/projects/simengine\ncd ~/projects/simengine\n\n# 2. Start the web UI\ncd v3 && python3 server.py\n\n# 3. Open your browser\n#    http://localhost:8420\n```\n\nThat is it. No `pip install`, no Docker, no Node, no build step. Python 3.8+ is the only requirement.\n\n## Install as an MCP server\n\n    uvx simengine-mcp\n\nListed on the [official MCP registry](https://registry.modelcontextprotocol.io/?search=simengine).\nHosted, no-install endpoint: [join the waitlist](https://cogswellspacely8-star.github.io/simengine-site/#waitlist).\n\n## Optional Native Acceleration\n\nThe default engine remains pure Python. A Rust workspace now lives at the repo root\nfor optional acceleration of metric reducers, trajectory bands, risk evaluation,\nhistograms, and native pre-sampling of simple numeric variable distributions.\n\nThis is opt-in and does not change the baseline workflow above.\n\n### Workspace layout\n\n```text\nCargo.toml\ncrates/\n├── simengine-core/   # pure Rust numeric kernels\n└── simengine-py/     # PyO3 extension module: simengine_native\nv3/native.py          # optional loader + Python fallback glue\n```\n\n### Build the native extension\n\n```bash\ncd crates/simengine-py\npython3 -m venv ../../.venv\nsource ../../.venv/bin/activate\npip install maturin\nmaturin develop --release\n```\n\nAfter that, the existing Python engine will automatically use the native\nreducers and the native batch sampler when `simengine_native` is importable.\nIf the module is missing or fails, SimEngine falls back to the existing\nPython implementation.\n\nThe batch sampler is conservative:\n- it only activates for plain numeric variable params\n- it stays off for correlated configs\n- it can be disabled explicitly with `SIMENGINE_DISABLE_NATIVE_SAMPLING=1`\n\nThis keeps the expression engine, accumulators, and metrics in Python while\nmoving a larger chunk of the Monte Carlo loop into Rust.\n\nThere is also a narrower native fast path for stochastic expressions:\n- `binomial(n, p)` can be delegated to Rust when the extension is installed\n- it can be disabled explicitly with `SIMENGINE_DISABLE_NATIVE_EXPR_SAMPLING=1`\n\nThis helps domains whose `step_logic` relies heavily on `binomial(...)` while\nstill leaving general expression evaluation in Python.\n\nOn the Python side, repeated expressions are now compiled once and reused\nacross Monte Carlo runs. That cache can be disabled explicitly with\n`SIMENGINE_DISABLE_EXPR_CACHE=1` for benchmarking or troubleshooting.\n\nThe diagnostics module applies the same idea to its AST-only safe evaluator:\nparsed trees are reused across calls, and that cache can be disabled with\n`SIMENGINE_DISABLE_SAFE_EXPR_CACHE=1`.\n\n---\n\n## What It Does\n\n- **Monte Carlo simulation** -- Run 5,000+ parallel futures for any domain you can describe in JSON.\n- **Safety constraints** -- Chance constraints (\"at most 15% probability of going broke\") and CVaR constraints (\"the average worst-case loss must not exceed $X\").\n- **Genetic optimizer** -- Evolve control parameters toward the best outcome while respecting safety bounds. Crossover, mutation, elitism, tournament selection.\n- **Bayesian updating** -- Feed observed data back into your model. Conjugate priors (Beta-Binomial, Gamma-Poisson, Normal-Normal) tighten distributions as evidence arrives.\n- **Diagnostics** -- Convergence analysis (proves your sample size is sufficient), sensitivity analysis (ranks which inputs drive the output), deterministic baseline comparison (quantifies the value of simulation over spreadsheet math).\n- **Web UI** -- Histograms, trajectory fans, risk gauges, scenario comparison with control sweeps and input diffs, domain wizard, Bayesian update modal, export to standalone HTML report.\n- **AI agent integration** -- POST a config inline to `/api/simulate` and get structured results back. No files needed. LLMs can use SimEngine as a tool (`llms.txt` at the repo root and `GET /llms.txt` orient them).\n- **Validated calibration** -- A sqlite forecast ledger scores predictions with proper scoring rules (Brier / log-loss / CRPS), and the pipeline is backtested on ~2,800 walk-forward monthly forecasts over decades of public FRED data. Run it yourself: `cd v3 && python3 backtest.py` (offline, from committed snapshots). Results: [docs/backtests/](docs/backtests/2026-07-06-fred-monthly-calibration.md).\n\n---\n\n## Architecture\n\nAll production code lives in `v3/`.\n\n```\nv3/\n├── kernel.py          Monte Carlo engine, distributions, safety evaluation,\n│                      fitness scoring, genetic optimizer, experiment manifests\n├── server.py          HTTP server (port 8420), all API endpoints, report generation\n├── bayesian.py        Conjugate prior updates for calibrating parameters from data\n├── diagnostics.py     Convergence, sensitivity (OAT), baseline comparison, AST evaluator\n├── static/\n│   └── index.html     Single-page web UI (vanilla HTML/JS/CSS, no framework)\n└── domains/\n    ├── _template.json              Annotated starter config\n    ├── business_pipeline_v3.json   B2B sales pipeline\n    ├── investment_portfolio.json   Portfolio allocation\n    ├── investment_portfolio_v2.json  Fat-tailed returns (Student's t)\n    ├── job_search.json             Job search decision model\n    ├── project_timeline.json       Software project estimation\n    ├── real_estate_rental.json     Rental property analysis\n    └── saas_startup.json           SaaS MRR growth and runway\n```\n\n---\n\n## Domain Config Schema\n\nA domain config is a single JSON file that fully describes the simulation. The v3 schema separates variables into three semantic categories, which matters for Bayesian updating and optimization.\n\n**Published contract:** the machine-readable [JSON Schema](v3/schema/domain.schema.json) (Draft 2020-12) is served live at `GET /api/schema` — hand it to any AI to author a valid config in one shot. A plain-language field reference is in [`docs/schema-reference.md`](docs/schema-reference.md). The schema is verified against every shipped domain in the test suite.\n\n### Top-Level Fields\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `name` | string | Human-readable domain name |\n| `description` | string | What this simulation models |\n| `n_simulations` | int | Number of Monte Carlo runs (default: 5000) |\n| `n_steps` | int | Time steps per simulation (e.g., 12 months) |\n| `step_label` | string | Label for time axis: \"month\", \"week\", \"sprint\" |\n| `seed` | int or null | Random seed for reproducibility |\n| `parameters` | object | Uncertain variables, updatable with evidence |\n| `controls` | object | Operator decisions (pricing, budget, effort) |\n| `exogenous` | object | External factors outside your control |\n| `initial_state` | object | Starting values for accumulators |\n| `step_logic` | array | Computations executed each time step |\n| `accumulators` | object | Running totals updated each step |\n| `metrics` | object | Final output measurements |\n| `safety` | object | Chance constraints and CVaR constraints |\n| `fitness` | object | Weighted scoring components for the optimizer |\n| `tunable` | array | Parameters the optimizer is allowed to adjust |\n| `context` | object | Optional metadata: decision question, key levers |\n| `calibration` | object | Per-variable data source and update method |\n\n### Variable Definition\n\nAll three variable categories (`parameters`, `controls`, `exogenous`) share the same structure:\n\n```json\n{\n  \"monthly_churn_rate\": {\n    \"distribution\": \"triangular\",\n    \"params\": {\"low\": 0.02, \"mode\": 0.05, \"high\": 0.12},\n    \"calibration\": {\n      \"source\": \"Baremetrics Open Benchmarks: median monthly churn 5-8%\",\n      \"updatable\": true,\n      \"update_method\": \"Beta-Binomial conjugate from observed monthly cancellations\"\n    }\n  }\n}\n```\n\n### Available Distributions\n\n| Distribution | Params | Use Case |\n|-------------|--------|----------|\n| `fixed` | `value` | Known constants |\n| `uniform` | `low`, `high` | Equal probability across a range |\n| `triangular` | `low`, `mode`, `high` | Expert estimates (pessimistic / likely / optimistic) |\n| `normal` | `mean`, `std`, `min?`, `max?` | Bell curve, optionally clamped |\n| `lognormal` | `median`, `spread`, `min?`, `max?` | Always positive, right-skewed (prices, durations) |\n| `bernoulli` | `p` | Binary yes/no events |\n| `poisson` | `lambda` | Counts of rare events |\n| `student_t` | `mean`, `scale`, `df`, `min?`, `max?` | Fat-tailed returns (use df=5 for equities) |\n| `correlated_normal` | `mean`, `std` | Normal with correlation support (via step_logic) |\n\n### Step Logic\n\nAn ordered list of computations executed each time step. Each entry has a `target` name and either an `expr` (expression string) or a `sample` (inline distribution):\n\n```json\n\"step_logic\": [\n  {\"target\": \"new_leads\",    \"expr\": \"binomial(round(demand), conversion_rate)\"},\n  {\"target\": \"revenue\",      \"expr\": \"new_leads * price_per_unit\"},\n  {\"target\": \"costs\",        \"expr\": \"fixed_cost + variable_cost * new_leads\"},\n  {\"target\": \"net_income\",   \"expr\": \"revenue - costs\"},\n  {\"target\": \"shock_event\",  \"sample\": {\"distribution\": \"bernoulli\", \"params\": {\"p\": 0.02}}}\n]\n```\n\n**Available expression functions:** `min`, `max`, `abs`, `round`, `sum`, `len`, `int`, `float`, `sqrt`, `log`, `exp`, `ceil`, `floor`, `pow`, `random()`, `gauss(mu, sigma)`, `binomial(n, p)`, `clamp(val, lo, hi)`, `range()`. Ternary expressions work: `100 if x > 0 else 0`.\n\n### Accumulators\n\nRunning state that persists across time steps:\n\n```json\n\"accumulators\": {\n  \"cumulative_revenue\": \"cumulative_revenue + revenue\",\n  \"cash\": \"cash + net_income\",\n  \"peak_subscribers\": \"max(peak_subscribers, subscribers)\"\n}\n```\n\n### Metrics\n\nFinal measurements computed after all steps complete:\n\n```json\n\"metrics\": {\n  \"total_revenue\": {\"expr\": \"cumulative_revenue\",          \"format\": \"dollar\"},\n  \"roi\":           {\"expr\": \"cumulative_revenue / cumulative_costs\", \"format\": \"percent\"},\n  \"final_subs\":    {\"expr\": \"subscribers\",                 \"format\": \"number\"}\n}\n```\n\n### Safety Constraints\n\n```json\n\"safety\": {\n  \"chance_constraints\": [\n    {\n      \"metric\": \"ending_cash\",\n      \"condition\": \"below\",\n      \"threshold\": 0,\n      \"max_prob\": 0.15,\n      \"rationale\": \"No more than 15% chance of running out of cash\"\n    }\n  ],\n  \"cvar_constraints\": [\n    {\n      \"metric\": \"ending_cash\",\n      \"alpha\": 0.10,\n      \"min_cvar\": -15000,\n      \"rationale\": \"Average of the worst 10% of outcomes must not exceed $15k loss\"\n    }\n  ]\n}\n```\n\n### Fitness and Tunable\n\nThe optimizer maximizes a weighted sum of fitness components while respecting safety constraints. Each component is an expression evaluated against the metric statistics (e.g., `total_revenue_median`, `ending_cash_p10`):\n\n```json\n\"fitness\": {\n  \"components\": [\n    {\"expr\": \"min(100, final_mrr_median / 100)\",  \"weight\": 0.30, \"name\": \"mrr_growth\"},\n    {\"expr\": \"max(0, 100 - months_negative_median * 5)\", \"weight\": 0.25, \"name\": \"time_to_profit\"}\n  ]\n},\n\"tunable\": [\n  {\"path\": \"controls.price_per_seat.params.value\",      \"range\": [9, 99]},\n  {\"path\": \"parameters.monthly_churn_rate.params.mode\",  \"range\": [0.01, 0.10]}\n]\n```\n\nFitness expressions can reference any metric stat in the form `{metric_name}_{stat}` where stat is one of: `mean`, `median`, `std`, `p10`, `p25`, `p75`, `p90`.\n\nSafety violations apply a 30% penalty per violated constraint. The optimizer learns to avoid them.\n\n---\n\n## API Reference\n\nThe server runs at `http://localhost:8420`. All POST endpoints accept and return JSON. CORS headers are included on all responses.\n\n### GET /api/domains\n\nList all available domain configs.\n\n**Response:**\n```json\n[\n  {\n    \"file\": \"saas_startup.json\",\n    \"name\": \"Indie SaaS -- MRR Growth & Runway\",\n    \"description\": \"Models a bootstrapped SaaS product over 24 months...\",\n    \"n_simulations\": 5000,\n    \"n_steps\": 24,\n    \"step_label\": \"month\"\n  }\n]\n```\n\n### GET /api/domain/{filename}\n\nLoad a specific domain config by filename.\n\n**Response:** The full JSON domain config object.\n\n### POST /api/simulate\n\nRun a Monte Carlo simulation.\n\n**Request (file-based):**\n```json\n{\n  \"domain\": \"saas_startup.json\",\n  \"n_simulations\": 5000,\n  \"seed\": 42,\n  \"overrides\": {\n    \"controls.price_per_seat.params.value\": 49\n  }\n}\n```\n\n**Request (inline config -- for AI agents):**\n```json\n{\n  \"config\": { ... full domain config object ... },\n  \"n_simulations\": 5000,\n  \"seed\": 42\n}\n```\n\n**Response:**\n```json\n{\n  \"domain\": \"Indie SaaS -- MRR Growth & Runway\",\n  \"n_simulations\": 5000,\n  \"elapsed\": 1.23,\n  \"stats\": {\n    \"final_mrr\": {\n      \"mean\": 4250.00,\n      \"median\": 3915.00,\n      \"std\": 2100.50,\n      \"p10\": 1740.00,\n      \"p25\": 2610.00,\n      \"p75\": 5220.00,\n      \"p90\": 7105.00,\n      \"format\": \"dollar\"\n    }\n  },\n  \"trajectories\": {\n    \"cash\": {\n      \"p10\": [24500, 23800, ...],\n      \"p50\": [25100, 25300, ...],\n      \"p90\": [25800, 26900, ...]\n    }\n  },\n  \"histograms\": {\n    \"final_mrr\": {\n      \"bins\": [0.0, 250.0, ...],\n      \"counts\": [12, 45, ...],\n      \"bin_width\": 250.0,\n      \"lo\": 0.0,\n      \"hi\": 10000.0\n    }\n  },\n  \"safety\": {\n    \"chance\": [\n      {\"metric\": \"ending_cash\", \"condition\": \"below\", \"threshold\": 0,\n       \"max_prob\": 0.15, \"actual_prob\": 0.082, \"passed\": true}\n    ],\n    \"cvar\": [\n      {\"metric\": \"ending_cash\", \"alpha\": 0.10, \"min_cvar\": -15000,\n       \"actual_cvar\": -8500.00, \"passed\": true}\n    ],\n    \"all_passed\": true\n  },\n  \"fitness\": 72.5,\n  \"narrative\": \"**Final Mrr** is most likely $3,915 (range: $1,740 to $7,105 in 80% of scenarios)...\",\n  \"context\": {\"decision\": \"Should I go full-time on this SaaS?\"}\n}\n```\n\n### POST /api/evolve\n\nRun the genetic optimizer to find optimal control settings.\n\n**Request:**\n```json\n{\n  \"domain\": \"saas_startup.json\",\n  \"generations\": 15,\n  \"population\": 20\n}\n```\n\n**Response:**\n```json\n{\n  \"domain\": \"Indie SaaS -- MRR Growth & Runway\",\n  \"best_fitness\": 85.3,\n  \"best_stats\": { ... same format as simulate stats ... },\n  \"best_safety\": { \"all_passed\": true, \"chance\": [...], \"cvar\": [...] },\n  \"history\": [\n    {\"gen\": 0, \"best_fit\": 62.1, \"avg_fit\": 45.3, \"safe_pct\": 60.0, \"elapsed\": 2.1},\n    {\"gen\": 1, \"best_fit\": 68.5, \"avg_fit\": 52.1, \"safe_pct\": 75.0, \"elapsed\": 2.0}\n  ],\n  \"elapsed\": 31.5\n}\n```\n\n### POST /api/validate\n\nValidate a domain config without running a full simulation. Runs a 10-iteration test to catch expression errors.\n\n**Request:**\n```json\n{\n  \"config\": { ... domain config object ... }\n}\n```\n\n**Response:**\n```json\n{\n  \"valid\": true,\n  \"errors\": []\n}\n```\n\nOr on failure:\n```json\n{\n  \"valid\": false,\n  \"errors\": [\n    \"Variable 'close_rate': unknown distribution 'beta'\",\n    \"step_logic[2]: missing 'target'\"\n  ]\n}\n```\n\n### POST /api/save-domain\n\nValidate and save a domain config to the `domains/` directory.\n\n**Request:**\n```json\n{\n  \"config\": { ... domain config object ... },\n  \"filename\": \"my_new_domain\"\n}\n```\n\n**Response:**\n```json\n{\n  \"saved\": true,\n  \"file\": \"my_new_domain.json\"\n}\n```\n\n### POST /api/update\n\nBayesian update: feed observed data into a domain config and get updated parameter distributions plus a preview simulation.\n\n**Request:**\n```json\n{\n  \"domain\": \"saas_startup.json\",\n  \"observations\": {\n    \"monthly_churn_rate\": [0.04, 0.06, 0.03, 0.05],\n    \"organic_signup_rate\": [18, 22, 15, 25, 20]\n  }\n}\n```\n\n**Response:**\n```json\n{\n  \"updated_config\": { ... full config with tightened distributions ... },\n  \"update_report\": {\n    \"monthly_churn_rate\": {\n      \"method\": \"beta_binomial\",\n      \"prior_mean\": 0.05,\n      \"posterior_mean\": 0.045,\n      \"posterior_std\": 0.012,\n      \"observations_used\": 4,\n      \"credible_interval_90\": [0.025, 0.065]\n    }\n  },\n  \"preview_stats\": { ... simulation results with updated params ... },\n  \"preview_safety\": { ... }\n}\n```\n\n### POST /api/report\n\nGenerate a standalone HTML report from simulation results.\n\n**Request:**\n```json\n{\n  \"results\": { ... simulate response object ... },\n  \"config\": { ... domain config ... }\n}\n```\n\n**Response:** HTML document (Content-Type: text/html). Self-contained, no external dependencies. Save it, email it, print it.\n\n---\n\n## CLI Usage\n\n### kernel.py\n\n```bash\ncd ~/projects/simengine/v3\n\n# Run a Monte Carlo simulation\npython3 kernel.py domains/saas_startup.json\n\n# Set a random seed for reproducibility\npython3 kernel.py domains/saas_startup.json --seed 42\n\n# Save results to a specific file\npython3 kernel.py domains/saas_startup.json --output results.json\n\n# Run evolutionary optimization\npython3 kernel.py domains/saas_startup.json --evolve\n\n# Customize optimizer parameters\npython3 kernel.py domains/saas_startup.json --evolve \\\n  --generations 25 \\\n  --population 30 \\\n  --workers 8 \\\n  --output best_strategy.json\n```\n\nShort flags: `-g` (generations), `-p` (population), `-w` (workers), `-o` (output).\n\n### diagnostics.py\n\n```bash\ncd ~/projects/simengine/v3\n\n# Convergence analysis -- proves sample size is sufficient\n# Runs at N=100, 250, 500, ..., 20000 and measures estimator stability.\npython3 diagnostics.py domains/saas_startup.json --convergence\n\n# Sensitivity analysis -- ranks which inputs drive the output\n# One-at-a-time perturbation with normalized impact scores.\npython3 diagnostics.py domains/saas_startup.json --sensitivity\n\n# Baseline comparison -- Monte Carlo vs deterministic spreadsheet\n# Shows the risk that point-estimate forecasting hides.\npython3 diagnostics.py domains/saas_startup.json --baseline\n\n# Target a specific metric (default: first metric defined)\npython3 diagnostics.py domains/saas_startup.json --convergence --metric ending_cash\n\n# Save results to JSON\npython3 diagnostics.py domains/saas_startup.json --convergence --output conv.json\n```\n\nYou can combine flags: `--convergence --sensitivity --baseline` runs all three.\n\n---\n\n## Creating a New Domain\n\nThere are three ways to create a domain config.\n\n### 1. Web UI Wizard\n\nOpen `http://localhost:8420`, click the domain wizard, and fill in the form. The wizard generates a valid config, validates it, and saves it to the `domains/` directory.\n\n### 2. Copy the Template\n\n```bash\ncp ~/projects/simengine/v3/domains/_template.json ~/projects/simengine/v3/domains/my_problem.json\n```\n\nEdit `my_problem.json`. The template includes inline documentation in the `_guide` field and a working example structure. Replace the placeholder variables, logic, and metrics with your own.\n\n### 3. Generate with an LLM\n\nDescribe your problem to an LLM and ask it to produce a SimEngine v3 domain config. Validate it before use:\n\n```bash\n# Validate from the command line via the API\ncurl -X POST http://localhost:8420/api/validate \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"config\": <paste your JSON>}'\n```\n\nOr POST the config directly to `/api/simulate` with `{\"config\": {...}}` -- it will either run or return an error.\n\n### Minimum Viable Config\n\nThe smallest config that will run:\n\n```json\n{\n  \"name\": \"Coin Flip\",\n  \"n_simulations\": 1000,\n  \"n_steps\": 10,\n  \"parameters\": {\n    \"heads\": {\n      \"distribution\": \"bernoulli\",\n      \"params\": {\"p\": 0.5}\n    }\n  },\n  \"step_logic\": [\n    {\"target\": \"payout\", \"expr\": \"1 if heads > 0.5 else -1\"}\n  ],\n  \"accumulators\": {\n    \"bankroll\": \"bankroll + payout\"\n  },\n  \"metrics\": {\n    \"final_bankroll\": {\"expr\": \"bankroll\", \"format\": \"number\"}\n  },\n  \"initial_state\": {\"bankroll\": 0}\n}\n```\n\n---\n\n## Bayesian Updating\n\nSimEngine supports conjugate Bayesian updates that tighten your parameter distributions as real-world data arrives.\n\n### How It Works\n\n1. You start with a prior -- the distribution you specified in the config (e.g., `triangular(0.02, 0.05, 0.12)` for churn rate).\n2. You observe real data (e.g., actual monthly churn: `[0.04, 0.06, 0.03, 0.05]`).\n3. The engine computes the posterior distribution using the appropriate conjugate update.\n4. The config is rewritten with tighter distributions that reflect both your prior belief and the evidence.\n\n### Supported Methods\n\n| Method | Conjugate Pair | Use For |\n|--------|---------------|---------|\n| `beta_binomial` | Beta-Binomial | Rates and probabilities: close rate, churn rate, conversion rate |\n| `gamma_poisson` | Gamma-Poisson | Counts: leads per month, bugs per sprint, signups per week |\n| `normal_normal` | Normal-Normal | Continuous means: project value, salary, deal size |\n\n### Method Selection\n\nThe method is chosen in this order:\n\n1. Explicit `calibration.update_method` in the variable definition.\n2. Auto-inferred from the distribution type and parameter range (values in [0,1] get Beta-Binomial; integer-valued Poisson gets Gamma-Poisson; everything else gets Normal-Normal).\n\n### Via the API\n\n```bash\ncurl -X POST http://localhost:8420/api/update \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n    \"domain\": \"saas_startup.json\",\n    \"observations\": {\n      \"monthly_churn_rate\": [0.04, 0.06, 0.03, 0.05]\n    }\n  }'\n```\n\nThe response includes the updated config, a diagnostic report showing prior vs. posterior, and a preview simulation with the updated parameters.\n\n### Via Python\n\n```python\nfrom bayesian import update_parameter\n\nupdated_params, diagnostics = update_parameter(\n    variable_def={\n        \"distribution\": \"triangular\",\n        \"params\": {\"low\": 0.02, \"mode\": 0.05, \"high\": 0.12}\n    },\n    observations=[0.04, 0.06, 0.03, 0.05],\n    method=\"beta_binomial\"\n)\n\nprint(diagnostics[\"posterior_mean\"])    # 0.0459\nprint(diagnostics[\"credible_interval_90\"])  # [0.0312, 0.0606]\n```\n\n---\n\n## AI Agent Integration\n\nSimEngine is designed to be used as a tool by LLMs and AI agents. The key feature is inline config support: POST a full domain config directly to `/api/simulate` without saving any files.\n\n### The Pattern\n\n1. The agent describes a problem as a SimEngine domain config (JSON).\n2. The agent POSTs `{\"config\": {...}, \"n_simulations\": 5000, \"seed\": 42}` to `/api/simulate`.\n3. The agent reads the structured response: stats, safety evaluation, fitness score, narrative.\n4. The agent reasons about the results and either reports back or adjusts the config and re-runs.\n\n### Example: Agent Tool Call\n\n```python\nimport json, urllib.request\n\nconfig = {\n    \"name\": \"Should I take this contract?\",\n    \"n_simulations\": 3000,\n    \"n_steps\": 6,\n    \"step_label\": \"month\",\n    \"parameters\": {\n        \"hours_per_week\": {\n            \"distribution\": \"triangular\",\n            \"params\": {\"low\": 15, \"mode\": 25, \"high\": 45}\n        },\n        \"scope_creep\": {\n            \"distribution\": \"bernoulli\",\n            \"params\": {\"p\": 0.3}\n        }\n    },\n    \"controls\": {\n        \"hourly_rate\": {\n            \"distribution\": \"fixed\",\n            \"params\": {\"value\": 150}\n        }\n    },\n    \"exogenous\": {},\n    \"initial_state\": {\"total_hours\": 0, \"total_revenue\": 0},\n    \"step_logic\": [\n        {\"target\": \"weekly_hours\", \"expr\": \"hours_per_week * (1.4 if scope_creep > 0.5 else 1.0)\"},\n        {\"target\": \"monthly_hours\", \"expr\": \"weekly_hours * 4.33\"},\n        {\"target\": \"monthly_rev\", \"expr\": \"min(monthly_hours, 160) * hourly_rate\"}\n    ],\n    \"accumulators\": {\n        \"total_hours\": \"total_hours + monthly_hours\",\n        \"total_revenue\": \"total_revenue + monthly_rev\"\n    },\n    \"metrics\": {\n        \"total_revenue\": {\"expr\": \"total_revenue\", \"format\": \"dollar\"},\n        \"effective_rate\": {\"expr\": \"total_revenue / total_hours if total_hours > 0 else 0\", \"format\": \"dollar\"},\n        \"total_hours\": {\"expr\": \"total_hours\", \"format\": \"number\"}\n    }\n}\n\nreq = urllib.request.Request(\n    \"http://localhost:8420/api/simulate\",\n    data=json.dumps({\"config\": config, \"seed\": 42}).encode(),\n    headers={\"Content-Type\": \"application/json\"},\n    method=\"POST\"\n)\nresp = json.loads(urllib.request.urlopen(req).read())\n\nprint(f\"Revenue (median): ${resp['stats']['total_revenue']['median']:,.0f}\")\nprint(f\"Revenue (P10-P90): ${resp['stats']['total_revenue']['p10']:,.0f} - ${resp['stats']['total_revenue']['p90']:,.0f}\")\nprint(f\"Effective rate (median): ${resp['stats']['effective_rate']['median']:,.0f}/hr\")\n```\n\n### What Agents Get Back\n\nThe `/api/simulate` response includes everything an agent needs to reason about the outcome:\n\n- `stats` -- Full distribution statistics for every metric (mean, median, std, p10/p25/p75/p90).\n- `safety` -- Pass/fail on every constraint with actual probabilities.\n- `fitness` -- Single scalar score for comparing scenarios.\n- `narrative` -- Human-readable summary text.\n- `trajectories` -- Time-series percentile bands for trend analysis.\n- `histograms` -- Binned data for distribution shape analysis.\n\n### Scenario Comparison\n\nTo compare alternatives, an agent can POST multiple configs (varying the controls) and compare fitness scores, safety evaluations, or specific metric percentiles across the responses.\n\n### Decision Gate: `decide_under_uncertainty` (MCP)\n\nMulti-step agents compound overconfident point answers. The `decide_under_uncertainty` MCP tool turns a simulation into a **proceed / escalate** verdict: it runs the Monte Carlo, measures how much of the *outcome* distribution lands inside a range you accept, and returns the flag plus the full distribution and a plain-English reason.\n\nIt models **outcome risk** -- the spread of results given uncertain inputs -- not the language model's own token confidence. The two are complementary; do not conflate them.\n\n**Arguments:** `metric` (required), `acceptable` (`{\"min\": ..., \"max\": ...}`, at least one bound), `min_confidence` (probability mass that must land in range to proceed, default 0.8), plus `domain` or inline `config`, and optional `seed` / `n_simulations`.\n\n```json\n{\n  \"metric\": \"annual_gross_income\",\n  \"decision\": \"proceed\",\n  \"proceed\": true,\n  \"probability_within_bounds\": 0.92,\n  \"min_confidence\": 0.8,\n  \"acceptable\": {\"min\": 40000, \"max\": null},\n  \"distribution\": {\"median\": 68000, \"p10\": 44000, \"p90\": 95000, \"format\": \"dollar\"},\n  \"n_simulations\": 5000,\n  \"reason\": \"92% of 5,000 simulated outcomes for 'annual_gross_income' fall within the acceptable range (>= 40,000) -- at or above the 80% confidence required to proceed.\"\n}\n```\n\nSee [`docs/decision-gate.md`](docs/decision-gate.md) for the full agent pattern.\n\n---\n\n## Calibration & Track Record\n\nA Monte Carlo engine will always emit a confident-looking distribution. The only thing that separates *\"a calculator that produces plausible numbers\"* from *\"a forecasting instrument with a demonstrated hit rate\"* is a track record scored against reality. `v3/calibration.py` is that spine -- pure stdlib, zero dependencies.\n\n**Proper scoring rules:** `brier_score`, `log_loss`, `crps_sample`.\n\n**Forecast ledger:** record a forecast now, resolve it against the real outcome later, score how well-calibrated you have been over time.\n\n```python\nfrom calibration import CalibrationLedger\n\nledger = CalibrationLedger(\"forecasts.db\")\nfid = ledger.record(\"Annual gross >= $40k?\", 0.82, metric=\"annual_gross_income\")\n# ... the year resolves ...\nledger.resolve(fid, 1)\nprint(ledger.score())   # {\"n\": 1, \"brier\": 0.0324, \"log_loss\": 0.198}\n```\n\nBrier 0.0 is perfect; 0.25 is the score of always guessing 50/50; 1.0 is worst. Log-loss punishes confident-and-wrong forecasts harder. Lower is better for both. Time is the moat: the track record only accumulates if logging starts now.\n\n---\n\n## Real-Time Data (opt-in)\n\nThe core engine is offline and pure-stdlib. The `v3/feeds/` package is the optional bridge to live data — the only part of SimEngine that touches the network, still using only `urllib` (no new dependencies), and never imported by the core.\n\nThe first adapter, `feeds/polymarket.py`, turns a live Polymarket prediction-market price into a **calibrated prior**: a market-implied probability becomes a SimEngine variable you can drop into a domain config and then tighten with the Beta-Binomial updater as your own evidence arrives. Price in → prior → posterior, not price in → price out.\n\n```python\nfrom feeds import polymarket\nmarkets = polymarket.fetch_markets(query=\"fed rate\", limit=5)\nprior = polymarket.market_prior(markets[0], strength=20)   # -> a triangular variable_def\n```\n\nSee [`docs/feeds.md`](docs/feeds.md). Market prices are treated as internal simulation inputs, never a rebroadcast feed or trade signal.\n\n---\n\n## Ask SimEngine Anything (natural language)\n\nDon't want to write a config? Describe your decision in plain words and let an LLM build the model for you. SimEngine drafts a config, validates it against the engine, **repairs** any errors, runs the simulation, and explains the result — so even small local models stay reliable, because the engine grounds whatever the model produces.\n\nThis is **opt-in** and stdlib-only. It defaults to a **local** model via [Ollama](https://ollama.com) — free, private, no API key. Set an environment variable to use a hosted model instead.\n\n### CLI\n\n```bash\n# With Ollama running (e.g. `ollama pull qwen2.5`):\npython3 v3/sim_ask.py \"Should I take an $8k/mo, 6-month contract if there's a 30% chance it overruns?\"\n```\n\nFlags: `--n` (simulations), `--seed`, `--save NAME` (persist the config to `domains/`), `--show-config`, `--json`, `--model NAME`, `--backend`.\n\n### Web\n\nOpen `http://localhost:8420`, click **Ask anything**, type your question, and run. You get a plain-language answer, the key numbers, the safety verdict, and the generated model — which you can save as a reusable domain.\n\n### API\n\n`POST /api/ask` with `{\"question\": \"...\", \"n_simulations\": 3000, \"seed\": 42}` returns `{config, results, explanation, attempts, valid}`.\n\n### Backends (environment variables)\n\n| Variable | Default | Purpose |\n|---|---|---|\n| `SIMENGINE_LLM_BACKEND` | `ollama` | `ollama` \\| `openai` \\| `anthropic` |\n| `SIMENGINE_LLM_MODEL` | `qwen2.5` | model name |\n| `SIMENGINE_LLM_TIMEOUT` | `120` | per-request seconds (raise for slow local models) |\n| `OPENAI_API_KEY` / `OPENAI_BASE_URL` | — | OpenAI-compatible (also covers llama.cpp / LM Studio / vLLM) |\n| `ANTHROPIC_API_KEY` | — | Anthropic |\n\nDecision support and scenario analysis only — never financial advice or a trade signal.\n\n---\n\n## What the Numbers Mean\n\nWhen you run a simulation, each metric reports:\n\n- **Mean** -- Average across all simulated futures.\n- **Median** -- The 50th percentile. Half of futures are above, half below.\n- **P10** -- Pessimistic. Only 10% of futures are worse.\n- **P90** -- Optimistic. Only 10% of futures are better.\n- **Std** -- Standard deviation. Width of the distribution.\n- **CVaR(alpha)** -- The average outcome in the worst alpha% of futures. This is the number that risk-aware decision makers care about most.\n\nSafety constraints translate directly to decision language:\n\n- **Chance constraint:** \"The probability of [metric] falling [below/above] [threshold] must not exceed [X]%.\"\n- **CVaR constraint:** \"In the worst [alpha]% of scenarios, the average [metric] must be at least [min_cvar].\"\n\n---\n\n## Documentation\n\n**New to SimEngine? Start with the [plain-language guide](docs/guide/).** It explains what SimEngine is, how to read results, who it is for, how to explain it to others, and how to model your own decision — no background required.\n\nPeer review reports and the system design paper are available on request. Contact the maintainer for access.\n\n## License\n\nSimEngine uses an **open-core** model.\n\n- **Engine, server, kernels, diagnostics, Rust acceleration, and domain library** — [Apache License 2.0](LICENSE). Free to use, modify, self-host, embed in your own products, and redistribute under the terms of that license.\n- **Hosted SimEngine MCP service** (operated by OtrovertLabs) — [Commercial license](LICENSE-COMMERCIAL.md). Subscription tiers for managed hosting, OAuth, persistent state, SLAs, and enterprise features.\n\nSelf-hosting the open-source engine is and will remain free. The commercial license applies only to the hosted service and managed features that are not part of the open-source distribution.\n\nSimEngine is an **OtrovertLabs** product, published by Venture Horizon LLC.\n\nFor commercial licensing, enterprise inquiries, or partnerships: https://otrovertlabs.com/contact\n",
  "bytes": 32410,
  "sha": "b85cd89ab781cae327d87718e059d20775d71331123a8a64b99f3e4dcccef1ea",
  "repo_slug": "cogswellspacely8-star/simengine",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_cogswellspacely8_star_simengin_bf50688e/readme"
}