robotruth Robot CI for learned policies
Check the code

Open source, Apache 2.0  /  v0.1.6 on PyPI  /  updated 21 Sep 2026

Find out whether a policychange is actually real.

robotruth is a Python library and command line tool for testing learned robot policies. It checks that the policy you deployed is the one you evaluated, puts a confidence interval on every number it prints, tells you when the robot itself has drifted, and watches a running policy for faults. It sits next to your stack and reads files. It is not a benchmark, a leaderboard or a model.

See the measurements Read the source pip install robotruth
01

The problem it was built for

A robot lab runs fifty trials, gets forty successes, and writes down eighty percent. That number is doing far more work than it can carry. The interval around it is roughly twenty points wide, so the next checkpoint can look better without being better. Change the camera angle and the same policy moves further than most model upgrades do. Ship the same weights with the wrong normalization file and a working policy stops working, with nothing in the logs but a warning.

None of this is a secret. It is in the literature, and it keeps happening anyway, because there is no standard layer that checks it.

FindingSource
Of 13 audited real robot VLA papers, 0 report a confidence intervalPhAIL, arXiv 2605.29710
A 50 trial success rate carries a 20 to 30 point wide 95% intervalToyota Research Institute, LBM study
Moving a camera or a tote shifts task completion by 22 pointsPhAIL
Same weights, different normalization metadata: 28/28 drops to 2/28arXiv 2606.03724
Same policy, second identical arm: 98% drops to 18%SPACE, arXiv 2606.24049
Video only success judges cap at 0.77 balanced accuracyFailBench, arXiv 2609.03611
Published findings, not opinions. The through line is that an evaluation of one configuration does not certify another, and a rate without an interval is not a result.
02

What it does

Six modules, each answering one question you can act on. They work on their own or together, and everything they produce is a Markdown and HTML report.

Contractrobotruth contract

Is the policy you evaluated the policy you deployed? Weights, normalizer statistics, action semantics, control rate, cameras and embodiment go into a manifest that is hashed and compared. The comparison fails closed, so anything it cannot prove is reported as unverified rather than assumed fine.

Statisticsrobotruth stats

Is B really better than A? Intervals on every rate, paired designs, sequential tests you are allowed to peek at, censored time to success, Bradley Terry rankings, and an audit that takes success counts someone else published and tells you which comparisons their own numbers can support.

Episodesrobotruth episodes

What actually happened in each rollout? One record per episode with outcome, interventions, failure class and provenance, plus the fleet numbers almost nobody reports: interventions per hour, mean time between interventions, autonomous fraction. Reads LeRobot datasets, writes MCAP for Foxglove.

Fingerprintrobotruth fingerprint

Did the cell or the arm change under you? Lighting and camera geometry from a single frame, and per joint lag, backlash, offset and gain from one excitation run. When you have a pile of fingerprints from a working cell instead of a known tolerance, the drift threshold is calibrated from them rather than guessed.

Judgerobotruth judge

Did the episode succeed? Motion features fused with a vision language model, then calibrated so that it abstains instead of guessing when it is not confident. It reports how often it answers, not only how often it is right. The open weight backend runs on your own GPU, so no API key is needed.

Guardrobotruth guard

Has the robot stopped behaving like itself right now? Per step scores with time uniform thresholds calibrated on your own successful rollouts, emitting ok, slow, handover or stop. It catches execution faults such as a stalled arm or erratic control. It does not catch task failures, and section 04 says so with numbers.

Companion package, at an earlier stage

Policy CI policyci makes the runs the six modules measure. It puts two policies through the same fixed set of scenes, compares them scene by scene, works out how much of the difference is ordinary run to run variation, and names the region of the workspace where the failures gather. It is a prototype and the repository says so plainly. Its first real finding is in section 04.

WHAT YOU ALREADY HAVE Checkpoint directory Episode logs, LeRobot data A camera frame of the cell A policy running live ROBOTRUTH ContractStatisticsEpisodes FingerprintJudgeGuard Report layer Refuses to print a rate without its interval, writes Markdown and styled HTML. WHAT YOU GET BACK A pass or fail you can gate on non zero exit code on any fatal mismatch Numbers with intervals and a verdict that says when it is still noise Alerts while the robot is moving ok, slow, handover, stop
Figure 1. Nothing asks you to change how you train or where data lives. The modules read files your stack already writes.
03

How it works

