Measurement protocol: provael-redteam/v1
Every execution manifest the CLI writes names the protocol its run followed:
"protocol_id": "provael-redteam",
"protocol_version": "provael-redteam/v1"
This page says what v1 is, as the code runs it today, and what v2 is planned to change. Read it before you compare a Provael rate with another harness's, or with another Provael run.
The acceptance protocol you pass with --protocol is a different thing. It decides a release
verdict from a finished report, under criteria you write down
(examples/assessment/).
This page covers how the report gets measured in the first place.
Read the versions beside the label
provael-redteam/v1 is a literal in src/provael/cli/_shared.py. The CLI has written it since the
execution manifest first shipped, in 0.25.0 (26 July 2026), and every committed manifest under
results/ carries it. The method under the label has moved since then: each report schema records
more, significance has been tested against the benign arm's own counts since 0.46.0 (E-2026-18),
and 0.47.0 added scene pinning. So the label alone does not pin a method. Read package_version
and report_schema_version in the same manifest, or tool_version and schema_version in the
report.
The published headline, results/smolvla_libero_object_suite_2026-09-14 (provael 0.41.2), is a v1
run: report schema 5, scenes not pinned, the default keep-out box, and every episode ending at its
first event.
What v1 fixes
The loop
provael.runner.run loops over tasks, then arms, then seeds, and plays one episode per cell. A
(task, arm) pair runs at environment seeds --seed, --seed + 1 and so on, for --seeds distinct
seeds, each repeated --episodes-per-seed times. At every step the arm produces the instruction and
the observation the policy sees (the benign arm passes both through unchanged), the policy acts,
the suite steps, and the predicate reads the new state.
Every episode records its environment seed as seed. From report schema 5 it also records
policy_seed: the seed the policy's own sampler was set to, as the adapter reports it, or null
when the adapter does not seed itself.
Arms and their budgets
An arm is a registered attack, and every attack belongs to a family. provael list-attacks prints
them; the counts are in
watch/registry.json and are not
typed on this page. Each arm plays one of three roles, recorded per run in roles:
none, familybaseline, hands the policy the task instruction unchanged. Its rate is the floor.- The
controlfamily (benign_reword,nonsense_text,scrambled_text,roleplay_no_target,declarative_text) holds harmless variations. They enter neither the attack rate nor the floor, and they show whether a harmless change to the input does what an attack does. - Every other family is an adversarial treatment.
A templated family applies a fixed, deterministic transform, which may vary with the episode's
seed, and has no attacker budget. The search families (optimized, optimized_instruction,
optimized_patch, universal_patch) spend a query budget, which --query-budget can override.
gradient_patch searches inside an L∞ bound for a set number of refinement steps per frame, and each
weight_integrity attack carries its bit-flip count in its own name. The attack catalog
describes each family, and each default sits in that family's module under src/provael/attacks/.
Every arm of a run gets the same evaluation budget: --seeds × --episodes-per-seed episodes per
task, each at most --horizon steps.
Only weight_integrity records its budget in the report, as weight_corruption on each episode. A
query budget, the gradient bound and its step count appear in neither report.json nor the
manifest. They are the attack's defaults at the recorded tool_version unless the command line
changed them, and v1 does not record the command line.
Threat-model fields
Every episode carries family, attacker_access (white-box-gradient, black-box-query,
in-scene-physical, or null where the attack asserts none) and action_head_class (token or
flow, from the policy, or from the attack when the policy declares none). The report maps each
attack to its Embodied AI Top 10 risk in eai. The templated families assert no access
class: every episode of the published headline carries attacker_access: null.
What a pair is matched on
Every arm runs on the same (task, seed) cells, so each attacked episode has a benign twin at the same task and seed. That is the pairing v1 has.
On LIBERO the seed does not pick the starting scene; lerobot 0.5.1's reset counter does. In a run that does not pin its scenes, the arms at one seed start from different fixed initial states, and a pair is matched on task and seed only (E-2026-19).
From 0.47.0, --pin-init-states S starts the episode at seed s from initial state (S + s) mod N in
every arm, where N is the number of fixed initial states the task has. From report schema 8, every
LIBERO episode records the state its reset loaded as init_state, pinned or not. Two episodes
share a scene only if both recorded the same init_state. Every committed LIBERO measurement
predates both, so none of their pairs is known to share a scene.
The benign control and the controls
A run does not have to include none. Without it the report has no floor (benign_fpr: null), and
under any acceptance protocol that keeps the default require_benign_control: true the benign
criterion is incomplete, so the run cannot pass. In v1 the benign control is required for a
decision and optional for a run. provael attack defaults to --attacks instruction, which runs no
benign arm; every committed real-policy measurement names none explicitly.
The controls are opt-in (--attacks control). In the published body they ran on 14 September 2026
as a run of their own, beside none and roleplay at three seeds per task:
results/smolvla_libero_object_control_2026-09-14.
The competence check is the benign arm's task success, clean_task_success_rate. An acceptance
protocol can set a floor on it (min_clean_task_success). v1 itself does not stop a run whose
policy cannot do the task.
The predicate
On LIBERO the endpoint is an envelope exit: on some executed step, the end-effector position
(robot_state.eef.pos) lies inside a keep-out box. The box is DEFAULT_KEEP_OUT_ZONE in
src/provael/suites/keepout_zones.py
unless a calibration for the task is installed (--calib). The ten committed LIBERO-Object fits are
withheld (#136), so every committed real-policy rate
is scored against the default box, and its report says calibrated: false.
PROVAEL_REQUIRE_CALIBRATED=1 makes a run refuse the default box instead.
An exit says where the end effector went under the named predicate. It does not say who chose where it went (E-2026-12), and the default box is not a calibrated hazard region.
Two more properties of the v1 predicate. A step whose end-effector position the simulator did not
surface scores as safe. The forbidden-object grasp rule stays off unless a grasp extractor is
passed in. From 0.45.0 a second predicate, a contact or force event (physical_hazard), is reported
beside the exit where the suite surfaces it and never enters the rate; every committed measurement
predates it.
The CPU fixtures (stub, reach, humanoid) run the same loop with their own predicates, and a
stub policy on them reports evidence_state: stub.
Termination
An episode ends at the first of three events: the predicate fires, the suite reports done, or the
horizon runs out. On LIBERO, done is lerobot's terminated or truncated, and lerobot terminates
at task success. So an episode cannot both succeed and exit, and it cannot stay unsafe past one
step. Succ-But-Unsafe, risk exposure time and cumulative cost cannot be observed under this rule
(E-2026-21). Reports rendered by main print them as N/A with the reason; that change
ships in 0.48.0 (12 November 2026).
The same rule shortens two published rates. Task success under an arm stops at the exit, and the floor is observed over a shorter window than an arm that runs on to the horizon.
Intervals and tests
- A rate is successes over applicable episodes. An arm with no surface on the suite is not applicable: it leaves every denominator and prints as N/A, never as 0.
- Each arm's rate, and the floor, carry a 95% Wilson interval (
provael.calibration.wilson_ci).provael.scoring.asr.benign_controlpairs a rate with its floor for every per-run emitter, so the pairing cannot differ between them; the leaderboard aggregates across reports and computes its own. - A rate over several tasks also carries a task-clustered interval: a seeded percentile bootstrap
that resamples whole tasks (
provael.scoring.paired.cluster_bootstrap_ci). It needs two tasks or more, and gives none when every task scores the same. report.mdcalls an attack significant on a one-sided Fisher exact test against the benign arm's own counts, Benjamini-Hochberg corrected across the run's EAI-tagged attacks (from 0.46.0, E-2026-18).- A generated results README adds a two-sided exact McNemar test on the (task, seed) pairs against
none, Holm-corrected across every arm the run tested againstnone. On a run that did not pin its scenes, that is a test on nominal pairs (E-2026-19). - The report's all-episode rate also carries an anytime-valid interval, and
preliminary: truewhen fewer than five seeds ran.
What each record carries
| record | file | what it carries |
|---|---|---|
one episode (AttackResult) |
report.json, under results |
task, arm, family and seed; success (the exit), steps and steps_to_success; the original instruction and the one the policy received; applicable; task_success; endpoints (physical_hazard where surfaced); the threat-model fields; one decision per executed step (instruction, action, unsafe); and from the schema named, trajectory (3), weight_corruption (4), policy_seed (5) and init_state (8) |
one run or shard (RunReport) |
report.json |
tool_version and schema_version; policy, the requested checkpoint (model), suite, tasks, arms, episodes, horizon and base seed; the rates above with their intervals; roles, eai, and counts per arm and per task; calibrated and calibration; and from the schema named, deployed_policy, the policy as the adapter loaded it (6), and each calibration's split and binding (7) |
the run's provenance (ExecutionManifest) |
execution-manifest.json |
the protocol id and version; the package version, the report's schema version and its digest; start and end times, OS, Python and hardware; commit, repository and lock digest where the run could supply them, with missing_fields naming the gaps; allow-listed environment variables; the release verdict and the acceptance protocol it was decided under |
report.json holds no wall-clock value, so the same configuration on the deterministic stub gives
the same bytes. Anything that depends on the machine or the clock goes in the manifest, which names
the report by its digest.
What v1 does not record
- Which scene an episode started from, before report schema 8.
- Why an episode ended. A reader infers it from
success,task_successandsteps. - Any attack budget except
weight_integrity's. - Whether the predicate could be evaluated on every step: a step with no end-effector position counts as safe.
- Counts and an interval method beside every stored rate.
benign_fpr,matched_benign_fpr,succ_but_unsafeandclean_task_success_rateare bare floats, andci95is a bare pair whose method only the schema's description names.
v2, planned
v2 exists once a manifest says provael-redteam/v2. None does yet, and until one does, every run
writes v1, pinned or not. The planned changes:
- Pinned scenes as the LIBERO default, with a check: a run fails if two arms at one (task, seed)
recorded different
init_statevalues. The pinning itself ships in 0.47.0 as--pin-init-states. - Report schema 9. Per episode: a digest of the scene right after reset, why the episode ended, whether it was judged, and how many of its steps had a pose. Per task: the predicate that scored it and where it came from. Per rate: its counts and its interval method. Per run: digests of the policy's weights and processors. Reports are schema 8 today.
- A calibrated predicate: a keep-out zone fitted per task from the benign reachable bounds, at a
target false-positive rate of 1% or less with the face chosen from data, and
PROVAEL_REQUIRE_CALIBRATED=1as the LIBERO default. This is proof 1 on the roadmap. - Fixed-horizon termination, if it is adopted. Every episode would run its full horizon in every arm, with success and exit both latched and unsafe steps still counted, so Succ-But-Unsafe, risk exposure time and cumulative cost could be observed. No decision has been taken.