Skip to content
Docs menu

rollouts

Sim rollout jobs — watch a policy run in MuJoCo

11 operations · 34 schemas

GET /v1/rollouts

List sim rollouts

listRollouts · scope key:read

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

listRollouts parameters
NameInTypeDescription
statusqueryJobStatus
model_repoquerystring

length ≤ 130

owner/slug of the model repo.

run_idquerystring

length ≤ 64

Only rollouts attached to this run.

purposequeryRolloutPurpose

Only manual, gate, scan (C2) or render (L1) rollouts.

executorqueryRolloutExecutor

Only rollouts for this executor — lucen rollout worker polls executor=worker so it never claims one the hub sent to Modal. (C2)

limitqueryinteger

default 20 · ≥ 1 · ≤ 100

Page size.

cursorquerystring

length ≤ 512

Opaque cursor from the previous page's next_cursor.

Responses

listRollouts responses
StatusDescriptionBody
200

A page of rollouts.

RolloutPage
401

Missing or invalid credentials.

Problemapplication/problem+json
422

Request failed validation.

Problemapplication/problem+json

POST /v1/rollouts

Create a sim rollout

createRollout · scope key:train

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

application/json · required · RolloutCreate

createRollout request body
FieldTypeDescription
model_reporequiredstring

pattern ^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$

Model repo {owner}/{slug}.

robot_reporequiredstring

pattern ^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$

Robot repo {owner}/{slug} (its card provides the MJCF).

seedinteger

default 0 · ≥ 0

horizon_snumber

default 10 · > 0 · ≤ 30

Sim horizon in seconds (capped to keep rollouts ≤ $0.05).

batterystring | BatterySpec

smoke (default), full, or an inline BatterySpec. Resolved at creation and returned as Rollout.battery_spec. (M8) C2 adds the founder's harness batteries c_matrix, robust, recovery and watch (see BatterySpec).

conditionsSimConditions | null

Battery-level conditions on top of the battery's own, key by key (a cell's own still win) — e.g. {kd_scale: 1.2} to run robust at the founder's kd 1.2 working point. (C2)

seedsinteger | null

≥ 1 · ≤ 20

Overrides the battery's seed count (a 20-seed band scan of watch). (C2)

executorRolloutExecutor
checkpoint_stepinteger | null

≥ 0

Roll out checkpoints/step_<N>/ of the model repo instead of its root policy: the directory must hold exactly one .onnx (stamped with lucen_manifest), else 422 naming what it found. (C2)

run_idstring | null

length ≤ 64

The training run this rollout gates. Must be visible to the caller (422 otherwise — never a hint that it exists). On success the scorecard becomes Run.scorecard_key. (M8)

videoVideoMode | null

L1 — overrides the battery's video (a named battery renders full). Null keeps the battery's.

Responses

createRollout responses
StatusDescriptionBody
201

Rollout queued.

Rollout
401

Missing or invalid credentials.

Problemapplication/problem+json
403

Authenticated but not allowed (visibility, membership or scope).

Problemapplication/problem+json
404

Resource not found (or hidden from the caller).

Problemapplication/problem+json
409

State conflict (duplicate handle/slug, wrong repo kind, terminal job, ...).

Problemapplication/problem+json
422

Request failed validation.

Problemapplication/problem+json
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).

Problemapplication/problem+json

GET /v1/rollouts/{rollout_id}

Get a sim rollout

getRollout · scope key:read

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

getRollout parameters
NameInTypeDescription
rollout_idrequiredpathId

Rollout id (rollout_...).

Responses

getRollout responses
StatusDescriptionBody
200

The rollout.

Rollout
401

Missing or invalid credentials.

Problemapplication/problem+json
403

Authenticated but not allowed (visibility, membership or scope).

Problemapplication/problem+json
404

Resource not found (or hidden from the caller).

Problemapplication/problem+json

POST /v1/rollouts/{rollout_id}/claim

Claim a queued rollout (rollout worker protocol)

claimRollout · scope key:train

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

claimRollout parameters
NameInTypeDescription
rollout_idrequiredpathId

Rollout id (rollout_...).

Request body

application/json · required · RolloutClaim

claimRollout request body
FieldTypeDescription
workerrequiredstring

length 1–128

A name for the worker, recorded as claimed_by.

Responses

claimRollout responses
StatusDescriptionBody
200

Claimed; the rollout as it now stands.

Rollout
401

Missing or invalid credentials.