Three rules run through all of it, and they are the reason the numbers are worth anything.

Fail closed, not open

If the contract check cannot prove the deployed policy matches the evaluated one, it does not pass you with a warning. It returns a non zero exit code and names the field it could not verify. Some things are not written to disk anywhere, like which of several shipped normalizer statistics is actually live, so those are listed as unverified and you supply them once.

No rate without an interval

The report layer will not print a bare percentage. Every rate carries a Wilson interval, every comparison carries a paired difference, and when a result is still inside the noise it says so and tells you how many more trials would settle it. This is a small rule that changes what a report can be used for.

Abstain instead of guessing

The judge is conformally calibrated, which means it is allowed to answer only when it can meet a stated error rate, and it hands the rest to a human. Coverage is reported next to accuracy, because a judge that answers everything at sixty percent is worse than useless to a team that has to trust it.

EVERY CONTROL STEP Robot state andthe action chunk HEAD A Distance from nominal Chunk to chunk agreement Action magnitude and spread averaged into one score HEAD B Motion over a sliding window the stall detector, kept separate so it cannot be outvoted Own threshold half the alarm budget Own threshold half the alarm budget Either one alarms ok / slow / handover / stop
Figure 2. The guard, and the one design decision that mattered. When the stall detector was averaged in with the other three scores it caught 1 frozen arm in 10, because a frozen action stream looks perfectly normal to a distance check and perfectly consistent to an agreement check, so the one detector that could see the problem was outvoted. Giving it its own threshold and alarming when either head fires took that to 10 in 10, with the false alarm budget split between the two so the guarantee still holds.
04

What it has actually measured

Everything below was produced by the tool itself on public data and on a live policy, on rented GPUs. The bundles and the scripts that made them are in the repository, so none of it has to be taken on trust.

54
Public datasets ingested
zero errors
0/338
Guard false alarms
[0.000, 0.011]
0.921
Judge balanced accuracy
[0.617, 0.972]
361/362
Real execution faults caught
within 0.2 s
10
Detectors tested, all blind to task failure
AUROC 0.31 to 0.57

Module by module

ModuleWhat was measuredResult
ContractFive public checkpoints, ACT through GR00TCaught a loader silently dropping normalizer statistics, which took a working policy from 83% to 0% with only a log warning
StatisticsAudit of published success countsOf 5 comparisons, 2 hold up and 3 are inside the noise
EpisodesA real assisted autonomy deployment log129.6 interventions per hour [120.9, 138.7]
FingerprintFive physical SO-100 and SO-101 armsTold apart from their logs alone, backlash 0.107 to 0.478, lag 101 to 135 ms
FingerprintCalibrated drift threshold, 270 clean episodes3 false alarms, against 23 for the rule of thumb it replaced, with no loss of sensitivity
Judge323 labelled real episodes, 20 tasks0.921 balanced accuracy [0.617, 0.972] on the quarter it chooses to answer
Guard338 held out successful episodes0 false alarms [0.000, 0.011]
GuardStalls and erratic control on three real robot corpora361 of 362 caught, within 0.1 to 0.2 s
GuardFaults that arrive gradually over up to 4 sStill caught every time, with the delay tracking how long the fault takes to grow
GuardNine other detectors from the literature, same episodesAll at chance on real task failures (0.31 to 0.57 AUROC), all good on injected faults
Every interval is 95% Wilson. Live policy numbers come from an ACT policy in simulation; the real robot numbers come from recorded logs of physical arms.
WHAT MORE EPISODES BUY YOU 00.05 0.100.150.20 true false alarm rate the evidence still allows target, 5% Before 18 episodes up to 17.6% After 338 episodes up to 1.1%
Figure 3. Both runs saw zero false alarms. With 18 episodes the true rate could still be one in six; with 338 it sits under one percent.
A correction we published about our own tool

The guard used to be described as catching a failing policy. Testing it against real robot logs showed it catching 5 of 145 real failures on one corpus and 0 of 58 on another, and a threshold free check confirmed there was no signal to recover rather than a setting to tune. A robot that fails a task usually keeps moving normally, so the motion stream does not give it away. What the guard reliably catches is the robot no longer behaving like itself, and the wording was changed to match. The task failure question belongs to the judge, and miscalibration belongs to the fingerprint, which spots an offset eight times smaller than the guard needs.

We checked whether this is just our tool

