# Upload a local checkpoint

There is no upload API — publish the checkpoint directory to a Hub repo you control, or bake it into an image, then evaluate like any other policy.

*Provider workflow: Local upload, Hugging Face, Docker.*

There is no direct artifact upload today — no upload call, no signed
upload URLs, and no size limits to document, because nothing is
uploaded to Roborama directly. A checkpoint sitting on your machine
still gets onto hardware through two supported routes: publish the
directory to a Hugging Face repo you control and
[import it](/docs/guides/import-from-hugging-face/), or copy it into a
container image and [run that](/docs/guides/run-your-container/). Both
end at the same place — a field on the `Policy` object — and everything
downstream is identical.

## Prerequisites

- **The complete checkpoint directory** — weights plus config plus
  normalization and processor files. A weights file alone does not
  identify the policy.
- **Route 1:** a Hugging Face account, `huggingface-cli`, and a policy
  that speaks a managed runtime — `openpi` or `lerobot`.
- **Route 2:** Docker and a registry Roborama can pull from — works for
  any runtime, including fully custom serving code.

## Connect

### Route 1 — publish to the Hub (recommended for supported runtimes)

Create a repo per version — the convention is version-in-repo-id, since
no `revision` field exists — and upload the directory:

```bash
huggingface-cli upload acme/skill-v4 ./pretrained_model
```

For `lerobot`, the uploaded directory must contain exactly what the
runtime expects to load:

```text
config.json
model.safetensors
train_config.json
policy_preprocessor.json
policy_postprocessor.json
```

(`PreTrainedPolicy.push_model_to_hub` publishes the same layout from
code.) The repo must be readable without authentication — the `hf:`
field carries no token. From here the path is exactly
[Import from Hugging Face](/docs/guides/import-from-hugging-face/):
point `Policy.checkpoint(hf="acme/skill-v4", runtime="lerobot")` at
the new repo.

### Route 2 — bake into a container image

When the runtime isn't managed, or the weights shouldn't be public,
copy the directory into an image next to your serving code:

```dockerfile
FROM pytorch/pytorch:2.4.0-cuda12.4-cudnn9-runtime
WORKDIR /policy
COPY pretrained_model/ /policy/checkpoint/
COPY serve.py /policy/serve.py
CMD ["python", "serve.py", "--checkpoint", "checkpoint"]
```

Build, push, and continue as
[Run your container](/docs/guides/run-your-container/):
`Policy.container(image=...)` with your declared contracts.

On either route, 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. Privating or deleting the source repo later does not delete
an already-fetched copy; purge does
([data contract](/docs/concepts/data-contract/)).

## Validate

The three layers are the same on both routes. Mock mode
(`ROBORAMA_MOCK=1`, or `mock=True` on the client) dry-runs the loop
with no API key and no hardware — worth doing before you publish
anything. Admission then checks the declared `action_space` and
`observation_contract` against the pinned robot and scene; a mismatch
is rejected with `contract_validation_failed`, free. Cell startup is
where the checkpoint fetch or container start is actually exercised.
The layers are laid out in
[Bring your policy](/docs/guides/bring-your-policy/).

*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
  }
}
```

> **Fixture output:** The quote and result this snippet prints are packaged fixture data —
> mock mode's output shapes are exact, the numbers are not yours.

## Quote

Quote before you run: both meters, the dollars, and the queue ETA,
free, valid seven days. Nothing bills until a motor moves —
[Billing & budgets](/docs/guides/billing-and-budgets/) covers the
meters and the hard stops.

*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

Submit with [`run()`](/docs/primitives/run/) — `Policy.checkpoint` on
route 1, `Policy.container` on route 2 — and follow episodes live with
`run.watch()` or the SSE stream.

## Inspect results

`run.result()` returns n, the success rate, and a Wilson ci95; each
episode carries video, MCAP telemetry, and a replay spec; and
`run.report(format="pdf")` renders the citable record. Retention and
export formats: [data contract](/docs/concepts/data-contract/).

## Compare versions

Publish `acme/skill-v5` — or push a `:v5` image — keep every pin the
same, and [`compare()`](/docs/primitives/compare/) reports the paired
delta between the two: same declared test, different artifact.

## Troubleshoot

| Code | Symptom | Fix |
| --- | --- | --- |
| `contract_validation_failed` | submission rejected — the declared contracts don't match the pinned robot and scene | declare what the checkpoint was trained for; pin hardware that provides it |
| `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 in the submission | check the task catalogue and pin an existing version |
| `budget_exceeded` | run halted at `max_budget_usd`; partial result kept | read the partial n and interval; resubmit with a higher cap if needed |
| `retention_expired` | artifacts fetched after the retention window, or after a purge | the data is gone by design — re-run for fresh episodes |

## Where next

- [Import from Hugging Face](/docs/guides/import-from-hugging-face/) — route 1 continues here.
- [Run your container](/docs/guides/run-your-container/) — route 2 continues here.
- [Import from private storage](/docs/guides/import-from-private-storage/) — the same bake, but CI does the copying from a bucket.
- [Bring your policy](/docs/guides/bring-your-policy/) — all six paths side by side.
