rollouts
Sim rollout jobs — watch a policy run in MuJoCo
GET /v1/rollouts
List sim rollouts
Rollouts the caller can see — ones they created, or whose model repo they can write (owner, or a member of the owning org) — newest first. A rollout worker polls this with status=queued to find work; the model repo page lists a policy's past batteries with model_repo=owner/slug. No rollout is public. (M8)
Parameters
| Name | In | Type | Description |
|---|---|---|---|
status | query | JobStatus | |
model_repo | query | string |
|
run_id | query | string | Only rollouts attached to this run. |
purpose | query | RolloutPurpose | Only |
executor | query | RolloutExecutor | Only rollouts for this executor — |
limit | query | integer | Page size. |
cursor | query | string | Opaque cursor from the previous page's |
Responses
| Status | Description | Body |
|---|---|---|
200 | A page of rollouts. | RolloutPage |
401 | Missing or invalid credentials. | Problem |
422 | Request failed validation. | Problem |
POST /v1/rollouts
Create a sim rollout
Queues a sim rollout: load model_repo's policy, run it on robot_repo's MJCF in MuJoCo, render a video and trajectory. Cost is capped by horizon_s ≤ 30 (CPU-only in v0).
M8 — the gate. A rollout is a battery of command cells × seeds (battery: smoke | full | an inline spec). Each cell is scored by the source harness's own rule — PASS when every seed stays upright for the whole horizon AND every commanded axis tracks inside [0.85, 1.15] — and the result is written as a scorecard.json whose PASS rows the Training Coach reads as standing constraints. Name a run_id and the terminal report also attaches that scorecard to the run (Run.scorecard_key), so POST /v1/coach/advice on the run sees the gates without anyone copying anything. The 720p mp4 per cell is the by-product. Both repos must be visible to the caller and of the right kind (a model and a robot whose card names an mjcf_path); an invisible repo is 404, a wrong kind 409, an invisible run_id 422.
Nothing executes server-side: the rollout waits queued until a rollout worker (lucen rollout worker, or lucen rollout run --local which claims its own) takes it through claimRollout / presignRolloutArtifacts / reportRollout. A CPU rollout is unpriced (cost_usd: 0, priced: false).
C2 — the founder's harness, in the cloud. The battery may be one of the founder's (c_matrix, robust, recovery, watch) or carry his sim2sim conditions (conditions: delay, friction, kp/kd scale, push, power scale, heading, the bridge slew, jitter, a fallen start); every cell's seed 0 is rendered from his three fixed views. executor: modal has the hub spawn the deployed rollout function after the commit (modal_call_id; 503 naming MODAL_APP_NAME when the backend is not configured, with nothing recorded). checkpoint_step rolls out checkpoints/step_<N>/ instead of the root policy.
Hosted execution (L0). executor: modal runs on the hub's own Modal account, whose container claims the rollout with the operator's key; so it needs the model's owner in the deployment's HOSTED_OWNERS (default lucen; * = everyone) and write access to the model repo — reading a public model is enough for a worker rollout, never for a hosted one. Either refusal is 403 with errors[].type: forbidden.hosted_owner and records nothing.
Request body
| Field | Type | Description |
|---|---|---|
model_repo | string | Model repo |
robot_repo | string | Robot repo |
seed | integer | |
horizon_s | number | Sim horizon in seconds (capped to keep rollouts ≤ $0.05). |
battery | string | BatterySpec |
|
conditions | SimConditions | null | Battery-level conditions on top of the battery's own, key by key (a cell's own still win) — e.g. |
seeds | integer | null | Overrides the battery's seed count (a 20-seed band scan of |
executor | RolloutExecutor | |
checkpoint_step | integer | null | Roll out |
run_id | string | null | The training run this rollout gates. Must be visible to the caller ( |
video | VideoMode | null | L1 — overrides the battery's |
Responses
| Status | Description | Body |
|---|---|---|
201 | Rollout queued. | Rollout |
401 | Missing or invalid credentials. | Problem |
403 | Authenticated but not allowed (visibility, membership or scope). | Problem |
404 | Resource not found (or hidden from the caller). | Problem |
409 | State conflict (duplicate handle/slug, wrong repo kind, terminal job, ...). | Problem |
422 | Request failed validation. | Problem |
503 | A service this endpoint needs is not configured in this deployment (M0's boot guarantee: the API boots with zero secrets, and an endpoint that needs one says so instead of returning a stack trace). | Problem |
GET /v1/rollouts/{rollout_id}
Get a sim rollout
Rollout status and, when succeeded, the storage keys of the rendered video and trajectory — plus, since M8, presigned 1-hour playback URLs (video_url, traj_url, scorecard_url, and one pair per cell), the per-cell verdicts and the battery that was run. Visibility is the creator's, or anyone who can write the model repo; anything else is 404.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
rollout_id | path | Id | Rollout id ( |
Responses
POST /v1/rollouts/{rollout_id}/claim
Claim a queued rollout (rollout worker protocol)
Compare-and-set queued → running, exactly like claimRun: one caller wins, everyone else gets 409 naming the holder. Records claimed_by, claimed_at, started_at, heartbeat_at, the claiming credential (presignRolloutArtifacts and reportRollout are 409 from any other — one key per worker) and an audit row, and returns the rollout with its resolved battery_spec — the cells, seeds and horizon the worker must execute. The model and robot repos are what the worker pulls next (the policy ONNX and its io_contract, the MJCF and its meshes). key:train: a worker spends compute on the creator's behalf. No lease (M7-RL's rule): a dead worker leaves the rollout running until a human acts. (M8)
Parameters
| Name | In | Type | Description |
|---|---|---|---|
rollout_id | path | Id | Rollout id ( |
Request body
| Field | Type | Description |
|---|---|---|
worker | string | A name for the worker, recorded as |
Responses
| Status | Description | Body |
|---|---|---|
200 | Claimed; the rollout as it now stands. | Rollout |
401 | Missing or invalid credentials. | Problem |
403 | Authenticated but not allowed (visibility, membership or scope). | Problem |
404 | Resource not found (or hidden from the caller). | Problem |
409 | State conflict (duplicate handle/slug, wrong repo kind, terminal job, ...). | Problem |
422 | Request failed validation. | Problem |
POST /v1/rollouts/{rollout_id}/artifacts
Presign uploads for a running rollout's artifacts
Bytes never stream through the API (docs/ARCHITECTURE.md), and a battery's videos are megabytes each — so a worker asks for presigned PUT URLs here and uploads straight to object storage, one object per artifact, under rollouts/{rollout_id}/… (outside the content-addressed blobs/ prefix M3's GC sweeps). Names are repo-style relative paths (cells/vx-plus-0p30/video.mp4, cells/vx-plus-0p30/trajectory.json, trajectory.json); the keys returned are what reportRollout then names in cells[]. Only a running rollout may presign, and only the credential that claimed it (409 otherwise); URLs live 1 h. (M8)
Storage (L0). The declared sizes count toward the rollout creator's storage quota (STORAGE_QUOTA_BYTES, the same 50 GiB as repo files): over it is 413 and no URL is issued.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
rollout_id | path | Id | Rollout id ( |
Request body
| Field | Type | Description |
|---|---|---|
files | array of RolloutArtifactRequest |
Responses
| Status | Description | Body |
|---|---|---|
200 | One presigned PUT per requested artifact. | RolloutArtifactsResponse |
401 | Missing or invalid credentials. | Problem |
403 | Authenticated but not allowed (visibility, membership or scope). | Problem |
404 | Resource not found (or hidden from the caller). | Problem |
409 | State conflict (duplicate handle/slug, wrong repo kind, terminal job, ...). | Problem |
413 | The rollout creator's storage quota would be exceeded (L0). | Problem |
422 | Request failed validation. | Problem |
POST /v1/rollouts/{rollout_id}/report
Report progress or the result of a claimed rollout
The worker's one write path after claimRollout; the rollout must be running and the caller the credential that claimed it (409 otherwise). Every field is optional: log_chunk (≤ 256 KiB) is stored as one object under rollouts/{id}/logs/; cells[] carries the measurements — seeds run, seeds that stayed upright, the tracking ratio per commanded axis, the artifact keys presigned earlier — and warnings[] / engine say what the worker could not do (a missing renderer, an unsupported observation). The hub scores, not the worker: on a terminal succeeded report the hub applies the harness rule to the measurements, writes scorecard.json (gates in the shape coach/gates.py reads, names that survive its normalisation — vy+0.10 and vy-0.10 stay distinct), sets scorecard_key / video_key / traj_key, records cost_usd 0 (priced: false), and — when the rollout carries a run_id — copies the scorecard to runs/{run_id}/scorecard.json, sets Run.scorecard_key and writes a run.scorecard audit row. status may only move running → succeeded | failed. A key named in cells[] that is not under this rollout's prefix, or that holds no object, is 422. (M8)
Parameters
| Name | In | Type | Description |
|---|---|---|---|
rollout_id | path | Id | Rollout id ( |
Request body
| Field | Type | Description |
|---|---|---|
status | RolloutReportStatus | null | |
log_chunk | string | null | Engine output since the last report, ≤ 256 KiB of UTF-8. |
error | string | null | Failure summary; expected with |
cells | array of RolloutCellResult | Measurements per cell. Replaces any earlier list. |
warnings | array of string | What the worker could not do, e.g. |
engine | object | null | Versions and fingerprints: |
Responses
| Status | Description | Body |
|---|---|---|
200 | Recorded; the rollout as it now stands. | Rollout |
401 | Missing or invalid credentials. | Problem |
403 | Authenticated but not allowed (visibility, membership or scope). | Problem |
404 | Resource not found (or hidden from the caller). | Problem |
409 | State conflict (duplicate handle/slug, wrong repo kind, terminal job, ...). | Problem |
413 | A log chunk or the report body is over its size limit, or the log chunk puts the rollout creator past their storage quota (L0). | Problem |
422 | Request failed validation. | Problem |
POST /v1/rollouts/{rollout_id}/cancel
Cancel a sim rollout
A queued or running rollout becomes canceled in this request (C2). A modal rollout's call is terminated first (502 if Modal refuses, and nothing changes); a worker rollout's worker is told on its next report, which is 409 — a rollout keeps no lease, so there is no flag to wait on. Nothing is billed either way (CPU rollouts are unpriced). A terminal rollout is 409. Writes a rollout.cancel audit row.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
rollout_id | path | Id | Rollout id ( |
Responses
| Status | Description | Body |
|---|---|---|
202 | Canceled; the rollout as it now stands. | Rollout |
401 | Missing or invalid credentials. | Problem |
403 | Authenticated but not allowed (visibility, membership or scope). | Problem |
404 | Resource not found (or hidden from the caller). | Problem |
409 | State conflict (duplicate handle/slug, wrong repo kind, terminal job, ...). | Problem |
502 | Modal refused to terminate the call; nothing changed. | Problem |
POST /v1/rollouts/{rollout_id}/render
Render an earlier rollout's videos
L1 — a gate while training renders at most a poster per cell (video: poster or none); this queues the videos afterwards. The new rollout (purpose: render, render_of = this one) runs the same model repo, checkpoint, robot, battery conditions and seed with seeds: 1 and video: full (or poster), so seed 0 of each chosen cell is simulated again and rendered — the engine is deterministic for a seed, and the hub checks the replay against the measured episode (render_check). It carries the source's run_id, never counts as a gate, a scan or the run's scorecard, and is unpriced like any CPU rollout. The source must be succeeded (409 otherwise); a cell name the source battery does not have is 422. Same executor rules as createRollout.
Hosted execution (L0). executor: modal runs on the hub's own Modal account, whose container claims the rollout with the operator's key; so it needs the model's owner in the deployment's HOSTED_OWNERS (default lucen; * = everyone) and write access to the model repo — reading a public model is enough for a worker rollout, never for a hosted one. Either refusal is 403 with errors[].type: forbidden.hosted_owner and records nothing.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
rollout_id | path | Id | Rollout id ( |
Request body
| Field | Type | Description |
|---|---|---|
cells | array of string | null | The source battery's cell names to render; null or absent = every cell. |
video | string | |
executor | RolloutExecutor | null | Null means the source rollout's executor. |
Responses
| Status | Description | Body |
|---|---|---|
201 | The render rollout, queued. | Rollout |
401 | Missing or invalid credentials. | Problem |
403 | Authenticated but not allowed (visibility, membership or scope). | Problem |
404 | Resource not found (or hidden from the caller). | Problem |
409 | State conflict (duplicate handle/slug, wrong repo kind, terminal job, ...). | Problem |
422 | Request failed validation. | Problem |
503 | A service this endpoint needs is not configured in this deployment (M0's boot guarantee: the API boots with zero secrets, and an endpoint that needs one says so instead of returning a stack trace). | Problem |
GET /v1/runs/{run_id}/gates
The sim gates a run's checkpoints got while it trained
Step × cells × verdict for the gate rollouts the hub queued from the run's Recipe.gate (C2), oldest step first, plus every checkpoint step the output repo holds and the tripwire state — which rule fired, at which step, on which rollout. A run with no gate answers an empty table. L1: each row says what its gate rendered (video), carries a presigned poster per cell and one for the row when it rendered posters, and the latest renderRollout of it.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
run_id | path | Id | Training run id ( |
Responses
| Status | Description | Body |
|---|---|---|
200 | The gate table. | CheckpointGateTable |
401 | Missing or invalid credentials. | Problem |
403 | Authenticated but not allowed (visibility, membership or scope). | Problem |
404 | Resource not found (or hidden from the caller). | Problem |
GET /v1/runs/{run_id}/scan
A band scan's table
Step × cells × verdict for the run's latest band scan (or scan_id), oldest step first (C2). An empty table when the run was never scanned.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
run_id | path | Id | Training run id ( |
scan_id | query | string |
Responses
| Status | Description | Body |
|---|---|---|
200 | The scan table. | CheckpointGateTable |
401 | Missing or invalid credentials. | Problem |
403 | Authenticated but not allowed (visibility, membership or scope). | Problem |
404 | Resource not found (or hidden from the caller). | Problem |
POST /v1/runs/{run_id}/scan
Band-scan a run's checkpoints in simulation
One rollout per checkpoints/step_<N>/ of the run's output model repo in the requested band, each battery × seeds (default watch × 20 — the founder's 20-seed rule, because a few seeds differ between machines), on robot_repo (default recipe.robot), all under one scan_id (C2). 409 when the run has no output repo or no checkpoint in the band; 422 for a band with more than 50 checkpoints. Same executor rules as createRollout.
Hosted execution (L0). executor: modal runs on the hub's own Modal account, whose container claims the rollout with the operator's key; so it needs the model's owner in the deployment's HOSTED_OWNERS (default lucen; * = everyone) and write access to the model repo — reading a public model is enough for a worker rollout, never for a hosted one. Either refusal is 403 with errors[].type: forbidden.hosted_owner and records nothing.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
run_id | path | Id | Training run id ( |
Request body
| Field | Type | Description |
|---|---|---|
from_step | integer | null | |
to_step | integer | null | |
steps | array of integer | null | |
seeds | integer | |
battery | string | |
conditions | SimConditions | null | |
executor | RolloutExecutor | null | Null means the run's own executor. |
robot_repo | string | null | Defaults to the run's |
video | VideoMode | null | L1 — null keeps the three views ( |
Responses
| Status | Description | Body |
|---|---|---|
201 | The scan's rollouts, queued. | CheckpointGateTable |
401 | Missing or invalid credentials. | Problem |
403 | Authenticated but not allowed (visibility, membership or scope). | Problem |
404 | Resource not found (or hidden from the caller). | Problem |
409 | State conflict (duplicate handle/slug, wrong repo kind, terminal job, ...). | Problem |
422 | Request failed validation. | Problem |
503 | A service this endpoint needs is not configured in this deployment (M0's boot guarantee: the API boots with zero secrets, and an endpoint that needs one says so instead of returning a stack trace). | Problem |
Schemas (34)
The schemas these operations reach before any other tag’s do. A type that links elsewhere is rendered on that tag’s page.
RolloutPurpose
Why a rollout exists (C2): manual (someone asked; only these attach their scorecard to run_id), gate (queued by the hub for a checkpoint of a running run's Recipe.gate) or scan (one checkpoint of a band scan). L1 adds render — the videos of an earlier rollout rendered afterwards (renderRollout: seed 0 of its cells replayed with video: full); it never counts as a gate, a scan or the run's scorecard.
RolloutPage
| Field | Type | Description |
|---|---|---|
items | array of Rollout | |
next_cursor | string | null |
RolloutCreate
| Field | Type | Description |
|---|---|---|
model_repo | string | Model repo |
robot_repo | string | Robot repo |
seed | integer | |
horizon_s | number | Sim horizon in seconds (capped to keep rollouts ≤ $0.05). |
battery | string | BatterySpec |
|
conditions | SimConditions | null | Battery-level conditions on top of the battery's own, key by key (a cell's own still win) — e.g. |
seeds | integer | null | Overrides the battery's seed count (a 20-seed band scan of |
executor | RolloutExecutor | |
checkpoint_step | integer | null | Roll out |
run_id | string | null | The training run this rollout gates. Must be visible to the caller ( |
video | VideoMode | null | L1 — overrides the battery's |
Rollout
| Field | Type | Description |
|---|---|---|
id | Id | |
status | JobStatus | |
model_repo | RepoRef | |
robot_repo | RepoRef | |
seed | integer | |
horizon_s | number | |
video_key | StorageKey | null | Rendered mp4 storage key (set on success; the first cell's). |
traj_key | StorageKey | null | Trajectory JSON storage key (set on success; the battery index). |
cost_usd | number | null | |
error | string | null | |
created_by | UserPublic | null | |
created_at | string (date-time) | |
started_at | string (date-time) | null (date-time) | |
finished_at | string (date-time) | null (date-time) | |
battery | string |
|
battery_spec | BatterySpec | |
run_id | string | null | The run this rollout gates, if any. |
scorecard_key | StorageKey | null |
|
logs_key | StorageKey | null | Prefix of the worker's log chunks. |
claimed_by | string | null | |
claimed_at | string (date-time) | null (date-time) | |
heartbeat_at | string (date-time) | null (date-time) | |
priced | boolean | True for a |
cells | array of RolloutCellResult | Per-cell measurements and, once scored, verdicts. |
warnings | array of string | |
engine | object | null | |
video_url | string (uri) | null (uri) | Presigned 1-hour GET for |
traj_url | string (uri) | null (uri) | |
scorecard_url | string (uri) | null (uri) | |
gates_passed | integer | null | PASS cells, once scored. |
gates_total | integer | null | |
executor | RolloutExecutor | |
modal_call_id | string | null | The Modal call the hub spawned for a |
dispatched_at | string (date-time) | null (date-time) | |
checkpoint_step | integer | null | The checkpoint of the model repo this rollout ran, if not its root policy. |
purpose | RolloutPurpose | |
scan_id | string | null | The band scan this rollout belongs to ( |
video | VideoMode | |
render_of | string | null |
|
render_check | RolloutRenderCheck | null |
|
RolloutClaim
| Field | Type | Description |
|---|---|---|
worker | string | A name for the worker, recorded as |
RolloutArtifactsRequest
| Field | Type | Description |
|---|---|---|
files | array of RolloutArtifactRequest |
RolloutArtifactsResponse
| Field | Type | Description |
|---|---|---|
uploads | array of RolloutArtifactUpload |
RolloutReport
One progress or terminal report from a rollout worker. Every field is optional.
| Field | Type | Description |
|---|---|---|
status | RolloutReportStatus | null | |
log_chunk | string | null | Engine output since the last report, ≤ 256 KiB of UTF-8. |
error | string | null | Failure summary; expected with |
cells | array of RolloutCellResult | Measurements per cell. Replaces any earlier list. |
warnings | array of string | What the worker could not do, e.g. |
engine | object | null | Versions and fingerprints: |
RolloutRenderRequest
L1 — render an earlier rollout's videos now. The hub queues a purpose: render rollout of the same model repo, checkpoint, robot, battery and seed with seeds: 1 and video (default full), so seed 0 of each chosen cell is simulated again and rendered. The engine is deterministic for a seed, so the replay is the episode that was measured; the hub checks it — seed 0's upright / fall time / tracking against the source's — and says so in Rollout.render_check.
| Field | Type | Description |
|---|---|---|
cells | array of string | null | The source battery's cell names to render; null or absent = every cell. |
video | string | |
executor | RolloutExecutor | null | Null means the source rollout's executor. |
CheckpointGateTable
Step × cells × verdict (C2): the gates a running run's checkpoints got (getRunGates) or one band scan (scanRun / getRunScan), oldest step first.
| Field | Type | Description |
|---|---|---|
run_id | Id | |
purpose | RolloutPurpose | |
scan_id | string | null | |
battery | string | null | |
seeds | integer | null | |
every_steps | integer | null | The run's gate cadence ( |
checkpoints | array of integer | Every checkpoint step the output repo holds, gated or not. |
rows | array of CheckpointGateRow | |
tripwire | TripwireState | null | |
best | CheckpointGateBest | null | P1 — the best finished row by one deterministic rule (docs/API.md "The best gate"), so a run card, a rail and an agent's line never break a tie two ways. Null before any row finished. |
RunScanRequest
A band scan (C2, the founder's 20-seed pick of 1–2 checkpoints): one rollout per checkpoints/step_<N>/ of the run's output model repo in [from_step, to_step] (or exactly steps), each battery × seeds.
| Field | Type | Description |
|---|---|---|
from_step | integer | null | |
to_step | integer | null | |
steps | array of integer | null | |
seeds | integer | |
battery | string | |
conditions | SimConditions | null | |
executor | RolloutExecutor | null | Null means the run's own executor. |
robot_repo | string | null | Defaults to the run's |
video | VideoMode | null | L1 — null keeps the three views ( |
BatterySpec
A battery: cells × seeds × horizon. The named batteries: smoke (stand, vx+0.30, vy+0.10, vy-0.10, yaw+0.50 × 2 seeds × 5 s) and full (the source harness's 13-cell C-matrix × 20 seeds × 20 s). M17b: when the robot card's interface says base: fixed, velocity cells are meaningless, so smoke resolves to the fixed-base smoke battery instead — hold, jt+0.30, jt-0.30 (every joint offset by ±0.30 rad from the default pose) × 2 seeds × 5 s — and full is 422. The resolved cells are always returned as Rollout.battery_spec. C2 adds the founder's harness batteries (legged, floating base only — 422 on a fixed base), each measured under protocol: sim2sim: c_matrix (eval_c_matrix.py: the 13 cells × 20 seeds × 20 s at delay 2, joint jitter 0.05, the bridge slew), robust (eval_matrix.py: vx+0.25 gated on tracking, then ground μ 0.4 / 0.6 / 0.8 / 1.0, lateral pushes ±4 / ±6 N·s at 8 s and 12 s and the zero command, each gated on survival — 14 cells × 20 seeds × 20 s at the same delay/jitter/slew), recovery (the four fallen categories supine / prone / side / mid × 20 seeds × 10 s at delay 2 with the slew) and watch (watch_ckpt.py's smoke gate: vx+0.25 × 3 seeds × 20 s at delay 2 — what gating during training runs). C3 adds stand: one zero-command cell scored on posture (score: posture) × 20 seeds × 20 s under the same sim2sim conditions — what a stand campaign gates and scans on.
| Field | Type | Description |
|---|---|---|
cells | array of BatteryCell | |
seeds | integer | Episodes per cell; seed |
horizon_s | number | Per-episode horizon; defaults to |
protocol | BatteryProtocol | |
conditions | SimConditions | null | Conditions for every cell (a cell's own override them key by key). (C2) |
video | VideoMode |
SimConditions
The founder's sim2sim conditions (C2), ported from tools/sim2sim.py flag by flag; absent keys are M8's plant. On a battery they apply to every cell; on a cell they override the battery's key by key; on RolloutCreate they override a named battery's own. The worker applies them; the hub still scores.
| Field | Type | Description |
|---|---|---|
delay | integer |
|
delay_jitter | array of integer |
|
friction | number |
|
kp_scale | number |
|
kd_scale | number |
|
push | SimPush | |
power_scale | number |
|
heading | boolean |
|
heading_k | number |
|
slew | boolean | The deployment bridge's speed limit ( |
jitter | number |
|
yaw_jitter | number | Start yaw noise on a floating base, rad (M8's default is 0.3; sim2sim's is none). |
init_pose | SimInitPose |
RolloutCellResult
What the worker measured for one cell. status/detail are filled in by the hub when it scores the terminal report; a worker leaves them out.
| Field | Type | Description |
|---|---|---|
name | string | |
command | RolloutCommand | |
seeds | integer | Episodes run. |
alive | integer | Episodes that stayed upright for the whole horizon. |
tracking | map of number | Mean measured / commanded ratio per commanded axis, over the seeds. |
episodes | array of RolloutEpisode | |
video_key | StorageKey | null | The cell's rendered mp4 (seed 0), from |
traj_key | StorageKey | null | The cell's trajectory JSON (seed 0). |
video_url | string (uri) | null (uri) | Presigned 1-hour GET (responses only). |
traj_url | string (uri) | null (uri) | Presigned 1-hour GET (responses only). |
status | GateVerdict | null | The hub's verdict under the harness rule (responses only). |
detail | string | null | The verdict's evidence, as written into the scorecard (responses only). |
kind | BatteryCellKind | |
fixed_base | boolean | The worker found no free joint in the robot (M17b). |
metrics | RolloutCellMetrics | |
videos | map of StorageKey | null | C2: seed 0 rendered from the founder's three fixed views ( |
video_urls | map of string (uri) | null | View → presigned 1-hour GET (responses only). The download is named the founder's way — |
poster_key | StorageKey | null | L1 — the cell's poster ( |
poster_t_s | number | null | When in seed 0's episode the poster was taken — its last control step (the horizon, or the fall). |
poster_url | string (uri) | null (uri) | Presigned 1-hour GET of |
RolloutRenderCheck
L1 — does a render's replay of seed 0 match the episode the source rollout measured? Compared per cell on the hub, once the render finished: upright, the fall time (±1 control step) and each tracked axis (±0.5 percentage points).
| Field | Type | Description |
|---|---|---|
matches | boolean | |
cells | array of object | |
name | string | |
matches | boolean | |
detail | string | null |
RolloutArtifactRequest
| Field | Type | Description |
|---|---|---|
name | string | Relative path under the rollout's prefix, e.g. |
size | integer | Bytes (≤ 256 MiB per artifact; a single PUT, no multipart). |
content_type | string |
|
RolloutArtifactUpload
| Field | Type | Description |
|---|---|---|
name | string | |
key | StorageKey | |
url | string (uri) | Presigned PUT; send exactly |
expires_at | string (date-time) |
RolloutReportStatus
The only transitions a worker may make — running → succeeded | failed.
CheckpointGateRow
| Field | Type | Description |
|---|---|---|
step | integer | |
rollout_id | Id | |
status | JobStatus | |
executor | RolloutExecutor | |
gates_passed | integer | null | |
gates_total | integer | null | |
pass_rate | number | null | |
alive_rate | number | null | |
cells | array of CheckpointGateCell | |
created_at | string (date-time) | |
finished_at | string (date-time) | null (date-time) | |
video | VideoMode | |
poster_url | string (uri) | null (uri) | L1 — the row's still: the poster of the first cell in battery order that commands a motion and has one ( |
render | RolloutRenderRef | null | L1 — the latest |
TripwireState
| Field | Type | Description |
|---|---|---|
fired | boolean | |
tripwire | GateTripwire | null | |
step | integer | null | The checkpoint at which it fired. |
rollout_id | string | null | |
value | number | null | |
at | string (date-time) | null (date-time) | When the hub asked the run to stop. |
CheckpointGateBest
P1 — the checkpoint a table's "best N / M at step" names. Ordered by: more cells passed; then more of the owning round's locked gates passed at that step; then the higher tracking on the round's declared axis (the mean over the cells commanding that axis, an overshoot counted as its mirror below 100 %); then the later step.
| Field | Type | Description |
|---|---|---|
step | integer | |
gates_passed | integer | Cells passing the hub's band rule at this step. |
gates_total | integer | |
round_gates_passed | integer | null | The owning round's counted (locked) gates passed at this step; null off a round. |
round_gates_total | integer | null | |
axis | string | null | The round's declared axis — its first counted |
tracking | number | null | The tracking score on |
decided_by | string | Which step of the rule separated this row from the runner-up: |
tied | integer | How many finished rows share the top cells count (1 when none tie). |
BatteryCell
One command cell: by default (kind: velocity) the body-frame velocity command held for the whole horizon. vx/vy in m/s, yaw in rad/s. A cell with every command zero is the stand test; only non-zero axes are tracked. M17b adds kind — joint_target and reach cells hold a target instead, given as an OFFSET from the robot's default pose so one battery is portable across robots.
| Field | Type | Description |
|---|---|---|
name | string | The harness's own cell name, e.g. |
vx | number | |
vy | number | |
yaw | number | |
kind | BatteryCellKind | |
joint_offsets | map of number | null |
|
ee_offset | array of number | null |
|
tolerance | number | null | PASS threshold on the settle error: radians (worst joint) for |
conditions | SimConditions | null | This cell's conditions, overriding the battery's key by key — e.g. |
score | BatteryCellScore | null | |
min_success | number | null |
|
posture_limits | PostureLimits | null |
BatteryProtocol
How a battery is measured (C2). m8 — M8's definitions: the robot's start pose, upright = never below 40% of the start height nor tilted past 60°, tracking the mean over every seed. sim2sim — the founder's harness: the standing keyframe with the policy's nominal joints, alive = the episode ends above 0.30 m and tilted under 25° (never below 0.25 m on the way; fractions of the standing height on another robot), the yaw rate from the net heading change, and tracking the median over the seeds that stayed alive (eval_c_matrix.py).
SimPush
--push N_S AT_S: at at_s seconds, impulse / total mass is added to the base's world velocity. (C2)
| Field | Type | Description |
|---|---|---|
impulse | number | Signed impulse, N·s. |
at_s | number | |
direction | string | The world axis; |
SimInitPose
--init-fallen: every seed starts fallen (C2). Seeds are allocated to the categories by their weights, stratified (20 seeds over {side_l: 1, side_r: 1} are exactly 10 + 10), and seed k of a category is the founder's sim2sim.py --init-fallen <category> --seed k: the category's roll/pitch ± jitter_deg, yaw anywhere, dropped from 0.28–0.40 m (scaled to the robot's standing height), joints uniform inside the soft limits, small random velocities. mid is a random-axis tilt of 50°–125°.
| Field | Type | Description |
|---|---|---|
kind | string | |
categories | object | Category → weight. Absent means the founder's training mix, supine 0.30 / prone 0.30 / side_l 0.15 / side_r 0.15 / mid 0.10. |
supine | number | |
prone | number | |
side_l | number | |
side_r | number | |
mid | number | |
jitter_deg | number |
RolloutCommand
| Field | Type | Description |
|---|---|---|
vx | number | |
vy | number | |
yaw | number |
RolloutEpisode
One seed of one cell, as measured.
| Field | Type | Description |
|---|---|---|
seed | integer | |
alive | boolean | Upright for the whole horizon. |
fell_at_s | number | null | When the robot stopped being upright, if it did. |
tracking | map of number | Measured / commanded per commanded axis ( |
metrics | RolloutCellMetrics | |
category | FallenCategory | null | The fallen category a |
BatteryCellKind
What a cell commands, and so how it is scored (M17b). velocity — the default, and every cell before M17b — holds a body-frame velocity and PASSes on upright + tracking in [0.85, 1.15]. joint_target and reach hold a joint-space / end-effector target and PASS when every seed's settle error (mean over the last 20% of the horizon) is within tolerance AND no control step left the soft joint limits. On a fixed base upright is true by construction, so every kind — including velocity, where a zero command means "hold the default pose" — is scored on settle error and limit violations instead, and a non-zero velocity command cannot PASS. C2 adds recovery: every seed starts fallen (conditions.init_pose), the episode never ends early, and the cell PASSes on the founder's stand-up rule (RECOVERY_V0_SPEC §5): at least min_success of the seeds stand up (tilt < 15°, base above 0.85 of the standing height, both feet on the ground, |ω| < 1 rad/s, held 0.5 s), the median stand-up time is ≤ 5 s, ≤ 10% of those that stood fall again (the last 0.5 s must hold), and the median peak PD torque demand is ≤ 90% of each joint's effort limit.
RolloutCellMetrics
M17b. What a joint_target / reach cell — or any cell on a fixed base — measured: settle_error (mean over the last 20% of the horizon), tracking_error (mean after a 1 s settle) and limit_violations (control steps with any joint outside its soft limit). Errors are the worst joint in radians, or metres for reach. A value is null when it could not be measured (the episode ended before the window) — never 0, which would read as perfect; the hub scores a null as FAIL. On a cell, errors are the worst seed and limit_violations the sum. C3 adds the posture metrics a posture cell (and every sim2sim episode on a floating base) measures: tilt_max_deg (the largest angle between the base's z axis and the world's over the episode), asym_max_deg (the worst mirrored joint pair's settled |mean(q_l − s·q_r)|, degrees), asym_pairs (how many pairs were found), drift_m (horizontal distance moved); on a cell, each is the median over the seeds. C3b adds, on an episode with a push: recover_s (seconds from the push until the base stayed within 3° of upright and under 5 cm/s across the floor to the end; the whole window left after the push when it fell or never settled) and recovered (1 when it settled, else 0).
CheckpointGateCell
| Field | Type | Description |
|---|---|---|
name | string | |
status | GateVerdict | null | |
seeds | integer | |
alive | integer | |
tracking | map of number | |
metrics | map of number | null | C3 — the cell's posture metrics as the median over its seeds ( |
poster_url | string (uri) | null (uri) | L1 — presigned 1-hour GET of the cell's poster ( |
RolloutRenderRef
L1 — the latest render of a gate rollout (renderRollout).
| Field | Type | Description |
|---|---|---|
rollout_id | Id | |
status | JobStatus | |
matches | boolean | null |
|
claimed_at | string (date-time) | null (date-time) | P1 — when a worker or a container took the render. |
expected_s | number | null | P1 — how long a render takes on this render's executor, measured on this hub (docs/API.md "A render's expected duration"): the median claim → finish of the hub's most recent finished renders on the same executor. Null before one has finished there — never a constant. |
expected_samples | integer | P1 — how many finished renders |
BatteryCellScore
How a velocity cell on a floating base is scored (C2). track (the default) is M8's rule — every seed upright AND every commanded axis inside [0.85, 1.15]. survive is eval_matrix.py's rule for its friction, push and zero-command blocks — every seed alive, tracking reported but not gated. posture (C3) is the stand battery's rule — every seed upright AND, as the median over the seeds, the maximum base tilt, the worst mirrored joint pair's left/right asymmetry and the drift across the floor each within posture_limits (defaults 3.0°, 2.0°, 0.25 m): a stand is judged by its posture, never by survival.
PostureLimits
C3 — a posture cell's bars, each on the median over the seeds. Null or absent means the hub's default. Set by the hub or the battery, never by the worker.
| Field | Type | Description |
|---|---|---|
tilt_max_deg | number | null | Maximum base tilt from vertical over the episode, degrees (default 3.0). |
asym_max_deg | number | null | Worst mirrored left/right joint pair, degrees: the settled mean of |
drift_m | number | null | Horizontal distance the base moved over the episode, metres (default 0.25). |
FallenCategory
The founder's fallen start categories (sim2sim.py --init-fallen). (C2)