# Import from Hugging Face

Point the checkpoint field at a public model repo, name the runtime, and evaluate — no export step, no container build.

*Provider workflow: Hugging Face.*

If your policy already speaks a known runtime, this is the shortest path
onto hardware: `Policy.checkpoint(hf=..., runtime=...)` names a Hugging
Face model repo and one of the managed runtimes — `openpi` or `lerobot`
— and Roborama fetches the checkpoint and serves it GPU-adjacent to the
cell. No export, no Dockerfile, no server of yours in the loop.

## Prerequisites

**A model repository, not a dataset repository.** The Hub hosts both
kinds under the same naming scheme: a model repo holds weights and
config, a dataset repo holds training episodes. The `hf:` field must
point at the model kind.

**Readable without authentication.** The `hf:` field carries a repo id
and nothing else — there is no token parameter anywhere in the API. On
the Hub side, private repos and gated models are real mechanisms
(per-user access approval, fine-grained read tokens scoped to a repo),
but Roborama has nowhere to accept a token, so private and gated models
cannot be imported directly today. Route those weights through
[Import from private storage](/docs/guides/import-from-private-storage/)
instead: your CI downloads with its own credentials and bakes the
checkpoint into a container image.

**The runtime's expected files, all present.**

| Runtime | Required files |
| --- | --- |
| `lerobot` | `config.json`, `model.safetensors`, `train_config.json`, plus the processor files (`policy_preprocessor.json`, `policy_postprocessor.json`) |
| `openpi` | a training-config name (e.g. `pi05_droid`) plus the checkpoint directory, with `assets/<asset_id>/norm_stats.json` present alongside the weights |

> **Missing norm stats are the classic silent failure:** A checkpoint that loads without its normalization statistics doesn't
> error — it acts on unnormalized inputs and produces confident, wrong
> actions. Check that `norm_stats.json` (openpi) or the processor files
> (lerobot) are in the repo before you import; the runtime cannot conjure
> them.

**Compatible hardware.** Declare the `action_space` and
`observation_contract` the checkpoint was trained for, and pin a robot
and scene that provide them — `aloha2-pro@fw1.1` in `cell-a@v1.0` in the
example below. Admission checks the declarations against the pins, so a
policy that expects `droid-3cam` needs a cell that has three cameras.

### Pin a version in the repo id

There is no `revision` field — you cannot pin a commit or a tag. The
supported convention is version-in-repo-id: publish an immutable release
repo per version (`acme/skill-v4`; the canonical openpi run uses
`pi/espresso-v7`) and never push to it again. Mutating a repo in place
makes results unattributable — two runs against the same repo id can be
two different policies, and nothing in the record will say so.

## Connect

One call packages the checkpoint, one call runs it. The `runtime` names
how the checkpoint is served; the declared contracts say what it needs
from the cell.

*Example: import and evaluate a Hub checkpoint*

**Python**

```python
import roborama  # reads ROBORAMA_API_KEY from the environment

policy = roborama.Policy.checkpoint(
    hf="acme/act-so101-pick-v2",
    runtime="lerobot",
    inference={"action_horizon": 100, "chunk_size": 100, "temp": 0.0},
    action_space="joint_delta_50hz",
    observation_contract="droid-3cam",
)

run = roborama.run(
    robot="aloha2-pro@fw1.1",
    environment="cell-a@v1.0",
    policy=policy,
    task="pick_place@v1",
    episodes=300,
    max_budget_usd=1000,
)
print(run.result())
# n=300  rate=0.843  ci95=(0.797, 0.881)
```

**TypeScript**

```typescript
import Roborama from "@roborama/sdk"; // reads ROBORAMA_API_KEY

const roborama = new Roborama();

const policy = {
  type: "checkpoint",
  hf: "acme/act-so101-pick-v2",
  runtime: "lerobot",
  inference: { action_horizon: 100, chunk_size: 100, temp: 0.0 },
  action_space: "joint_delta_50hz",
  observation_contract: "droid-3cam",
} as const;

const run = await roborama.runs.create({
  robot: "aloha2-pro@fw1.1",
  environment: "cell-a@v1.0",
  policy,
  task: "pick_place@v1",
  episodes: 300,
  max_budget_usd: 1000,
});
console.log(await run.result());
// n=300  rate=0.843  ci95=(0.797, 0.881)
```

