Graphs and reports for agents

A noodlelab graph is a pipeline of typed nodes saved as <name>.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.

{
  "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.

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.