Problemapplication/problem+json
403

Authenticated but not allowed (visibility, membership or scope).

Problemapplication/problem+json
404

Resource not found (or hidden from the caller).

Problemapplication/problem+json
409

State conflict (duplicate handle/slug, wrong repo kind, terminal job, ...).

Problemapplication/problem+json
422

Request failed validation.

Problemapplication/problem+json

POST /v1/rollouts/{rollout_id}/artifacts

Presign uploads for a running rollout's artifacts

presignRolloutArtifacts · scope key:train

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

presignRolloutArtifacts parameters
NameInTypeDescription
rollout_idrequiredpathId

Rollout id (rollout_...).

Request body

application/json · required · RolloutArtifactsRequest

presignRolloutArtifacts request body
FieldTypeDescription
filesrequiredarray of RolloutArtifactRequest

items 1–100

Responses

presignRolloutArtifacts responses
StatusDescriptionBody
200

One presigned PUT per requested artifact.

RolloutArtifactsResponse
401

Missing or invalid credentials.

Problemapplication/problem+json
403

Authenticated but not allowed (visibility, membership or scope).

Problemapplication/problem+json
404

Resource not found (or hidden from the caller).

Problemapplication/problem+json
409

State conflict (duplicate handle/slug, wrong repo kind, terminal job, ...).

Problemapplication/problem+json
413

The rollout creator's storage quota would be exceeded (L0).

Problemapplication/problem+json
422

Request failed validation.

Problemapplication/problem+json

POST /v1/rollouts/{rollout_id}/report

Report progress or the result of a claimed rollout

reportRollout · scope key:train

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

reportRollout parameters
NameInTypeDescription
rollout_idrequiredpathId

Rollout id (rollout_...).

Request body

application/json · required · RolloutReport

reportRollout request body
FieldTypeDescription
statusRolloutReportStatus | null
log_chunkstring | null

length ≤ 262144

Engine output since the last report, ≤ 256 KiB of UTF-8.

errorstring | null

length ≤ 4000

Failure summary; expected with status: failed — a contract mismatch names itself here.

cellsarray of RolloutCellResult

items ≤ 24

Measurements per cell. Replaces any earlier list.

warningsarray of string

items ≤ 32

What the worker could not do, e.g. video unavailable: ffmpeg not on PATH.

engineobject | null

Versions and fingerprints: mujoco, onnxruntime, policy_sha256, contract_profile, renderer…

Responses

reportRollout responses
StatusDescriptionBody
200

Recorded; the rollout as it now stands.

Rollout
401

Missing or invalid credentials.

Problemapplication/problem+json
403

Authenticated but not allowed (visibility, membership or scope).

Problemapplication/problem+json
404

Resource not found (or hidden from the caller).

Problemapplication/problem+json
409

State conflict (duplicate handle/slug, wrong repo kind, terminal job, ...).

Problemapplication/problem+json
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).

Problemapplication/problem+json
422

Request failed validation.

Problemapplication/problem+json

POST /v1/rollouts/{rollout_id}/cancel

Cancel a sim rollout

cancelRollout · scope key:train

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

cancelRollout parameters
NameInTypeDescription
rollout_idrequiredpathId

Rollout id (rollout_...).

Responses

cancelRollout responses
StatusDescriptionBody
202

Canceled; the rollout as it now stands.

Rollout
401

Missing or invalid credentials.

Problemapplication/problem+json
403

Authenticated but not allowed (visibility, membership or scope).

Problemapplication/problem+json
404

Resource not found (or hidden from the caller).

Problemapplication/problem+json
409

State conflict (duplicate handle/slug, wrong repo kind, terminal job, ...).

Problemapplication/problem+json
502

Modal refused to terminate the call; nothing changed.

Problemapplication/problem+json

POST /v1/rollouts/{rollout_id}/render

Render an earlier rollout's videos

renderRollout · scope key:train

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

renderRollout parameters
NameInTypeDescription
rollout_idrequiredpathId

Rollout id (rollout_...).

Request body

application/json · RolloutRenderRequest

renderRollout request body
FieldTypeDescription
cellsarray of string | null

items ≤ 24

The source battery's cell names to render; null or absent = every cell.

videostring

one of full · poster

default "full"

executorRolloutExecutor | null

Null means the source rollout's executor.

Responses

renderRollout responses
StatusDescriptionBody
201

The render rollout, queued.

Rollout
401

