# Graphs and reports for agents A noodlelab graph is a pipeline of typed nodes saved as `.graph.json`. People open it in the browser editor. Agents write it as JSON, through the MCP tools or directly, and run it headless. It carries its own report: Reporting nodes that render a PDF. ## The loop With the MCP server (`noodlelab mcp`, set up by `noodlelab init-agent`): 1. `guide`, then `list_examples`. Read a similar example with `get_example`, or copy it with `copy_example` and adapt it. 2. `list_nodes` with a query (`"uncertainty"`, `"beam"`, `"csv"`), then `describe_node` for each node you use: its inputs, units, options and defaults. 3. `save_graph` with the whole graph, or build it up step by step with `edit_graph`. Both return `issues`: fix every error (type or unit mismatches, unknown nodes, missing inputs) before running. 4. `run_graph` returns each node's result in short, the checks and requirement verdicts with their margins, errors, the files written (the report's PDF) and the provenance record. 5. `verify` with the graph's path. It passes when every check passed and every requirement was verified. When the user has the editor open and started the agent from its **Agent** panel, every save shows up on their canvas straight away. Without MCP, write the JSON file and use the command line: `noodlelab verify g.graph.json --json` (it checks, runs and audits), `noodlelab run g.graph.json -v` for every node's value, and `noodlelab nodes` to list the node types. ## The format A graph document holds a graph and optional metadata. The editor adds a layout when it saves; omit it, and the editor arranges the nodes itself. ```json { "format": "noodlelab.graph", "version": 1, "name": "drop", "metadata": {"title": "Drop test", "tracked": [{"node": "4", "output": "result", "label": "h/g"}]}, "graph": { "nodes": [ {"id": "1", "type": "requirements.requirements", "inputs": {"text": {"value": "DRP-001 h_over_g <= 0.5 s^2 [Analysis] # Short enough"}}}, {"id": "2", "type": "units.quantity", "title": "Height", "inputs": {"value": {"value": "2.0 m"}}}, {"id": "3", "type": "uncertainty.measurement", "title": "g", "inputs": {"value": {"value": "9.81 ± 0.02 m/s^2"}, "name": {"value": "g"}}}, {"id": "4", "type": "units.quantity_math", "title": "h / g", "inputs": {"operation": {"value": "divide"}, "a": {"link": {"node": "2", "output": "result"}}, "b": {"link": {"node": "3", "output": "result"}}}}, {"id": "5", "type": "requirements.verify_requirement", "inputs": {"requirements": {"link": {"node": "1", "output": "result"}}, "value": {"link": {"node": "4", "output": "result"}}, "id": {"value": "DRP-001"}}} ], "report": [ {"id": "1", "type": "report.new_report", "inputs": {"title": {"value": "Drop test"}}}, {"id": "2", "type": "report.add_value", "inputs": {"report": {"link": {"node": "1", "output": "result"}}, "label": {"value": "h / g"}, "value": {"link": {"node": "4", "output": "result", "tab": "processing"}}}}, {"id": "3", "type": "report.add_compliance_matrix", "inputs": {"report": {"link": {"node": "2", "output": "result"}}, "verifications": {"link": {"node": "5", "output": "log", "tab": "processing"}}}}, {"id": "4", "type": "report.add_run_details", "inputs": {"report": {"link": {"node": "3", "output": "result"}}}}, {"id": "5", "type": "report.render_report", "inputs": {"report": {"link": {"node": "4", "output": "result"}}, "filename": {"value": "drop-test.pdf"}}} ] } } ``` - Every input is `{"value": ...}` or `{"link": {"node", "output"}}`. The MCP tools also accept bare values (`"b": 2`). - Values with units and uncertainty are typed as text: `"2.0 m"`, `"9.81 ± 0.02 m/s^2"`. - A report node links to a processing output with `"tab": "processing"`. Links never go from processing into the report. - `metadata.tracked` lists outputs the editor's Tracked tab follows from run to run. - Subgraphs (`"type": "subgraph"`) and repeat, sweep and optimize zones are described in the docstring of `noodlelab.core.graph`. The examples use all of them. - The JSON schema of the graph document is in [graph.schema.json](graph.schema.json). ## Patterns - **Formulas with units**: `symbolic.expression` (for example `"sqrt(2*h/g)"`), `symbolic.set_value` for each symbol, then `symbolic.evaluate`. `symbolic.to_math` feeds `report.add_equation`, so the report shows the formula it computed. Example 23 does this throughout. - **Constants**: a `units.constant` node (`name`: `pi`, `tau`, `c`, `g0`, `k_B`, `G`... and optional `unit`), or a line in a `symbolic.values` node (`g = g0`, or just `c`). Never retype a constant's digits. A graph's own constants go in `"constants"` next to `"nodes"` (`edit_graph` op `"constant"`), and constants for the whole workspace go in `constants.toml`. Examples 3, 6, 9, 17 and 23 use them. - **Measurements**: `uncertainty.measurement` for `x ± u`, `uncertainty.add_uncertainty` for Type B (from a datasheet or resolution), and `uncertainty.mean_of_repeats` for Type A. `uncertainty.uncertainty_budget` goes into the report, and `uncertainty.monte_carlo` checks the GUM result. - **Requirements**: one `requirements.requirements` node. Use `requirements.requirement_value` to feed a limit into the design, `requirements.verify_requirement` for each check (chain their `log` outputs), and `report.add_compliance_matrix` from the last `log`. - **Checks that are not requirements**: `checks.expect_value`, `checks.expect_in_range`, `checks.expect_table`. - **Trade studies**: a Sweep zone over a design, then `requirements.check_candidates` to keep the rows that meet every requirement. - **The report**: `report.new_report`, then add headings, text, values, figures, equations, requirements and the compliance matrix. Finish with `report.add_run_details` (provenance: who ran it, when, versions and input hashes) and `report.render_report`.