A limitation in our own monitor is one thing. A limitation in the approach is another, and it is the more useful finding, so we tested it. We took nine other detectors covering the families used in the recent literature, including the cheap half of VLA-FAIL, the distance and density signals from FAIL-Detect, and standard anomaly detectors, then fitted each one on successful episodes only and scored it two ways on exactly the same held-out episodes.

The measure is AUROC, which is the chance that a failed episode scores higher than a successful one. It needs no threshold, so a poor score cannot be blamed on bad tuning. 0.5 is a coin flip.

DetectorReal task failuresStallOffsetErratic
Chunk consistency (VLA-FAIL)0.51 / 0.310.40 / 0.260.99 / 1.000.98 / 0.98
Mahalanobis distance0.53 / 0.400.99 / 0.990.99 / 1.000.98 / 0.99
Action variance (FAIL-Detect)0.50 / 0.310.39 / 0.260.50 / 0.500.98 / 0.98
Density, Gaussian mixture0.51 / 0.390.51 / 0.470.95 / 0.770.86 / 0.63
PCA reconstruction0.53 / 0.360.99 / 0.991.00 / 1.000.98 / 0.99
Isolation forest0.49 / 0.470.43 / 0.450.72 / 0.650.60 / 0.55
One-class SVM0.52 / 0.510.90 / 0.540.99 / 0.810.86 / 0.61
Random network distillation0.54 / 0.430.99 / 0.890.99 / 0.980.93 / 0.81
GRU prediction error0.56 / 0.570.92 / 0.790.96 / 0.960.75 / 0.70
Ours0.52 / 0.461.00 / 1.001.00 / 1.001.00 / 1.00
AUROC on BotFails and DROID, written as BotFails / DROID. Every detector is fitted on successful episodes only. The fault columns are injected into the same held-out successful episodes, so the contrast is not caused by different data.

Every one of them is at chance on the failures the robots actually had, and every one of them catches injected faults. Methods that share almost nothing in how they work, a distance, a density, a tree ensemble, a kernel method, a learned novelty score and a sequence model, do not agree this closely by accident. The signal they all read does not carry the answer. Our own monitor is the best of the group on execution faults, scoring a perfect 1.00 on all three, and just as blind as the rest on real failures.

Worse than chance, and why

On DROID seven of the ten score below 0.5, some of them clearly so. That is not the same as having no signal. It means the score runs backwards: failed episodes look calmer than successful ones, so a monitor tuned on this data would raise its alarms on the successes first.

The cause turned out to be simple once we measured it. On DROID a failed episode runs 188 steps against 239 for a successful one, and covers 5.2 units of total motion against 7.6. A policy that fails often does less. It gives up early, or never reaches the part of the task where the interesting motion happens. Any score built on magnitude or movement therefore reads failure as quiet and quiet as normal. Anyone deploying an action-stream monitor should check the direction of this effect on their own data before trusting it.

Policy CI, the first two runs

Two rented A10 boxes, 2,280 episodes, one ACT checkpoint from the LeRobot hub with its weights left alone. The battery for the second run varies where the cube sits and how far it is turned. The turning matters: the simulator never rotates the cube itself, in training or in evaluation, so that axis is outside anything the policy has seen.

What was checkedWhat came backWhy it matters
Two separate runs of one policy over the same 256 scenesSame outcome, same reward and the same number of steps in 256 of 256Both sides of a comparison have to see the same scene or none of it means anything. Twelve worker processes rebuilt every scene from a hash and agreed on all of them.
Where the untouched public checkpoint fails, grouped by cube position and rotationTurned past 11.9 degrees it fails 46 of 77, against 43 of 179 everywhere else. p 5.9e-08It names the condition instead of handing over 89 videos to watch. Nothing told it that rotation was worth looking at.
The worst corner of that spaceCube near the front edge and turned: 14 of 16 fail, against 75 of 240 elsewhereThat is an operating limit you can design a fixture around, without retraining anything.
The same policy once rotation is added to the battery85.0% [79.4, 89.3] falls to 65.2% [59.2, 70.8]Puts a number on what leaving the training distribution costs, on a checkpoint whose headline number is the first one.
Run to run variation, same weights run twice with noise on the actions19 of 157 passing scenes flipped, a floor of 12.1% [7.9, 18.1]Until you know this number, a count of broken scenes is not evidence of anything.
A deliberately weakened policy measured against that floor65.2% to 4.3%, paired difference −60.9% [−67.3, −54.6], 160 broken against a floor of 19The regression is reported as 141 beyond the noise, not as a bare 160 that nobody can interpret.
A policy compared against its own rerun0 of 256 scenes differed, and no regression was reportedA tool that raises an alarm on a policy against itself is one nobody would keep running.
Every interval is 95% Wilson. The last row is the one to read first: a tool that cannot stay quiet when nothing changed cannot be trusted when something does. The raw manifests, the episode records and the scripts behind each row are in examples/validation.
What these numbers do not say

