{"name":"run_simulation","description":"Run a Monte Carlo simulation on a supplied graph. Small runs return the full summarised result inline; large runs return a run_id to stream (POST /api/v1/runs SSE) or poll (get_results). Debits credits per run for authenticated callers.","input_schema":{"$defs":{"AggregationRequest":{"additionalProperties":false,"description":"A post-simulation, series-aware metric over one node's full series.\n\nNPV/IRR/payback/CAGR/cumsum are NOT per-period node values: they consume a\ntarget node's complete ``(time_periods x trials)`` matrix AFTER the run and\nreturn one value per trial, summarised into a distribution.\n\nDiscount convention (0-based series index): period ``t=0`` is the present\n(undiscounted) cash flow; period ``t`` is discounted by ``(1+rate)**t``. The\nfirst period is undiscounted.\n\nWarning — two discount conventions coexist; do not mix them. These\naggregations use the **0-based** series index above (first period\nundiscounted). The per-period formula functions ``DISCOUNT(rate,\n{time_period})`` and ``PV(rate, value, {time_period})`` use the **1-based**\n``{time_period}``, so they discount the first period by ``(1+rate)**1``.\n\n\"Investment at period 0\" pattern: model an up-front outlay as a negative\nperiod-0 cash flow in the target node's series; NPV's t=0-undiscounted\nconvention then treats it as the present-value outlay. Do not additionally\ndiscount that first period with ``PV(...,{time_period})`` inside the target\nnode's formula when also requesting an aggregation ``npv`` — combining the\n1-based per-period discount with the 0-based ``npv`` double-shifts the first\nperiod.\n\nPer-period node vs aggregate metric (``prob``): pointing ``prob`` at a\nmulti-period cash-flow NODE via ``target_node_id`` thresholds only that node's\nFINAL-period per-trial value — i.e. ``P(per-period value > x)``, NOT\n``P(NPV > x)``. To get a true ``P(NPV > 0)``, define an ``npv`` aggregation\nfirst, then add a ``prob`` aggregation whose ``target_aggregation_id`` points\nat that ``npv`` id; ``prob`` then thresholds the NPV per-trial vector directly.\nA ``prob`` request targets EITHER a node (``target_node_id``) OR an earlier\naggregation (``target_aggregation_id``) — exactly one, never both.","properties":{"id":{"description":"Result key for this metric (unique within the request).","title":"Id","type":"string"},"target_node_id":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"The node whose full per-period series the metric consumes. Required for every metric except a ``prob`` that targets a prior aggregation via ``target_aggregation_id``. Exactly one of ``target_node_id`` / ``target_aggregation_id`` may be set on a ``prob`` request.","title":"Target Node Id"},"target_aggregation_id":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"For a ``prob`` request only: the id of an aggregation defined EARLIER in this output_request whose per-trial vector (e.g. an ``npv``) is thresholded. Lets ``P(NPV > 0)`` be expressed directly instead of the per-period ``P(value > x)`` you get by targeting a multi-period node. The referenced aggregation must produce a per-trial vector (npv/irr/payback/discounted_payback/cagr/cumsum/terminal_value/mirr).","title":"Target Aggregation Id"},"function":{"description":"One of: npv, irr, payback, discounted_payback, cagr, cumsum, prob, terminal_value, mirr. discounted_payback requires params.rate; npv requires params.rate OR a per-period params.rates curve; prob requires params.op (one of >, >=, <, <=, ==) and params.value; terminal_value requires params.g and params.rate; mirr requires params.finance_rate and params.reinvest_rate.","title":"Function","type":"string"},"params":{"additionalProperties":true,"description":"Function params, e.g. {'rate': 0.1} for npv/discounted_payback, {'op': '>', 'value': 0} for prob, {'g': 0.02, 'rate': 0.1} for terminal_value, {'finance_rate': 0.1, 'reinvest_rate': 0.08} for mirr.","title":"Params","type":"object"}},"required":["id","function"],"title":"AggregationRequest","type":"object"},"CalcNode":{"additionalProperties":false,"description":"A computed node. ``formula`` is OPTIONAL.\n\nA null/empty/whitespace formula is allowed: the node is treated as a\nconstant ``0`` every period/trial (incoming edges are ignored — there is no\nimplicit \"sum of incoming edges\"). ``validate_graph`` surfaces this as a\nWARNING, not an error. To sum incoming edges, write the sum explicitly,\ne.g. ``{e1}+{e2}``; for pass-through use ``{e1}``.","properties":{"id":{"description":"Unique node id within the graph.","title":"Id","type":"string"},"name":{"default":"","description":"Human-readable label.","title":"Name","type":"string"},"formula":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Optional expression over incoming edge ids, e.g. '{e1} * 0.5 + {e2}'. To sum incoming edges write '{e1}+{e2}'; for pass-through use '{e1}'. If omitted/empty the node is treated as a constant 0 (sum-of-incoming is not implied); validate_graph reports this as a warning.","title":"Formula"}},"required":["id"],"title":"CalcNode","type":"object"},"Edge":{"additionalProperties":false,"description":"A directed connection carrying a value from source to target.","properties":{"id":{"description":"Unique edge id within the graph.","title":"Id","type":"string"},"source":{"description":"id of the source node.","title":"Source","type":"string"},"target":{"description":"id of the target node.","title":"Target","type":"string"},"delay":{"default":0,"description":"Periods the value is delayed in transit.","minimum":0,"title":"Delay","type":"integer"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Optional label.","title":"Name"}},"required":["id","source","target"],"title":"Edge","type":"object"},"OutputRequest":{"additionalProperties":false,"description":"Which nodes/edges/periods to include in the result.\n\nEmpty lists mean 'all': all nodes, all edges, all time periods.\n``aggregations`` requests post-simulation series metrics (NPV/IRR/...).","properties":{"nodes":{"items":{"type":"string"},"title":"Nodes","type":"array"},"edges":{"items":{"type":"string"},"title":"Edges","type":"array"},"time_periods":{"items":{"type":"integer"},"title":"Time Periods","type":"array"},"aggregations":{"items":{"$ref":"#/$defs/AggregationRequest"},"title":"Aggregations","type":"array"}},"title":"OutputRequest","type":"object"},"PublicGraph":{"additionalProperties":false,"description":"A complete, validated simulation request — the agent-facing input.\n\nThis is the canonical public shape. Reference integrity (edges point at real\nnodes, output_request references real ids) is validated here so malformed\ngraphs are rejected before they ever reach the engine.","properties":{"contract_version":{"const":"1.0","default":"1.0","description":"Schema version this graph targets.","title":"Contract Version","type":"string"},"root_nodes":{"items":{"$ref":"#/$defs/RootNode"},"title":"Root Nodes","type":"array"},"calc_nodes":{"items":{"$ref":"#/$defs/CalcNode"},"title":"Calc Nodes","type":"array"},"edges":{"items":{"$ref":"#/$defs/Edge"},"title":"Edges","type":"array"},"run_params":{"$ref":"#/$defs/RunParams"}},"required":["run_params"],"title":"PublicGraph","type":"object"},"RootNode":{"additionalProperties":false,"description":"A source node that samples from a probability distribution each period.","properties":{"id":{"description":"Unique node id within the graph.","title":"Id","type":"string"},"name":{"default":"","description":"Human-readable label.","title":"Name","type":"string"},"distribution_type":{"description":"One of the catalog distributions (GET /api/v1/contract).","title":"Distribution Type","type":"string"},"distribution_params":{"additionalProperties":true,"description":"Params for the chosen distribution, e.g. {'mean': 100, 'std': 15}.","title":"Distribution Params","type":"object"},"interval":{"default":1,"description":"Sample every N periods (1 = every period).","minimum":0,"title":"Interval","type":"integer"},"delay":{"default":0,"description":"Periods to wait before the first sample.","minimum":0,"title":"Delay","type":"integer"},"sampling_frequency":{"default":"per_period","description":"'once' draws a single value reused across periods.","enum":["per_period","once"],"title":"Sampling Frequency","type":"string"}},"required":["id","distribution_type","distribution_params"],"title":"RootNode","type":"object"},"RunParams":{"additionalProperties":false,"description":"Top-level run configuration.","properties":{"trials":{"description":"Number of Monte Carlo trials.","exclusiveMinimum":0,"title":"Trials","type":"integer"},"time_periods":{"description":"Number of time periods to simulate.","exclusiveMinimum":0,"title":"Time Periods","type":"integer"},"output_request":{"$ref":"#/$defs/OutputRequest"}},"required":["trials","time_periods"],"title":"RunParams","type":"object"}},"description":"Arguments for ``run_simulation``.","properties":{"graph":{"$ref":"#/$defs/PublicGraph","description":"The full simulation graph (root_nodes, calc_nodes, edges, run_params). See GET /api/v1/contract for valid distribution types."},"seed":{"anyOf":[{"type":"integer"},{"type":"null"}],"default":null,"description":"Optional RNG seed; reproducible only on the small/inline path.","title":"Seed"},"include_histogram":{"default":false,"description":"Include a per-element histogram in each summary element.","title":"Include Histogram","type":"boolean"},"api_key":{"anyOf":[{"type":"string"},{"type":"null"}],"default":null,"description":"Your API key from POST /api/v1/accounts. Debits credits per run. Omit to run anonymously (free, but no credit accounting).","title":"Api Key"}},"required":["graph"],"title":"RunSimulationInput","type":"object"},"output_schema":{"$defs":{"BasicStats":{"description":"Summary statistics for one element at one time period.","properties":{"mean":{"title":"Mean","type":"number"},"std":{"title":"Std","type":"number"},"min":{"title":"Min","type":"number"},"max":{"title":"Max","type":"number"}},"required":["mean","std","min","max"],"title":"BasicStats","type":"object"},"Histogram":{"description":"Optional histogram of the trial values for one element.","properties":{"counts":{"items":{"type":"integer"},"title":"Counts","type":"array"},"bins":{"items":{"type":"number"},"title":"Bins","type":"array"}},"required":["counts","bins"],"title":"Histogram","type":"object"},"Percentiles":{"description":"Distribution percentiles for one element at one time period.","properties":{"p10":{"title":"P10","type":"number"},"p25":{"title":"P25","type":"number"},"p50":{"title":"P50","type":"number"},"p75":{"title":"P75","type":"number"},"p90":{"title":"P90","type":"number"}},"required":["p10","p25","p50","p75","p90"],"title":"Percentiles","type":"object"},"SimulationResult":{"description":"The agent-facing result of a simulation run.\n\n``summary_statistics`` carries one entry per requested time period. Each\nelement is reduced to percentiles + basic stats, with optional histogram and\nraw per-trial arrays.","properties":{"contract_version":{"const":"1.0","default":"1.0","title":"Contract Version","type":"string"},"trials":{"title":"Trials","type":"integer"},"time_periods":{"title":"Time Periods","type":"integer"},"summary_statistics":{"items":{"$ref":"#/$defs/SummaryTimePeriod"},"title":"Summary Statistics","type":"array"},"aggregations":{"additionalProperties":{"$ref":"#/$defs/SummaryElement"},"description":"Post-simulation series metrics, keyed by the request id. Each is the per-trial distribution (percentiles + basic stats) of NPV/IRR/etc. Undefined trials (e.g. non-convergent IRR) are dropped from the distribution, so basic_stats reflect only the finite trials.","title":"Aggregations","type":"object"}},"required":["trials","time_periods"],"title":"SimulationResult","type":"object"},"SummaryElement":{"description":"Summarised result for a single node or edge at a single time period.","properties":{"percentiles":{"$ref":"#/$defs/Percentiles"},"basic_stats":{"$ref":"#/$defs/BasicStats"},"histogram":{"anyOf":[{"$ref":"#/$defs/Histogram"},{"type":"null"}],"default":null},"raw":{"anyOf":[{"items":{"type":"number"},"type":"array"},{"type":"null"}],"default":null,"description":"Per-trial values; only present when raw output is requested.","title":"Raw"}},"required":["percentiles","basic_stats"],"title":"SummaryElement","type":"object"},"SummaryTimePeriod":{"description":"All summarised nodes/edges for a single time period.","properties":{"time_period":{"title":"Time Period","type":"integer"},"nodes":{"additionalProperties":{"$ref":"#/$defs/SummaryElement"},"title":"Nodes","type":"object"},"edges":{"additionalProperties":{"$ref":"#/$defs/SummaryElement"},"title":"Edges","type":"object"}},"required":["time_period"],"title":"SummaryTimePeriod","type":"object"}},"description":"Result of ``run_simulation``.\n\nBoth tiers run to completion and carry the full summarised\n``SimulationResult``. The small tier runs inline; the large tier fans across\nthe local multiprocessing pool — either way the tool blocks until the run\nfinishes (seconds) and returns the real result with aggregations.","properties":{"run_id":{"title":"Run Id","type":"string"},"status":{"description":"Always 'completed' on a 2xx response. A run that fails propagates as an error response (non-2xx + error envelope), not a 'failed' result.","title":"Status","type":"string"},"tier":{"description":"'small' or 'large'.","title":"Tier","type":"string"},"total_trials":{"title":"Total Trials","type":"integer"},"credits_debited":{"default":0,"description":"Credits charged to the account (0 for anonymous).","title":"Credits Debited","type":"integer"},"result":{"$ref":"#/$defs/SimulationResult","description":"The summarised result with aggregations."}},"required":["run_id","status","tier","total_trials","result"],"title":"RunSimulationResult","type":"object"},"hint":"POST 'run_simulation' to this path to run it; GET /mcp/tools lists all tools."}