**cURL**

```bash
# Declared contracts are validated before any motor moves.
curl -sS https://api.roborama.com/v1/runs \
  -H "Authorization: Bearer $ROBORAMA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "run",
    "robot": "aloha2-pro@fw1.1",
    "environment": "cell-a@v1.0",
    "policy": {
      "type": "checkpoint",
      "hf": "acme/act-so101-pick-v2",
      "runtime": "lerobot",
      "inference": {
        "action_horizon": 100,
        "chunk_size": 100,
        "temp": 0.0
      },
      "action_space": "joint_delta_50hz",
      "observation_contract": "droid-3cam"
    },
    "task": "pick_place@v1",
    "episodes": 300,
    "max_budget_usd": 1000
  }'
```

**Agent (tool-use payload)**

```json
{
  "type": "tool_use",
  "name": "roborama_run",
  "input": {
    "robot": "aloha2-pro@fw1.1",
    "environment": "cell-a@v1.0",
    "policy": {
      "type": "checkpoint",
      "hf": "acme/act-so101-pick-v2",
      "runtime": "lerobot",
      "inference": {
        "action_horizon": 100,
        "chunk_size": 100,
        "temp": 0.0
      },
      "action_space": "joint_delta_50hz",
      "observation_contract": "droid-3cam"
    },
    "task": "pick_place@v1",
    "episodes": 300,
    "max_budget_usd": 1000
  }
}
```

The `inference` dict — `action_horizon`, `chunk_size`, `temp` — is
recorded with the run, because reproducibility includes decoding: the
same checkpoint at a different action horizon is a different policy as
far as your statistics are concerned.

> **Fixture output:** The printed result is illustrative fixture data — your n, rate, and
> interval come from your episodes.

### The fetched copy

A checkpoint fetched for a run is cached to schedule and execute that
run, and is deleted when the run's `data.retention` window closes — or
immediately on `roborama.data.purge(run_id)`, receipted. Making the
source repo private after import does not delete an already-fetched
copy; purge does. Retention and the rest of the IP posture live in the
[data contract](/docs/concepts/data-contract/).

## Validate

Three layers, in the order they can fail:

1. **Local (mock).** `ROBORAMA_MOCK=1` or `mock=True` runs the full
   quote → submit → result loop against packaged fixtures — no API key,
   no hardware. Catch a malformed call at your desk.
2. **Admission (free).** At submission, the declared `action_space` and
   `observation_contract` are checked against the pinned robot and
   scene. A mismatch returns `contract_validation_failed` (422), and a
   rejected submission costs nothing.
3. **Cell startup.** The cell fetches the repo and loads the checkpoint
   under the named runtime. A repo that went private, or a checkpoint
   missing its files, surfaces here — after admission, before episodes.

*Example: the local dry-run*

**Python**

```python
import roborama

# Mock mode: no API key, no hardware. The whole loop runs
# offline against packaged fixtures — or set ROBORAMA_MOCK=1.
client = roborama.Client(mock=True)

quote = client.quote(
    robot="aloha2-pro",
    environment="cell-a",
    episodes=300,
)
print(quote)
# {robot_hours: 10.0, env_hours: 10.0, usd: 860, queue_eta: "2h"}

run = client.run(
    robot="aloha2-pro@fw1.1",
    environment="cell-a@v1.0",
    policy=roborama.Policy.checkpoint(
        hf="acme/act-so101-pick-v2",
        runtime="lerobot",
    ),
    task="pick_place@v1",
    episodes=300,
    max_budget_usd=1000,
)
print(run.result())
# n=300  rate=0.843  ci95=(0.797, 0.881)
```

