From one TOML file to a verified event loop
2026-06-21 — a worked example: spec → validate → derived branches → generated Rust → proof the generated code matches the hand-written reference.
The previous note argued the thesis: as
agents write analysis code faster than anyone can review it, correctness has to
move from soft guidance into the compiler. This post is the thesis made
concrete — a real, end-to-end run you can reproduce from the repo today. We
start from a physics-facing spec, and at every step the tool either derives the
next artifact or rejects an inconsistent one. Nothing here is mocked; every
block below is real output from cargo-built binaries in the tree.
1. The spec — what a physicist writes
A muon control region, the whole thing, in TOML
(crates/nano-spec/examples/muon.toml):
[analysis]
name = "muon_demo"
year = "Run2018"
[objects.good_muon]
source = "Muon"
cuts = ["pt > 30 GeV", "abs(eta) < 2.4"]
[regions.signal]
require = ["count(good_muon) >= 1"]
[[outputs]]
name = "n_good_muon"
expr = "count(good_muon)"
[[outputs]]
name = "lead_muon_pt"
expr = "leading(good_muon).pt"
This is the only thing a physicist edits. It names an object (good_muon,
derived from the Muon collection with two cuts), a region, and two outputs.
Note pt > 30 GeV — the unit is part of the spec, not a comment.
2. Validate — the spec is checked before any code runs
$ nano validate crates/nano-spec/examples/muon.toml
OK validate crates/nano-spec/examples/muon.toml
analysis: muon_demo (Run2018)
catalogue: v9
objects: good_muon:Muon
regions: signal
outputs: n_good_muon, lead_muon_pt
read_branches: nMuon U32, Muon_eta VecF32, Muon_pt VecF32
Validation resolves the spec against the NanoAODv9 branch catalogue and proves the things review usually has to catch by eye:
- every branch the cuts and outputs touch exists for this era with the right
type (
Muon_ptis aVecF32,nMuonis aU32); - every referenced object is defined; every region requirement is well-formed;
- units are present where the grammar requires them.
A typo like Muon_ptt, a Run2018 branch that only exists in v12, or
pt > 30 without a unit doesn't produce a wrong number downstream — it fails
right here, with a precise error, before a single event is read.
3. Derive the read set — never "read everything just in case"
The exact set of input branches to bind is computed from the spec, not written by hand:
$ nano branches crates/nano-spec/examples/muon.toml
nMuon U32
Muon_eta VecF32
Muon_pt VecF32
$ nano branches crates/nano-spec/examples/muon.toml --json
{
"command": "branches",
"status": "ok",
"spec_path": "crates/nano-spec/examples/muon.toml",
"catalogue_version": "v9",
"branches": [
{ "name": "nMuon", "branch_type": "U32" },
{ "name": "Muon_eta", "branch_type": "VecF32" },
{ "name": "Muon_pt", "branch_type": "VecF32" }
]
}
Three branches — exactly the ones the cuts (pt, eta) and the counter
(nMuon) require, and nothing else. This is what the streaming reader binds, so
the I/O cost is a function of the spec, not of the file's 1000+ branches. The
--json form is the same fact in machine-readable shape — this is what the MCP
server and an orchestrating agent consume.
4. Codegen — the spec becomes Rust
$ nano codegen crates/nano-spec/examples/muon.toml
// @generated by nano-spec from analysis `muon_demo`. Do not edit by hand.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct GenRow {
pub n_good_muon: u32,
pub lead_muon_pt: f32,
}
pub struct GeneratedProducer;
impl GeneratedProducer {
pub fn analyze(event: &nano_core::Event) -> nano_core::Result<Option<GenRow>> {
let good_muon_collection = event.collection("Muon")?;
let mut n_good_muon = 0_u32;
let mut lead_muon_pt: Option<f32> = None;
for good_muon_item in good_muon_collection.iter() {
let good_muon_eta = good_muon_item.get::<f32>("eta")?;
let good_muon_pt = good_muon_item.get::<f32>("pt")?;
if (good_muon_pt > 30.0_f32) && (good_muon_eta.abs() < 2.4_f32) {
n_good_muon += 1;
lead_muon_pt = Some(
lead_muon_pt.map_or(good_muon_pt, |lead| lead.max(good_muon_pt)),
);
}
}
if !(n_good_muon >= 1_u32) {
return Ok(None);
}
let Some(lead_muon_pt) = lead_muon_pt else {
return Ok(None);
};
Ok(Some(GenRow {
n_good_muon: n_good_muon,
lead_muon_pt: lead_muon_pt,
}))
}
}
This is the generated event loop, verbatim. Read it as a physicist: the cut
order matches the spec, the >30 and <2.4 thresholds are right there, the
region requirement count(good_muon) >= 1 is the return Ok(None) veto, and the
two outputs are the struct fields. It is readable on purpose — the whole design
contract is "agents (here, the generator) write; humans review the physics." And
because it is generated from the validated plan, it can only read branches that
validation already proved exist with the right type: get::<f32>("pt") cannot be
wired to a branch that isn't there.
5. The guarantee — generated code equals the hand-written reference
The claim that codegen is faithful isn't a promise in prose — it's a test in CI.
nano-gen-demo generates the producer at build time (build.rs runs the exact
same validate + generate_producer_source), then checks it against the
hand-written MuonProducer on synthetic events spanning the edge cases (a muon
just below 30 GeV, one at |η| = 2.39, an empty event):
let generated = GeneratedProducer::analyze(&event).unwrap().map(|r| (r.n_good_muon, r.lead_muon_pt));
let handwritten = MuonProducer::analyze(&event) .unwrap().map(|r| (r.n_good_muon, r.lead_muon_pt));
assert_eq!(generated, handwritten, "entry {entry}");
$ cargo test -p nano-gen-demo
test generated_muon_producer_matches_handwritten_producer_on_synthetic_events ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
So the chain closes: spec → validated plan → generated kernel → proven equivalent to the reference physicists trust. The human reviews the spec and the reference once; the generator reproduces it; the test guarantees the two agree.
The workflow, in one line per step
nano validate spec.toml # reject inconsistent physics before any I/O
nano branches spec.toml # derive the exact read set (nothing "just in case")
nano codegen spec.toml # emit a readable, typed event loop
cargo test # prove the generated loop == the trusted reference
Every arrow is mechanical. A physicist's edits live in the TOML; an agent's
edits live in code that has to compile against a validated plan and match a
golden reference to survive. The same four operations are exposed over the MCP
protocol (docs/agent-interface.md), so an orchestrating agent drives this exact
loop through typed tools whose effects are gated by the validator — the
orchestrator proposes, the compiler disposes.
Reproduce it / watch it
Everything above is one cargo build away:
$ cargo build -p nano-cli
$ nano validate crates/nano-spec/examples/muon.toml
$ nano branches crates/nano-spec/examples/muon.toml
$ nano codegen crates/nano-spec/examples/muon.toml
$ cargo test -p nano-gen-demo
For a terminal recording of the full run — including a wrongly-defined
analysis getting blocked by the validator — see the companion
screencast post. It's a reproducible
asciinema cast (asciinema rec docs/site/demo.cast -c "bash
scripts/demo_session.sh") driven by the live binaries, no hand-edited frames —
same spirit as everything else here. Next in this series: declaring an ML tagger
in the same spec and watching its score become a typed column the region cut can
use — codegen for inference.