Missing or invalid credentials.

Problemapplication/problem+json
403

Authenticated but not allowed (visibility, membership or scope).

Problemapplication/problem+json
404

Resource not found (or hidden from the caller).

Problemapplication/problem+json
409

State conflict (duplicate handle/slug, wrong repo kind, terminal job, ...).

Problemapplication/problem+json
422

Request failed validation.

Problemapplication/problem+json
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).

Problemapplication/problem+json

GET /v1/runs/{run_id}/gates

The sim gates a run's checkpoints got while it trained

getRunGates · scope key:read

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

getRunGates parameters
NameInTypeDescription
run_idrequiredpathId

Training run id (run_...).

Responses

getRunGates responses
StatusDescriptionBody
200

The gate table.

CheckpointGateTable
401

Missing or invalid credentials.

Problemapplication/problem+json
403

Authenticated but not allowed (visibility, membership or scope).

Problemapplication/problem+json
404

Resource not found (or hidden from the caller).

Problemapplication/problem+json

GET /v1/runs/{run_id}/scan

A band scan's table

getRunScan · scope key:read

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

getRunScan parameters
NameInTypeDescription
run_idrequiredpathId

Training run id (run_...).

scan_idquerystring

length ≤ 64

Responses

getRunScan responses
StatusDescriptionBody
200

The scan table.

CheckpointGateTable
401

Missing or invalid credentials.

Problemapplication/problem+json
403

Authenticated but not allowed (visibility, membership or scope).

Problemapplication/problem+json
404

Resource not found (or hidden from the caller).

Problemapplication/problem+json

POST /v1/runs/{run_id}/scan

Band-scan a run's checkpoints in simulation

scanRun · scope key:train

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

scanRun parameters
NameInTypeDescription
run_idrequiredpathId

Training run id (run_...).

Request body

application/json · required · RunScanRequest

scanRun request body
FieldTypeDescription
from_stepinteger | null

≥ 0

to_stepinteger | null

≥ 0

stepsarray of integer | null

items ≤ 50

seedsinteger

default 20 · ≥ 1 · ≤ 20

batterystring

one of watch · smoke · c_matrix · robust · recovery · full · stand

default "watch"

conditionsSimConditions | null
executorRolloutExecutor | null

Null means the run's own executor.

robot_repostring | null

pattern ^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$

Defaults to the run's recipe.robot.

videoVideoMode | null

L1 — null keeps the three views (full), which is what a band scan is for.

Responses

scanRun responses
StatusDescriptionBody
201

The scan's rollouts, queued.

CheckpointGateTable
401

Missing or invalid credentials.

Problemapplication/problem+json
403

Authenticated but not allowed (visibility, membership or scope).

Problemapplication/problem+json
404

Resource not found (or hidden from the caller).

Problemapplication/problem+json
409

State conflict (duplicate handle/slug, wrong repo kind, terminal job, ...).

Problemapplication/problem+json
422

Request failed validation.

Problemapplication/problem+json
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).

Problemapplication/problem+json

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

string

one of manual · gate · scan · render

default "manual"

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

object

RolloutPage fields
FieldTypeDescription
itemsrequiredarray of Rollout
next_cursorrequiredstring | null

RolloutCreate

object

RolloutCreate fields
FieldTypeDescription
model_reporequiredstring

pattern ^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$

Model repo {owner}/{slug}.

robot_reporequiredstring

pattern ^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$

Robot repo {owner}/{slug} (its card provides the MJCF).

seedinteger

default 0 · ≥ 0

horizon_snumber

default 10 · > 0 · ≤ 30

Sim horizon in seconds (capped to keep rollouts ≤ $0.05).

batterystring | BatterySpec

smoke (default), full, or an inline BatterySpec. Resolved at creation and returned as Rollout.battery_spec. (M8) C2 adds the founder's harness batteries c_matrix, robust, recovery and watch (see BatterySpec).

conditionsSimConditions | null

Battery-level conditions on top of the battery's own, key by key (a cell's own still win) — e.g. {kd_scale: 1.2} to run robust at the founder's kd 1.2 working point. (C2)

seedsinteger | null

≥ 1 · ≤ 20

Overrides the battery's seed count (a 20-seed band scan of watch). (C2)

executorRolloutExecutor
checkpoint_stepinteger | null

≥ 0

Roll out checkpoints/step_<N>/ of the model repo instead of its root policy: the directory must hold exactly one .onnx (stamped with lucen_manifest), else 422 naming what it found. (C2)