**TypeScript**

```typescript
import Roborama from "@roborama/sdk";

// Mock mode: no API key, no hardware. The whole loop runs
// offline against packaged fixtures — or set ROBORAMA_MOCK=1.
const roborama = new Roborama({ mock: true });

const quote = await roborama.quote({
  robot: "aloha2-pro",
  environment: "cell-a",
  episodes: 300,
});
console.log(quote);
// { robot_hours: 10.0, env_hours: 10.0, usd: 860, queue_eta: "2h" }

const run = await roborama.runs.create({
  robot: "aloha2-pro@fw1.1",
  environment: "cell-a@v1.0",
  policy: {
    type: "checkpoint",
    hf: "acme/act-so101-pick-v2",
    runtime: "lerobot",
  },
  task: "pick_place@v1",
  episodes: 300,
  max_budget_usd: 1000,
});
console.log(await run.result());
// n=300  rate=0.843  ci95=(0.797, 0.881)
```

**cURL**

```bash
# Mock mode is an SDK feature — curl always talks to the live
# API. ROBORAMA_MOCK=1 flips any SDK entry point to packaged
# fixtures: no API key, no hardware, no spend.
ROBORAMA_MOCK=1 python - <<'PY'
import roborama

quote = roborama.quote(
    robot="aloha2-pro", environment="cell-a", episodes=300)
print(quote)

run = roborama.run(
    robot="aloha2-pro@fw1.1",
    environment="cell-a@v1.0",
    policy=roborama.Policy.checkpoint(
        hf="acme/act-so101-pick-v2", runtime="lerobot"),
    task="pick_place@v1",
    episodes=300,
    max_budget_usd=1000,
)
print(run.result())
PY
```

**Agent (tool-use payload)**

```json
{
  "type": "tool_use",
  "name": "roborama_quote",
  "input": {
    "robot": "aloha2-pro",
    "environment": "cell-a",
    "episodes": 300
  }
}
```

## Quote

A quote resolves both meters and the dollars before you commit
anything: quotes are free, rejected submissions are free, and nothing
bills until a motor moves. `max_budget_usd` is the mid-run hard stop.
Meters, tiers, and account-level controls are covered in
[Billing & budgets](/docs/guides/billing-and-budgets/).

*Example: quote before you run*

**Python**

```python
import roborama  # reads ROBORAMA_API_KEY from the environment

quote = roborama.quote(
    robot="g1-edu-pro",
    environment="kitchen-std",
    episodes=600,
)
print(quote)
# {robot_hours: 20.0, env_hours: 20.0, usd: 3740, queue_eta: "6h"}
```

**TypeScript**

```typescript
import Roborama from "@roborama/sdk"; // reads ROBORAMA_API_KEY

const roborama = new Roborama();

const quote = await roborama.quote({
  robot: "g1-edu-pro",
  environment: "kitchen-std",
  episodes: 600,
});
console.log(quote);
// { robot_hours: 20.0, env_hours: 20.0, usd: 3740, queue_eta: "6h" }
```

**cURL**

```bash
curl https://api.roborama.com/v1/quote \
  -H "Authorization: Bearer $ROBORAMA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "robot": "g1-edu-pro",
    "environment": "kitchen-std",
    "episodes": 600
  }'
```

**Agent (tool-use payload)**

```json
{
  "type": "tool_use",
  "name": "roborama_quote",
  "input": {
    "robot": "g1-edu-pro",
    "environment": "kitchen-std",
    "episodes": 600
  }
}
```

## Evaluate

Submission is the same [`run()`](/docs/primitives/run/) every packaging
plugs into — the Connect example above already passes the policy in.
Episodes tick as the cell works through them; follow live with
`run.watch()` or the SSE stream.

*Example: watch it live*

**Python**

