Recipes and provenance¶
A number without a method is not a result. HelioAI's answer to "how did you get that?" is recipes: reviewed scientific scripts that each carry the paper they come from, and that follow the value all the way into the exported notebook.
What a recipe is¶
A plain Python module in helioai/data/recipes/, with a comment header:
# name: mvab
# description: Minimum variance analysis of the magnetic field (MVAB).
# inputs: B (nT, N×3)
# outputs: n_min, n_int, n_max, eigenvalue ratios
# reference: Sonnerup & Scheible (1998), ISSI SR-001, Ch. 8
The agent finds a recipe with list_recipes, whose entries carry run_with — the exact
call, with the recipe's own input names — and runs it with
run_recipe("theta_bn", inputs={...}): the inputs are bound first — each value a Python
expression evaluated in the sandbox, load_data() included — then the recipe's source
follows verbatim, and its run block reads what was bound. Every physics recipe runs that
way; fill_values, a library for scripts that run outside HelioAI, takes one call
applying its function, and any recipe's functions can be called that way too. A run that
fails or produces nothing comes back with the recipe's notice (recipe_notice).
load_recipe("mvab") returns the source and the metadata, to read the method. The
recipe's own export() calls produce the numbers, and the run is recorded as a use of the
recipe with its reference. Because the recipe is a
real script rather than a prompt instruction, the model cannot quietly reimplement the
method its own way — and through run_recipe it does not get the chance to.
The shipped recipes¶
| Recipe | Method |
|---|---|
theta_bn |
Shock normal angle via the coplanarity theorem |
mvab |
Minimum variance analysis of B |
walen_test |
Walén test for rotational discontinuities |
rankine_hugoniot |
MHD jump conditions across a shock |
shock_timing_2sc |
Two-spacecraft shock timing against a normal from theta_bn/mvab |
pressure_balance |
Magnetopause pressure balance |
pitch_angle_dist |
Pitch angle distribution |
superposed_epoch |
Superposed epoch (Chree) analysis |
sep_onset_poisson_cusum |
SEP onset via Poisson-CUSUM |
fill_values |
Fill-value blanking for scripts that run outside HelioAI, where load_data() has not done it |
solar_mach |
Parker spiral connectivity (needs the solarmach extra) |
Why "recipe-first" is enforced¶
Left alone, a capable model will happily recompute θ_Bn from first principles inline. The
arithmetic is often right and the provenance is always gone. So the data_analyst skill
carries a hard rule and a task→recipe table: if a recipe exists for the task, load it.
This came directly from user feedback. During a demo, θ_Bn was reported "via recipes" while an intermediate MVAB had in fact been done inline, with no trace of the method or its source. The fix was not a better prompt — it was making provenance a tracked artifact.
The rule is checked, not trusted. When a run exports values, the answer gets a
ℹ️ RECIPE CHECK note if any of three things is true:
- never loaded — the exported names look like a recipe's computation and
load_recipewas never called for it; - loaded, outputs missing — the recipe was loaded, but none of its declared
# outputs:were exported; - loaded, never called — the recipe was loaded, and no
run_pythonafter it calls one of the recipe's functions. This is the case the other two miss: on a live run the analyst loadedtheta_bn, rewrote the coplanarity formula by hand with a poorly chosen averaging window, and exported the result astheta_bn— right name, wrong number.
The note annotates; it never blocks, and it is not proof either way. Read it as "check
these numbers against the recipe before quoting them". A recipe the turn ran through
run_recipe is exempt from all three: the shipped source ran, verbatim, on the inputs the
tool bound — the very thing the three signals try to establish from the outside.
Methods you write inline¶
Not everything has a recipe. For a one-off method, the sandbox provides:
document_method(
name="Shue et al. magnetopause model",
reference="Shue et al. (1997), JGR 102, 9497",
method="r = r0 * (2 / (1 + cos(theta)))**alpha",
)
This produces the same provenance card as a recipe, and lands in the same export section. The point is that every derived quantity can be traced, not only the pre-packaged ones.
Where provenance surfaces¶
- Live — a 📐 chip in the web UI and Jupyter, naming the recipe or method.
- In prose — the agent adds a line to its answer saying how the value was obtained.
- In the export — a Methods & data acknowledgements cell listing every recipe and reference used in the session, assembled by scanning the tool calls.
The double signal is deliberate: the artifact chip is precise, and the prose line is what a reader actually notices.
Where each number came from¶
Every export() call is recorded in <session>/data/provenance.json: the value, its unit,
the script that produced it, and which agent ran it. When the agent finishes an answer, the
numbers it states are checked against that ledger and a 📐 provenance line reports how many
were traced, contradicted, derived or unsourced — with the flagged ones listed underneath.
Pass the unit when you export, or a compression ratio of 2.53 and a field of 2.53 nT are the same number to the checker:
When the agent closes with final_answer and names its numbers — the export each one was
computed as, or literature / asserted for a value it did not compute — a second line,
⚖ claims, judges them by name rather than by finding them in the prose: 57.2 deg
states a recorded 57.16 deg, 0.0101 uT states 10.077 nT, and any run of the session
that produced the value sources it. A named scalar the session computed that holds another
value is reported as contradicted, with both numbers; a claim marked literature or
asserted is listed as unsourced and never contradicted.
Three things it deliberately does not do:
- It annotates, it never blocks. A number that is absent from the ledger is flagged, not removed, and the answer is delivered either way. An approximate numeric match is not a good enough judge to overrule a scientist.
- It certifies provenance, not correctness. A compression ratio computed over the wrong averaging window is recorded, traceable, and wrong. Nothing here will say so.
- It only sees numbers. "ACE is sunward of Wind" was published four times against the positions that had just been downloaded; a claim with no number in it is out of reach.
Adding your own¶
Drop a .py file in helioai/data/recipes/ with the header above. The # reference: line
is required — a recipe without a citation defeats the purpose, and the test suite enforces
that every shipped recipe has one.
Point HELIOAI_RECIPES_DIR at your own directory to use a private set instead.
Recipes are not linted
They are executed in the sandbox against an injected namespace (export, the loaded
parameters), so they legitimately reference names that do not exist at import time and
are excluded from ruff.
Citing the data too¶
Recipes cover the method. The data has its own obligations — AMDA, CDAWeb and CSA each ask to be acknowledged, and catalogs like HELIO4CAST/ICMECAT require citing Möstl et al. HelioAI's export collects these alongside the method references so the acknowledgements section is complete rather than half-remembered.