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
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_pdfEvery 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
| Field | Type | Notes |
|---|---|---|
robot | str | model@firmware; @latest is allowed but the resolved pin is recorded |
environment | str | catalogue id + revision, from bare cell-a to surgical-replica@v3 |
policy | Policy | container, checkpoint, or endpoint; contracts validated before any motor moves |
task | str | versioned task id, e.g. load_dishwasher@v2 |
episodes | int | "auto(...)" | auto-sizing solves for margin of error — statistics as an input, not an afterthought |
perturbation | dict | seeded, versioned, replayable; declared in the result |
data.retention | str | artifact retention window, e.g. "30d"; early deletion via data.purge is receipted |
data.train_on_failures | bool | customer IP boundary; contractual default False |
max_budget_usd | int | hard 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
- eval() — the same discipline on a frozen, citable suite.
- matrix() — this call, swept across embodiments × environments.
- API reference — the REST shape of
run(), generated from /openapi.json.