```python
import roborama  # reads ROBORAMA_API_KEY from the environment

run = roborama.runs.get("run_8842")

for event in run.watch():   # live: episode ticker + WebRTC stream URLs
    print(event.episode, event.status)

stream = roborama.streams.get("cell-g1-04")
print(stream)
# {webrtc: "wss://streams.roborama.com/cells/cell-g1-04/webrtc",
#  mjpeg: "https://streams.roborama.com/cells/cell-g1-04/mjpeg",
#  viewer_token: "vt_7Kq2mHentXw4"}
```

**TypeScript**

```typescript
import Roborama from "@roborama/sdk"; // reads ROBORAMA_API_KEY

const roborama = new Roborama();

const run = await roborama.runs.get("run_8842");

for await (const event of run.watch()) {
  // live: episode ticker + WebRTC stream URLs
  console.log(event.episode, event.status);
}

const stream = await roborama.streams.get("cell-g1-04");
console.log(stream);
// { webrtc: "wss://streams.roborama.com/cells/cell-g1-04/webrtc",
//   mjpeg: "https://streams.roborama.com/cells/cell-g1-04/mjpeg",
//   viewer_token: "vt_7Kq2mHentXw4" }
```

**cURL**

```bash
# live event stream (server-sent events): episode ticker + status
curl -N https://api.roborama.com/v1/runs/run_8842/events \
  -H "Authorization: Bearer $ROBORAMA_API_KEY"

# cell video stream descriptor
curl https://api.roborama.com/v1/streams/cell-g1-04 \
  -H "Authorization: Bearer $ROBORAMA_API_KEY"
```

**Agent (tool-use payload)**

```json
[
  {
    "type": "tool_use",
    "name": "roborama_watch_run",
    "input": { "run_id": "run_8842" }
  },
  {
    "type": "tool_use",
    "name": "roborama_get_stream",
    "input": { "cell": "cell-g1-04" }
  }
]
```

## Inspect results

`run.result()` carries n, the success rate, and a Wilson 95% interval —
the canonical openpi import, `run_9101` (`pi/espresso-v7` on
`espresso@v1`), reports n=412, rate=0.85. Per-episode artifacts (video,
MCAP telemetry, replay spec) hang off `run.episodes`, and
`run.report(format="pdf")` renders the citable verification report.
Field-by-field detail: [data contract](/docs/concepts/data-contract/).

## Compare versions

Version-in-repo-id makes comparison mechanical: keep the declared test
configuration — robot, scene, task, contracts, `inference` — identical
and change only the repo id, `acme/act-so101-pick-v1` against
`acme/act-so101-pick-v2`. [`compare()`](/docs/primitives/compare/)
reports the paired delta; change anything else and you are comparing
test setups, not policies.

## Troubleshoot

| Code | Symptom | Fix |
| --- | --- | --- |
| `contract_validation_failed` | submission rejected — the checkpoint's processor expects a camera set the pinned robot lacks, or an action space its firmware doesn't accept | declare the contracts the checkpoint was trained for, and pin a robot and scene that provide them |
| `firmware_pin_unavailable` | the requested `model@firmware` isn't installed on any cell | list installed firmwares with `robots.list()` and pin one of them |
| `task_unknown` | task id or version typo — `pick_place@v2` when the catalogue has `pick_place@v1` | check the task catalogue and pin an existing version |
| `budget_exceeded` | run halted mid-flight at `max_budget_usd`; partial result kept, with honest n and interval | read the partial result; resubmit with a higher cap if the interval is too wide |
| `retention_expired` | artifacts fetched after the run's `data.retention` window closed, or after a purge | the data is gone by design — re-run if you need fresh episodes |

## Where next

- [Walkthrough: LeRobot from the Hub](/docs/guides/walkthrough-lerobot/) — this path end to end, with real numbers.
- [Upload a local checkpoint](/docs/guides/upload-a-checkpoint/) — publish local files to the Hub first, then import them the same way.
- [Import from private storage](/docs/guides/import-from-private-storage/) — the route for weights that can't be public.
- [Policies](/docs/concepts/policies/) — the declared contracts and the `inference` dict in depth.