All of it is simulation. Nothing here connects to a real robot yet, and the deploy record Policy CI writes says so in its own text rather than leaving it to be assumed. The rotation limit is a fact about this checkpoint in this simulator, and whether the same limit shows up on a physical arm is exactly the experiment nobody has run.

05

What it does not do yet

A tool about honest measurement has to be honest about itself, so this section exists and stays current.

06

Where we need help

This is one person's work so far, and the gaps in section 05 are not going to close on their own. If you run robots, study them, or just like breaking things carefully, there is real work here. Every item below is something we cannot do alone, ordered by how much it would change the answer to whether this is ready to trust.

Put it on a robotthe biggest gap

Every real robot result we have is replay. The guard watched recordings and never once stopped an arm. If you have a working cell and are willing to run the guard in the loop, even in a corner of the lab on a task you do not care about, that single experiment is worth more than anything else on this list. We want to know what happens when it fires on a robot that is actually moving.

Bring a second policysimulation counts

All the live numbers come from one ACT policy on one task. We tried three other public checkpoints and all three failed, for three different reasons we have written down. If you have a policy that reliably does its task, in simulation or otherwise, running the same battery against it would tell us whether any of the envelope generalises or whether we got lucky.

Teach it to see task failuresopen research

The guard is at chance on real task failures, and we showed the signal is not in the action stream to begin with. This is a research problem, not a tuning problem. If you have a view on what a cheap per step signal for "the robot is moving fine but the task is going wrong" looks like, we would like to hear it, including the answer that there is not one.

Be the first outsidera morning of your time

Nobody outside has installed this. Take it, point it at your own logs, and tell us where it confused you, what it got wrong about your data, and which error message wasted your time. A blunt issue about a bad assumption is more useful to us than a pull request.

Check our numbersadversarial welcome

Every run is published with the scripts that produced it, including the ones that went badly. If you think a result is wrong, or that an interval is doing more work than it should, say so. We have already had to publish one correction against our own tool and we would rather do that again than be quietly wrong.

The data we have been working from

The benchmark currently runs over 54 public datasets, all of them open. RoboMIND and AgiBotWorld task collections, the Open X-Embodiment sets converted to LeRobot format, DROID, BotFails, several HIL-SERL and DAgger intervention logs, SO-100 and SO-101 pick and place sets, and the RoboArena pairwise sessions. Between them they cover Franka, UR5, SO-100, SO-101 and bimanual platforms.

That mix is a starting point, not a standard. It is weighted towards tabletop manipulation and towards whatever happened to be public and well formed, which is its own kind of bias. If you have logs from a different robot, a different task family, or a setup where you expect this to fall over, those are the most useful ones to run. You do not have to share the data to share the result: the reports are text and the raw logs never need to leave your machine.

How to start

Open an issue on GitHub describing what you have and what you want to try, or just send a report the tool produced with your read on whether it told you the truth. Independent researchers, students and lab engineers are all equally welcome. There is no contributor agreement to sign and no roadmap to fit into.

07

Using it

Python 3.11 or newer. The core has no GPU requirement, and the vision channel is optional.

pip install robotruth
robotruth --version

Or from source, if you want to run the tests or change something:

git clone https://github.com/mulkakhileshmj/robotruth.git
cd robotruth
uv venv .venv && uv pip install -e ".[dev]"
.venv/bin/python -m pytest -q

Check a deployment before it goes out:

robotruth contract extract runs/ckpt_0400/pretrained_model -o evaluated.json --role evaluated
robotruth contract extract /robot/current_policy -o deployed.json --role deployed
robotruth contract check evaluated.json deployed.json

Settle whether a new checkpoint is better:

robotruth stats plan --p-a 0.80 --p-b 0.90
robotruth stats compare results.csv --a base --b cand
Where to start reading

The repository README covers every command. The validation bundles under examples/validation hold the raw output behind every number on this page, including the runs that went badly.