# Policies

Three ways to hand us a policy — container, checkpoint, endpoint — each behind the same declared contracts, validated before any motor moves.

A policy enters the facility in one of three packagings. All three sit behind
the same two declarations — an `action_space` and an `observation_contract` —
and both are validated against the robot and scene *before any motor moves*.
A policy that expects three cameras and gets two fails at submission with
`contract_validation_failed`, not at episode 40 with a mystery cluster.

## The three packagings

*Example: roborama.Policy*

**Python**

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

# 1. Container — runs in-facility, GPU-adjacent
container = roborama.Policy.container(
    image="ghcr.io/acme/skill:v4",
    action_space="joint_delta_50hz",
    observation_contract="droid-3cam",
)

# 2. Checkpoint — known runtimes: openpi, lerobot
checkpoint = roborama.Policy.checkpoint(
    hf="acme/skill-v4", runtime="openpi")

# 3. Endpoint — customer-hosted inference; measured RTT logged per step
endpoint = roborama.Policy.endpoint(
    url="https://inference.acme.ai/act",
    latency_budget_ms=80,
    fallback="halt",
)

# Declared contracts are validated before any motor moves.
run = roborama.run(
    robot="g1-edu-pro@fw2.3",
    environment="kitchen-std@v1.2",
    policy=container,
    task="pick_place@v1",
    episodes="auto(ci=0.95, moe=0.03)",
)
print(run.id)
```

**TypeScript**

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

const roborama = new Roborama();

// 1. Container — runs in-facility, GPU-adjacent
const container = {
  type: "container",
  image: "ghcr.io/acme/skill:v4",
  action_space: "joint_delta_50hz",
  observation_contract: "droid-3cam",
} as const;

// 2. Checkpoint — known runtimes: openpi, lerobot
const checkpoint = {
  type: "checkpoint",
  hf: "acme/skill-v4",
  runtime: "openpi",
} as const;

// 3. Endpoint — customer-hosted inference; measured RTT logged per step
const endpoint = {
  type: "endpoint",
  url: "https://inference.acme.ai/act",
  latency_budget_ms: 80,
  fallback: "halt",
} as const;

// Declared contracts are validated before any motor moves.
const run = await roborama.runs.create({
  robot: "g1-edu-pro@fw2.3",
  environment: "kitchen-std@v1.2",
  policy: container,
  task: "pick_place@v1",
  episodes: "auto(ci=0.95, moe=0.03)",
});
console.log(run.id, checkpoint.type, endpoint.type);
```

**cURL**

```bash
# Declared contracts are validated before any motor moves.
# policy.type: "container" | "checkpoint" | "endpoint"
curl https://api.roborama.com/v1/runs \
  -H "Authorization: Bearer $ROBORAMA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "run",
    "robot": "g1-edu-pro@fw2.3",
    "environment": "kitchen-std@v1.2",
    "policy": {
      "type": "endpoint",
      "url": "https://inference.acme.ai/act",
      "latency_budget_ms": 80,
      "fallback": "halt"
    },
    "task": "pick_place@v1",
    "episodes": "auto(ci=0.95, moe=0.03)"
  }'
```

**Agent (tool-use payload)**

```json
{
  "type": "tool_use",
  "name": "roborama_run",
  "input": {
    "robot": "g1-edu-pro@fw2.3",
    "environment": "kitchen-std@v1.2",
    "policy": {
      "type": "endpoint",
      "url": "https://inference.acme.ai/act",
      "latency_budget_ms": 80,
      "fallback": "halt"
    },
    "task": "pick_place@v1",
    "episodes": "auto(ci=0.95, moe=0.03)"
  }
}
```

| Packaging | Runs | Notes |
| --- | --- | --- |
| `Policy.container(image, action_space, observation_contract)` | in-facility, GPU-adjacent | any OCI image; lowest latency, no network in the loop |
| `Policy.checkpoint(hf=..., runtime=..., inference=...)` | in-facility, managed runtime | known runtimes: `openpi`, `lerobot`; the `inference` dict pins decoding |
| `Policy.endpoint(url, latency_budget_ms, fallback)` | your infrastructure | measured round-trip time logged per step; weights never leave your side |

**Container** is the default for anything custom: ship an OCI image, we run
it GPU-adjacent to the cell. **Checkpoint** is the low-friction path when
your policy already speaks a known runtime — point at a Hugging Face repo and
name the runtime. **Endpoint** inverts the trust: inference stays on your
infrastructure and the facility streams observations to your URL. That
choice is also an IP posture — see the
[data contract](/docs/concepts/data-contract/).

Checkpoints also pin how they're decoded. The `inference` dict —
`{"action_horizon": 50, "chunk_size": 50, "temp": 0.0}` — is recorded with
the run, because reproducibility includes decoding, not just weights: the
same checkpoint at a different action horizon is a different policy as far
as your statistics are concerned, the same way a firmware bump is a
different robot.

## Declared contracts

`action_space` names the command interface and rate the policy emits:
`joint_delta_50hz`, `joint_abs_20hz`, `ee_pose_10hz`, and the rest of the
catalogued spaces. `observation_contract` names the camera set and
proprioception schema it expects — `droid-3cam` is three RGB views plus the
joint-state schema. Validation checks both against the pinned robot and
scene: the declared space must be one the firmware accepts, and every feed
the contract names must exist in the cell.

The contracts are not paperwork; they are what makes a result attributable.
When a policy fails, you want to know it failed on physics, not on a silently
resampled action rate or a missing camera.

## Endpoint latency and the fallback

An endpoint policy puts your network in the control loop, so the latency
budget is explicit: `latency_budget_ms=80` means any step whose round trip
exceeds 80 ms triggers the declared `fallback` — `"halt"` freezes the arm and
ends the episode, which then lands in its own cluster rather than polluting
the physics statistics. Measured RTT is logged per step to `/events`, so you
can tell a policy problem from a network problem after the fact. Persistent
unreachability fails the run with `policy_endpoint_timeout`.

> **Budget the tail, not the mean:** A 40 ms median with a 200 ms p99 will halt episodes at a rate that dominates
> your failure statistics. Set `latency_budget_ms` from your p99, or package as
> a container and take the network out of the loop.

## Where next

- [run()](/docs/primitives/run/) — the call every packaging plugs into.
- [Data contract](/docs/concepts/data-contract/) — retention, `train_on_failures`, and who sees your weights.
- [Error codes](/docs/errors/) — `contract_validation_failed` and `policy_endpoint_timeout` in detail.