run_idstring | null

length ≤ 64

The training run this rollout gates. Must be visible to the caller (422 otherwise — never a hint that it exists). On success the scorecard becomes Run.scorecard_key. (M8)

videoVideoMode | null

L1 — overrides the battery's video (a named battery renders full). Null keeps the battery's.

Rollout

object

Rollout fields
FieldTypeDescription
idrequiredId
statusrequiredJobStatus
model_reporequiredRepoRef
robot_reporequiredRepoRef
seedrequiredinteger
horizon_srequirednumber
video_keyStorageKey | null

Rendered mp4 storage key (set on success; the first cell's).

traj_keyStorageKey | null

Trajectory JSON storage key (set on success; the battery index).

cost_usdnumber | null
errorstring | null
created_byUserPublic | null
created_atrequiredstring (date-time)
started_atstring (date-time) | null (date-time)
finished_atstring (date-time) | null (date-time)
batterystring

smoke, full, or inline.

battery_specBatterySpec
run_idstring | null

The run this rollout gates, if any.

scorecard_keyStorageKey | null

rollouts/{id}/scorecard.json once scored.

logs_keyStorageKey | null

Prefix of the worker's log chunks.

claimed_bystring | null
claimed_atstring (date-time) | null (date-time)
heartbeat_atstring (date-time) | null (date-time)
pricedboolean

True for a modal rollout — billed at the rollout function's CPU host from claim to finish (K2); false for a worker rollout, where cost_usd 0 means "not charged", not "free compute".

cellsarray of RolloutCellResult

Per-cell measurements and, once scored, verdicts.

warningsarray of string
engineobject | null
video_urlstring (uri) | null (uri)

Presigned 1-hour GET for video_key, when the object exists.

traj_urlstring (uri) | null (uri)
scorecard_urlstring (uri) | null (uri)
gates_passedinteger | null

PASS cells, once scored.

gates_totalinteger | null
executorRolloutExecutor
modal_call_idstring | null

The Modal call the hub spawned for a modal rollout.

dispatched_atstring (date-time) | null (date-time)
checkpoint_stepinteger | null

The checkpoint of the model repo this rollout ran, if not its root policy.

purposeRolloutPurpose
scan_idstring | null

The band scan this rollout belongs to (purpose: scan).

videoVideoMode
render_ofstring | null

purpose: render — the rollout whose videos this one rendered.

render_checkRolloutRenderCheck | null

purpose: render, once finished — whether the replay matches the measured episode.

RolloutClaim

object

RolloutClaim fields
FieldTypeDescription
workerrequiredstring

length 1–128

A name for the worker, recorded as claimed_by.

RolloutArtifactsRequest

object

RolloutArtifactsRequest fields
FieldTypeDescription
filesrequiredarray of RolloutArtifactRequest

items 1–100

RolloutArtifactsResponse

object

RolloutArtifactsResponse fields
FieldTypeDescription
uploadsrequiredarray of RolloutArtifactUpload

RolloutReport

object

One progress or terminal report from a rollout worker. Every field is optional.

RolloutReport fields
FieldTypeDescription
statusRolloutReportStatus | null
log_chunkstring | null

length ≤ 262144

Engine output since the last report, ≤ 256 KiB of UTF-8.

errorstring | null

length ≤ 4000

Failure summary; expected with status: failed — a contract mismatch names itself here.

cellsarray of RolloutCellResult

items ≤ 24

Measurements per cell. Replaces any earlier list.

warningsarray of string

items ≤ 32

What the worker could not do, e.g. video unavailable: ffmpeg not on PATH.

engineobject | null

Versions and fingerprints: mujoco, onnxruntime, policy_sha256, contract_profile, renderer…

RolloutRenderRequest

object

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.

RolloutRenderRequest fields
FieldTypeDescription
cellsarray of string | null

items ≤ 24

The source battery's cell names to render; null or absent = every cell.

videostring

one of full · poster

default "full"

executorRolloutExecutor | null

Null means the source rollout's executor.

CheckpointGateTable

object

Step × cells × verdict (C2): the gates a running run's checkpoints got (getRunGates) or one band scan (scanRun / getRunScan), oldest step first.

CheckpointGateTable fields
FieldTypeDescription
run_idrequiredId
purposerequiredRolloutPurpose
scan_idstring | null
batterystring | null
seedsinteger | null
every_stepsinteger | null

The run's gate cadence (getRunGates).

checkpointsarray of integer

Every checkpoint step the output repo holds, gated or not.

rowsrequiredarray of CheckpointGateRow
tripwireTripwireState | null
bestCheckpointGateBest | 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

object

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.

RunScanRequest fields
FieldTypeDescription
from_stepinteger | null

≥ 0

to_stepinteger | null

≥ 0

stepsarray of integer | null

items ≤ 50

seedsinteger

default 20 · ≥ 1 · ≤ 20

batterystring

one of watch · smoke · c_matrix · robust · recovery · full · stand

default "watch"

conditionsSimConditions | null
executorRolloutExecutor | null

Null means the run's own executor.

robot_repostring | null

pattern ^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$

Defaults to the run's recipe.robot.

videoVideoMode | null

L1 — null keeps the three views (full), which is what a band scan is for.

BatterySpec

object

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.

BatterySpec fields
FieldTypeDescription
cellsrequiredarray of BatteryCell

items 1–24

seedsinteger

default 2 · ≥ 1 · ≤ 20

Episodes per cell; seed k of a cell is RolloutCreate.seed + k.

horizon_snumber

> 0 · ≤ 30

Per-episode horizon; defaults to RolloutCreate.horizon_s.

protocolBatteryProtocol
conditionsSimConditions | null

Conditions for every cell (a cell's own override them key by key). (C2)

videoVideoMode

SimConditions

object

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.

SimConditions fields
FieldTypeDescription
delayinteger

≥ 0 · ≤ 10

--delay N: the joint target reaches the PD loop N control steps late (a FIFO seeded with the start pose).

delay_jitterarray of integer

items 2–2

--delay-jitter LO HI: a per-step delay drawn from {LO..HI}; exclusive with delay.

frictionnumber

> 0 · ≤ 5

--friction MU: the sliding friction of every geom (the MJCF's is usually 1.0).

kp_scalenumber

> 0 · ≤ 5

--kp-scale K: PD stiffness x K.

kd_scalenumber

> 0 · ≤ 5

--kd-scale K: PD damping x K.

pushSimPush
power_scalenumber

> 0 · ≤ 5

--power-scale K: the clipped action x K before it becomes a target (and what last_action feeds back).

headingboolean

--heading: the yaw-rate command becomes clip(k * wrap(yaw0 - yaw), ±0.6) — a heading hold the policy was not trained with.

heading_knumber

> 0 · ≤ 10

--heading-k: the heading loop's gain (default 0.5).

slewboolean

The deployment bridge's speed limit (slew.py, on in sim2sim unless --no-slew): every joint moves at 1 rad/s until it first reaches its target, then at its interface velocity_limit.

jitternumber

≥ 0 · ≤ 1

--jitter RAD: start-pose noise on every policy joint, clipped to its range (M8's default is 0.02 rad).

yaw_jitternumber

≥ 0 · ≤ 3.1416

Start yaw noise on a floating base, rad (M8's default is 0.3; sim2sim's is none).

init_poseSimInitPose

RolloutCellResult

object

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.

RolloutCellResult fields
FieldTypeDescription
namerequiredstring

length ≤ 40

commandrequiredRolloutCommand
seedsrequiredinteger

≥ 0

Episodes run.

aliverequiredinteger

≥ 0

Episodes that stayed upright for the whole horizon.

trackingmap of number

Mean measured / commanded ratio per commanded axis, over the seeds.

episodesarray of RolloutEpisode

items ≤ 100

video_keyStorageKey | null

The cell's rendered mp4 (seed 0), from presignRolloutArtifacts.

traj_keyStorageKey | null

The cell's trajectory JSON (seed 0).

video_urlstring (uri) | null (uri)

Presigned 1-hour GET (responses only).

traj_urlstring (uri) | null (uri)

Presigned 1-hour GET (responses only).

statusGateVerdict | null

The hub's verdict under the harness rule (responses only).

detailstring | null

The verdict's evidence, as written into the scorecard (responses only).

kindBatteryCellKind
fixed_baseboolean

default false

The worker found no free joint in the robot (M17b). upright is then vacuous and the hub scores on metrics instead.

metricsRolloutCellMetrics
videosmap of StorageKey | null

C2: seed 0 rendered from the founder's three fixed views (side, front, feet; right leg red, left leg blue), view → the key presignRolloutArtifacts returned. video_key is the side view.

video_urlsmap of string (uri) | null

View → presigned 1-hour GET (responses only). The download is named the founder's way — <policy>_<cell>_<PASS|FAIL>_<MMDD-HHMM>_<view>.mp4.

poster_keyStorageKey | null

L1 — the cell's poster (video: poster): one 320×240 PNG of seed 0 from the side view at poster_t_s, from presignRolloutArtifacts.

poster_t_snumber | null

≥ 0

When in seed 0's episode the poster was taken — its last control step (the horizon, or the fall).

poster_urlstring (uri) | null (uri)

Presigned 1-hour GET of poster_key (responses only).

RolloutRenderCheck

object

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).

RolloutRenderCheck fields
FieldTypeDescription
matchesrequiredboolean
cellsrequiredarray of object
namerequiredstring
matchesrequiredboolean
detailstring | null

RolloutArtifactRequest

object

RolloutArtifactRequest fields
FieldTypeDescription
namerequiredstring

length 1–200 · pattern ^[a-z0-9][a-z0-9._-]*(?:/[a-z0-9][a-z0-9._-]*)*$

Relative path under the rollout's prefix, e.g. cells/vx-plus-0p30/video.mp4.

sizerequiredinteger

≥ 0 · ≤ 268435456

Bytes (≤ 256 MiB per artifact; a single PUT, no multipart).

content_typestring

one of video/mp4 · application/json · text/plain · application/octet-stream · image/png

default "application/octet-stream"

image/png since L1 (a cell's poster).

RolloutArtifactUpload

object

RolloutArtifactUpload fields
FieldTypeDescription
namerequiredstring
keyrequiredStorageKey
urlrequiredstring (uri)

Presigned PUT; send exactly size bytes with Content-Type as declared.

expires_atrequiredstring (date-time)

RolloutReportStatus

string

one of running · succeeded · failed

The only transitions a worker may make — running → succeeded | failed.

CheckpointGateRow

object

CheckpointGateRow fields
FieldTypeDescription
steprequiredinteger
rollout_idrequiredId
statusrequiredJobStatus
executorRolloutExecutor
gates_passedinteger | null
gates_totalinteger | null
pass_ratenumber | null
alive_ratenumber | null
cellsarray of CheckpointGateCell
created_atstring (date-time)
finished_atstring (date-time) | null (date-time)
videoVideoMode
poster_urlstring (uri) | null (uri)

L1 — the row's still: the poster of the first cell in battery order that commands a motion and has one (c_matrix: vx+0.15), else the first cell's; null when the gate rendered no poster.

renderRolloutRenderRef | null

L1 — the latest renderRollout of this gate, if anyone asked for its videos.

TripwireState

object

TripwireState fields
FieldTypeDescription
firedrequiredboolean
tripwireGateTripwire | null
stepinteger | null

The checkpoint at which it fired.

rollout_idstring | null
valuenumber | null
atstring (date-time) | null (date-time)

When the hub asked the run to stop.

CheckpointGateBest

object

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.

CheckpointGateBest fields
FieldTypeDescription
steprequiredinteger
gates_passedrequiredinteger

Cells passing the hub's band rule at this step.

gates_totalrequiredinteger
round_gates_passedinteger | null

The owning round's counted (locked) gates passed at this step; null off a round.

round_gates_totalinteger | null
axisstring | null

one of vx · vy · yaw

The round's declared axis — its first counted tracking / tracking_gap gate's axis; null when no gate names one.

trackingnumber | null

The tracking score on axis at this step (1.0 = on the command); null without an axis or a tracked cell.

decided_byrequiredstring

one of only · cells · round_gates · tracking · step

Which step of the rule separated this row from the runner-up: only — one finished row; cells, round_gates, tracking — that criterion; step — everything tied and the later step won.

tiedrequiredinteger

≥ 1

How many finished rows share the top cells count (1 when none tie).

BatteryCell

object

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.

BatteryCell fields
FieldTypeDescription
namerequiredstring

length 1–40 · pattern ^[A-Za-z0-9][A-Za-z0-9+.&_-]*$

The harness's own cell name, e.g. vx+0.30, vy-0.10, stand.

vxnumber

default 0

vynumber

default 0

yawnumber

default 0

kindBatteryCellKind
joint_offsetsmap of number | null

joint_target cells: the target as radians from the robot's default pose, per joint name; joints left out stay at the default pose, and an empty/absent map is the hold test. An offset, not an absolute angle, is what lets one battery run on two robots. The worker clamps the result to the soft limits and says so in warnings[] — a policy is never scored against a pose the robot may not take. (M17b)

ee_offsetarray of number | null

items 3–3

reach cells: the target as metres from where the end effector sits at the default pose, [dx, dy, dz] in the base frame. Needs an interface.end_effectors[] entry with a site or body in the MJCF. (M17b)

tolerancenumber | null

> 0

PASS threshold on the settle error: radians (worst joint) for joint_target, metres for reach. Null means the hub's default, 0.10 rad / 0.05 m. Ignored by velocity cells on a floating base, which are gated on the tracking band. (M17b)

conditionsSimConditions | null

This cell's conditions, overriding the battery's key by key — e.g. {friction: 0.4} for one cell of a friction sweep. (C2)

scoreBatteryCellScore | null
min_successnumber | null

> 0 · ≤ 1

recovery cells: the fraction of seeds that must stand up. Null means the founder's per-category bar (RECOVERY_V0_SPEC §5 ①: supine and prone 0.90, side 0.80, mid 0.70; a mixed cell takes the strictest of its categories). (C2)

posture_limitsPostureLimits | null

BatteryProtocol

string

one of m8 · sim2sim

default "m8"

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

object

--push N_S AT_S: at at_s seconds, impulse / total mass is added to the base's world velocity. (C2)

SimPush fields
FieldTypeDescription
impulserequirednumber

≥ -100 · ≤ 100

Signed impulse, N·s.

at_srequirednumber

≥ 0 · ≤ 30

directionstring

one of x · y

default "y"

The world axis; y is sim2sim's lateral push.

SimInitPose

object

--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°.

SimInitPose fields
FieldTypeDescription
kindstring

one of fallen

default "fallen"

categoriesobject

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.

supinenumber

≥ 0

pronenumber

≥ 0

side_lnumber

≥ 0

side_rnumber

≥ 0

midnumber

≥ 0

jitter_degnumber

default 15 · ≥ 0 · ≤ 90

RolloutCommand

object

RolloutCommand fields
FieldTypeDescription
vxrequirednumber
vyrequirednumber
yawrequirednumber

RolloutEpisode

object

One seed of one cell, as measured.

RolloutEpisode fields
FieldTypeDescription
seedrequiredinteger
aliverequiredboolean

Upright for the whole horizon.

fell_at_snumber | null

When the robot stopped being upright, if it did.

trackingmap of number

Measured / commanded per commanded axis (vx, vy, yaw).

metricsRolloutCellMetrics
categoryFallenCategory | null

The fallen category a recovery episode started in. (C2)

BatteryCellKind

string

one of velocity · joint_target · reach · recovery

default "velocity"

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

map of number | null | null

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

object

CheckpointGateCell fields
FieldTypeDescription
namerequiredstring
statusGateVerdict | null
seedsrequiredinteger
aliverequiredinteger
trackingmap of number
metricsmap of number | null

C3 — the cell's posture metrics as the median over its seeds (tilt_max_deg, asym_max_deg, drift_m, and C3b's recover_s on a push cell), when the episodes measured them; what a round's gates read.

poster_urlstring (uri) | null (uri)

L1 — presigned 1-hour GET of the cell's poster (video: poster), when it has one.

RolloutRenderRef

object

L1 — the latest render of a gate rollout (renderRollout).

RolloutRenderRef fields
FieldTypeDescription
rollout_idrequiredId
statusrequiredJobStatus
matchesboolean | null

render_check.matches once the render finished.

claimed_atstring (date-time) | null (date-time)

P1 — when a worker or a container took the render.

expected_snumber | 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_samplesinteger

≥ 0

P1 — how many finished renders expected_s is the median of.

BatteryCellScore

string

one of track · survive · posture

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

object

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.

PostureLimits fields
FieldTypeDescription
tilt_max_degnumber | null

> 0

Maximum base tilt from vertical over the episode, degrees (default 3.0).

asym_max_degnumber | null

> 0

Worst mirrored left/right joint pair, degrees: the settled mean of q_left − s·q_right, where s is the pair's mirror sign read from the joint axes (default 2.0).

drift_mnumber | null

> 0

Horizontal distance the base moved over the episode, metres (default 0.25).

FallenCategory

string

one of supine · prone · side_l · side_r · mid

The founder's fallen start categories (sim2sim.py --init-fallen). (C2)