# Neumann Agent Quickstart

This page is a fully self-contained cold-start guide for AI agents. Follow it
end-to-end and you will be running Monte Carlo simulations in under 60 seconds.

> **Base URL:** this page was served from `https://neumann.krobar.ai`. Use it as your base URL;
> every path below is host-relative. Accounts are environment-scoped (localhost /
> staging / prod) — don't mix them.

## What Neumann is

Neumann runs Monte Carlo simulations over a **graph**:

- **root nodes** sample a value from a probability distribution each time period;
- **calc nodes** compute a value from their incoming edges via a formula;
- the engine runs thousands of trials and returns each requested node/edge as a
  distribution summarised into percentiles + basic stats (optionally a histogram).

**Use Neumann when you need** the distribution of an uncertain quantity that is a
function of several other uncertain quantities — revenue models, cost roll-ups,
risk analysis, capacity planning — anywhere a point estimate hides the spread.

## Authentication

Three ways to authenticate — and one common mistake to avoid.

1. **Raw API key** (recommended for agents) — `POST /api/v1/accounts` returns a
   raw `api_key`. Pass it as the `api_key` **MCP tool argument** or the
   **`X-API-Key` REST header**. The raw `api_key` is NOT a bearer token: putting
   it in an `Authorization: Bearer` header will be rejected.
2. **Opaque bearer** — only tokens prefixed `nmn_oat_` / `nmn_pat_` are valid
   `Authorization: Bearer <token>` values (used on the MCP endpoint). Mint one
   with `POST https://neumann.krobar.ai/api/v1/tokens`; it is the only thing that belongs in a
   `Bearer` header.
3. **Anonymous** — omit credentials entirely. Free, but no credit accounting and
   no saved runs tied to an account. Good for a first experiment before signup.

> **Bearer vs api_key, in one line:** raw signup `api_key` → `X-API-Key` /
> `api_key` arg; opaque `nmn_oat_`/`nmn_pat_` token → `Authorization: Bearer`.
> Never the reverse.

### Cold-start signup

```
curl -X POST https://neumann.krobar.ai/api/v1/accounts
```

Response (`201`):

```json
{
  "account_id": "…",
  "api_key": "<raw key — shown only once>",
  "credit_balance": 5,
  "created_at": "…"
}
```

**Save the `api_key` immediately** — it is hashed server-side and never shown
again. Pass it on every subsequent call.

## Running a simulation

### Option A — MCP (MCP-native clients)

Connect to the Streamable-HTTP endpoint and call `run_simulation`:

```
claude mcp add neumann --transport http https://neumann.krobar.ai/mcp
```

Then call the tool with a `graph` and (optionally) your `api_key`:

```json
{
  "graph": {
    "contract_version": "1.0",
    "root_nodes": [
      {
        "id": "units_sold",
        "name": "Units sold per month",
        "distribution_type": "normal",
        "distribution_params": {
          "mean": 1000.0,
          "std": 150.0
        }
      },
      {
        "id": "price_per_unit",
        "name": "Price per unit (USD)",
        "distribution_type": "triangle",
        "distribution_params": {
          "min_val": 18.0,
          "mode_val": 25.0,
          "max_val": 40.0
        }
      }
    ],
    "calc_nodes": [
      {
        "id": "revenue",
        "name": "Monthly revenue (USD)",
        "formula": "{e_units} * {e_price}"
      }
    ],
    "edges": [
      {
        "id": "e_units",
        "source": "units_sold",
        "target": "revenue"
      },
      {
        "id": "e_price",
        "source": "price_per_unit",
        "target": "revenue"
      }
    ],
    "run_params": {
      "trials": 5000,
      "time_periods": 1
    }
  },
  "seed": 42
}
```

A body-bearing `POST https://neumann.krobar.ai/mcp` (no trailing slash) is handled with a `308`
to `/mcp/` that preserves the method and JSON-RPC body, so you can POST to either
`/mcp` or `/mcp/`.

### Option B — REST

The same operation over plain HTTP. Either invoke the tool twin directly:

```
curl -X POST https://neumann.krobar.ai/mcp/tools/run_simulation \
  -H "Content-Type: application/json" \
  -H "X-API-Key: <your api_key>" \
  -d '{
  "graph": {
    "contract_version": "1.0",
    "root_nodes": [
      {
        "id": "units_sold",
        "name": "Units sold per month",
        "distribution_type": "normal",
        "distribution_params": {
          "mean": 1000.0,
          "std": 150.0
        }
      },
      {
        "id": "price_per_unit",
        "name": "Price per unit (USD)",
        "distribution_type": "triangle",
        "distribution_params": {
          "min_val": 18.0,
          "mode_val": 25.0,
          "max_val": 40.0
        }
      }
    ],
    "calc_nodes": [
      {
        "id": "revenue",
        "name": "Monthly revenue (USD)",
        "formula": "{e_units} * {e_price}"
      }
    ],
    "edges": [
      {
        "id": "e_units",
        "source": "units_sold",
        "target": "revenue"
      },
      {
        "id": "e_price",
        "source": "price_per_unit",
        "target": "revenue"
      }
    ],
    "run_params": {
      "trials": 5000,
      "time_periods": 1
    }
  },
  "seed": 42
}'
```

…or use the dedicated run endpoint `POST https://neumann.krobar.ai/api/v1/runs` (same `graph`
shape; set `"stream": true` to receive `text/event-stream` progress for large
runs).

### The example graph

The body above is a tiny monthly-revenue business case:

- `units_sold` — a **normal** distribution (mean 1000, std 150);
- `price_per_unit` — a **triangle** distribution (min 18, mode 25, max 40 USD);
- `revenue` — a calc node, `units_sold * price_per_unit`.

A small run returns the full summarised result inline. Expect `revenue.mean`
around 27,000–28,000 USD with a spread captured in the percentiles.

## Tools

### `run_simulation`

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.

### `oat_sensitivity`

Run a one-at-a-time (OAT) tornado sensitivity analysis: for each scalar root node, sweep it over a set of quantiles (others held at baseline) and measure the target node's response. Returns a tornado-ranked list (response_curve, elasticity, rank). Debits one credit per analysis for authenticated callers.

### `scenario_analysis`

Best/median/worst scenario summary for a target node across trials. Runs the simulation once (reusing the run pipeline) and returns, per requested percentile (default worst p0 / median p50 / best p100), the target node's output value plus a best-effort per-root driving input. Debits one credit per analysis for authenticated callers.

### `validate_graph`

Validate a graph with the engine's ModularGraphValidator and return a structured list of issues (rule, severity, affected elements).

### `estimate`

Pre-flight runtime/memory estimate for a graph without running it: estimated peak memory, complexity, the tier it would run as, and the credit cost.

### `solve_distribution_parameters`

Solve distribution parameters from a target min/max range and confidence so a human-style estimate becomes engine-ready distribution_params.

### `get_results`

Fetch the status and summary for a previously-submitted run.

### `feedback`

Send feedback, a feature request, or a bug report to the Neumann team. Emails the submission to the team and CCs your sender_email so you keep a copy. Optionally pass api_key to attach your account id for context; anonymous submissions are allowed.


List the live tool descriptors (name, description, input/output JSON schema) with
`GET https://neumann.krobar.ai/mcp/tools`. The MCP `tools/list` and these REST descriptors are
single-sourced — they cannot drift.

## The graph contract

Fetch the machine-readable contract — version marker + the full distribution
catalog with each distribution's parameters — at:

```
curl https://neumann.krobar.ai/api/v1/contract
```

Validate and size a graph for **free** before spending credits:

- `validate_graph` — structural + simulation-readiness check, returns issues.
- `estimate` — peak memory, complexity, the tier it would run as, and the credit
  cost.
- `solve_distribution_parameters` — turn a "between X and Y, 90% confident"
  estimate into engine-ready `distribution_params`.

### Time periods are 1-based in formulas (the #1 gotcha)

`{time_period}` inside ANY per-period formula is **1-based** — the first period
is `{time_period} == 1`. The post-simulation aggregation series index
(`npv`/`irr`/`payback`/…) is **0-based** (first period undiscounted). These two
conventions coexist; don't mix them on one series.

**Silent-zero trap:** a mask written `{time_period} == 0` NEVER matches a real
period, so the term it gates silently contributes 0 for the whole run with no
error. Use `{time_period} == 1` to target the first period.

Worked time-phased example — a 3-period model where revenue launches in period 2:

```
"calc_nodes": [
  { "id": "rev", "formula": "{e_base} * if({time_period} >= 2, 1, 0)" }
]
```

Period 1 → 0 (pre-launch); periods 2–3 → `e_base`. Writing
`if({time_period} == 0, 0, 1)` to "skip the first period" would be inert: there
is no period 0, so every period is multiplied by 1.

### Formula functions & aggregations

Per-period formula functions and post-simulation aggregation metrics. The full
machine-readable catalog (with `kind`, description, and arity per function) is the
`formula_functions` block of `GET https://neumann.krobar.ai/api/v1/contract`:

- `abs` (formula) *(args: 1)* — Absolute value.
- `and` (formula) *(args: 1..N)* — Logical AND of its arguments.
- `average` (formula) *(args: 1..N)* — Arithmetic mean of its arguments.
- `binomial` (formula) *(args: 2)* — Sample a binomial outcome.
- `ceil` (formula) *(args: 1)* — Round up to the nearest integer.
- `discount` (formula) *(args: (rate, n))* — Discount factor 1 / (1 + rate) ** n. n is the 1-based {time_period}. Pairs with the 1-based per-period convention; the post-sim 'npv' aggregation is 0-based — don't combine them on one series.
- `exp` (formula) *(args: 1)* — e raised to the argument.
- `floor` (formula) *(args: 1)* — Round down to the nearest integer.
- `if` (formula) *(args: 3)* — Ternary: if(cond, then, else).
- `index` (formula) *(args: 2)* — Element at a position in an array argument.
- `length` (formula) *(args: 1)* — Number of elements in an array argument.
- `ln` (formula) *(args: 1)* — Natural logarithm.
- `log` (formula) *(args: 1)* — Base-10 logarithm.
- `max` (formula) *(args: 1..N)* — Largest of its arguments.
- `mean` (formula) *(args: 1..N)* — Arithmetic mean of its arguments (alias of average).
- `min` (formula) *(args: 1..N)* — Smallest of its arguments.
- `not` (formula) *(args: 1)* — Logical negation.
- `or` (formula) *(args: 1..N)* — Logical OR of its arguments.
- `pow` (formula) *(args: 2)* — Raise the first argument to the power of the second.
- `product` (formula) *(args: 1..N)* — Product of its arguments.
- `pv` (formula) *(args: (rate, value, period))* — Present value of a cash flow discounted by 'rate' over 'period' periods: value / (1 + rate) ** period. period is the 1-based {time_period}; the first period is discounted by ^1. Do NOT also request an aggregation 'npv' on the same series (it is 0-based and would double-shift period 1).
- `round` (formula) *(args: 1..2)* — Round to the nearest integer (or to N decimals).
- `sqrt` (formula) *(args: 1)* — Square root.
- `sum` (formula) *(args: 1..N)* — Sum of its arguments.
- `cagr` (aggregation) *(args: series)* — Compound annual growth rate from the first to the last period of the series.
- `cumsum` (aggregation) *(args: series)* — Terminal value of the cumulative sum across periods (sum_t series[t]).
- `discounted_payback` (aggregation) *(args: series, rate)* — Like payback, but on the rate-discounted cumulative series. Requires a 'rate' param.
- `irr` (aggregation) *(args: series)* — Internal rate of return per trial (the per-period rate where NPV is zero; series index 0-based).
- `mirr` (aggregation) — Aggregation metric 'mirr'.
- `npv` (aggregation) *(args: series, rate)* — Net present value per trial: sum_t series[t] / (1+rate)**t with a 0-BASED series index (period 0 undiscounted). Requires a 'rate' param. Model an up-front outlay as a negative period-0 cash flow.
- `payback` (aggregation) *(args: series)* — Number of periods until the cumulative (undiscounted) series turns non-negative.
- `prob` (aggregation) — Aggregation metric 'prob'.
- `terminal_value` (aggregation) — Aggregation metric 'terminal_value'.

## Credit model

- Starter credits on signup. A **small** run costs 1 credit; **large** runs cost
  more (`estimate` reports the exact cost and tier up front).
- Anonymous runs are free but unaccounted.
- `402 insufficient_credits` when you run out. Check balance + history at
  `GET https://neumann.krobar.ai/api/v1/accounts/me/ledger`.

## Discovery artifacts

- [`/llms.txt`](https://neumann.krobar.ai/llms.txt) — the short cold-start guide
- [`/.well-known/mcp.json`](https://neumann.krobar.ai/.well-known/mcp.json) — MCP descriptor (machine-readable)
- [`/server.json`](https://neumann.krobar.ai/server.json) — MCP-registry manifest
- [`/api/v1/contract`](https://neumann.krobar.ai/api/v1/contract) — distribution + formula-function catalogs
- [`/api/v1/tokens`](https://neumann.krobar.ai/api/v1/tokens) — mint an opaque `nmn_oat_`/`nmn_pat_` bearer (POST)
- [`/openapi.json`](https://neumann.krobar.ai/openapi.json) — OpenAPI schema
- [`/docs`](https://neumann.krobar.ai/docs) — Swagger UI
