Documentation menu

run()

The core primitive — one policy, one robot, one scene, and back comes a rate with n, a Wilson interval, and named failure clusters.

roborama.run() puts a policy on a physical robot in a versioned scene and returns a statistic you can defend: a success rate with its sample size and Wilson 95% interval, failure clusters, both meters, and the full episode record. Every other primitive is a composition of this call. Everything else on this page is a field on it.

The call

roborama.run()
import roborama  # reads ROBORAMA_API_KEY from the environment

run = roborama.run(
    robot="g1-edu-pro@fw2.3",            # embodiment @ pinned firmware
    environment="kitchen-std@v1.2",      # versioned catalogue scene
    policy=roborama.Policy.container(    # see policy packaging
        image="ghcr.io/acme/skill:v4",
        action_space="joint_delta_50hz",
        observation_contract="droid-3cam",
    ),
    task="load_dishwasher@v2",
    episodes="auto(ci=0.95, moe=0.03)",  # size n for ±3% at 95% — or an int
    perturbation={                       # the schedule IS the product spec
        "layout_jitter_mm": 25,
        "lighting": ["3000K", "5600K"],
        "distractors": "set-B",
        "seed": 42,
    },
    data={"retention": "30d", "train_on_failures": False},  # IP posture
    max_budget_usd=4_000,                # hard stop, metered live
)

print(run.result())
#  n=612  success_rate=0.874  ci95=(0.846, 0.898)
#  failure_clusters: [grasp_slip: 41, perception_miss: 22, collision: 9]
#  robot_hours=20.4  environment_hours=20.4  cost_usd=3_812
#  artifacts: mcap[], video[], ground_truth[], report_pdf
n=612 success_rate=0.874 ci95=(0.846, 0.898)
failure_clusters: [grasp_slip: 41, perception_miss: 22, collision: 9]
robot_hours=20.4 environment_hours=20.4 cost_usd=3,812
artifacts: mcap[], video[], ground_truth[], report_pdf

Every field of that result is load-bearing: the rate ships as 0.874 with n=612 and ci95=(0.846, 0.898), the failures arrive pre-clustered (grasp_slip: 41, perception_miss: 22, collision: 9), and both meters are on the receipt.

Fields

FieldTypeNotes
robotstrmodel@firmware; @latest is allowed but the resolved pin is recorded
environmentstrcatalogue id + revision, from bare cell-a to surgical-replica@v3
policyPolicycontainer, checkpoint, or endpoint; contracts validated before any motor moves
taskstrversioned task id, e.g. load_dishwasher@v2
episodesint | "auto(...)"auto-sizing solves for margin of error — statistics as an input, not an afterthought
perturbationdictseeded, versioned, replayable; declared in the result
data.retentionstrartifact retention window, e.g. "30d"; early deletion via data.purge is receipted
data.train_on_failuresboolcustomer IP boundary; contractual default False
max_budget_usdinthard stop, metered live against both meters
priority"burst" | "standard" | "soak"maps to the rate-card tiers
interventions"none" | "on_stall" | "scripted"declared before the run; counts and timestamps ship in results, and any intervened episode is flagged in the statistics

Auto-sizing episodes

Pass an int if you already know your n. episodes="auto(ci=0.95, moe=0.03)" solves for it instead: episodes keep scheduling until the margin of error tightens to ±3 points at 95% confidence. The canonical run above stopped at n=612. How the sizing works, and when a tighter margin is worth the episodes, is covered in Runs & statistics.

The perturbation schedule

The schedule is the product spec. layout_jitter_mm: 25 is a claim about the messiness of the kitchens your policy is supposed to survive; the lighting list and distractor set are the rest of that claim. The schedule is seeded and versioned, so the same schedule and seed reproduce the same distribution of scenes — which is what makes two runs comparable and a result replayable. The realized values, not just the requested ones, are recorded per episode in the /meta channel, alongside firmware, calibration age, and thermal state.

Data posture

data.train_on_failures defaults to False, and the default is contractual: your failure episodes are your IP, not our training set, unless you flip the bit yourself. data.retention sets how long artifacts live; roborama.data.purge(run_id) deletes early and returns a receipt. The full posture — what comes back, who owns it, what we may touch — is specified in the data contract.

Budget and priority

max_budget_usd is a hard stop, metered live against robot-hours and environment-hours. A run that hits it halts with budget_exceeded and keeps the partial result: n and the interval simply describe the episodes that ran. The canonical run was capped at 4,000 and finished at 3,812.

priority picks the queue: "burst" when you need the answer today, "standard" for the default rate, "soak" for patient bulk iteration on commodity cells.

Interventions

Human help is a statistic-shaped hole unless it's declared. interventions sets the policy before the run: "none" (the default — a stalled episode is a failed episode), "on_stall" (an operator may recover a stall), or "scripted" (declared recovery procedures only). Whatever you declare, the counts and timestamps ship in the result, and any intervened episode is flagged in the statistics — a rate propped up by helping hands can't masquerade as autonomy. The canonical espresso run in /fixtures/runs.json declares "none" and reports count: 0.

For tasks whose Task Spec declares stages or an instruction distribution, the result also carries stage_rates and instruction_breakdown — each entry with its own n and ci95.

Where next