Skip to main content

Scoring

A Scorer maps a recorded TrialRecord (plus the scene's Target) to a Score. Scorers read the recorded trajectory (never a live environment), so scoring is reproducible from a saved log.

Builtin scorers

from inspect_robots.scorer import (
success_at_end, # 1.0 iff the episode terminated with reason "success"
episode_length, # number of steps taken
min_distance_to_goal, # closest the effector got (reads StepResult.info["distance"])
reached_goal_state, # success iff min distance <= threshold
operator_scorer, # reads a human verdict recorded during the rollout
)

Custom scorers

A scorer is any object with a name and a __call__(record, target) -> Score:

from dataclasses import dataclass
from inspect_robots.scorer import Score

@dataclass(frozen=True)
class SmoothMotion:
name: str = "smooth_motion"

def __call__(self, record, target) -> Score:
deltas = [abs(float(s.action.data.sum())) for s in record.steps]
return Score(value=-sum(deltas), explanation="negative total command magnitude")

Register it with scorer to resolve it by name.

Epochs and reducers

When a Task runs epochs > 1, an epoch reducer collapses the per-epoch scores of a scene before metrics aggregate across scenes. Reducers are namespaced separately from metrics and are selected by name on Epochs:

ReducerMeaning
mean, median, max, minnumeric reductions (raise on non-numeric strings)
modemost common value (works for categorical scores)
pass_at_<k>unbiased pass@k estimator (success = value ≥ 0.5)
from inspect_robots.task import Epochs, Task
Task(..., epochs=Epochs(count=5, reducer="pass_at_2"))

Operator and VLM scoring (real world)

Real robots have no privileged success oracle. The dominant method is a human verdict, captured once per trial and read back by operator_scorer, keeping scoring reproducible. Capture is the job of a Grader: a registered component (inspect_robots.graders entry point, grader decorator) whose grade(record, scene) runs once per scored trial, after the rollout and before the scorers, and writes the judgement onto the record. The builtin operator grader prompts the terminal operator, and the builtin vlm grader is the autograder on the same seam: a vision model judges the trial's first and last frames against a rubric (the reserved VLMScorer interface predates it and stays a stub, because R6 requires scorers to be pure readers).

Every attended CLI run is graded by default, registered tasks included, so judgement-reading scorers (the operator scorer, or task scorers that fall back to operator_judgement) work with operator-in-the-loop embodiments and with policies that end their own trials (done()/give_up()). success_at_end reads only embodiment-detected "success" terminations and scores operator-graded trials as failures; pair attended operator-graded runs with a judgement-reading scorer instead. From the Python API, pass eval(..., grader="operator") (or any Grader object); unattended runs and eval() without a grader stay prompt-free.