# Tasks & method transfer

A claim becomes testable as a Task Spec — instrument-bound predicates, a piloted method, and a frozen, mutually signed revision.

"Our robot makes espresso" is not testable as stated. It becomes testable as
a **Task Spec**: a declarative, versioned protocol whose success predicates
are measurable by the cell's instrumentation. Onboarding a claim is a
first-class API flow, not a sales process — and the spec, not a conversation,
is what every verification report cites.

## Anatomy of a Task Spec

*Example: roborama.tasks.create() + lifecycle*

**Python**

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

# A claim becomes testable as a Task Spec: declarative, versioned, with
# success predicates measurable by the cell's instrumentation.
task = roborama.tasks.create(
    name="espresso",
    visibility="private",  # "private" | "published" (comparable, citable)
    initial_conditions={
        "machine": {"object": "kit/acme-breville",
                    "pose": "P1", "tol_mm": 20},
        "cup": {"object": "catalogue/cup-std-08", "randomize": "zone-A"},
    },
    success_predicates=[               # each must bind to an instrument
        {"cup_on_tray": "gt.pose(cup) within tray_zone"},     # mocap
        {"liquid_mass": "scale.delta between 25 and 40 g"},   # load cell
        {"no_spill": "vision.spill_area < 2 cm2"},            # scene cams
        {"t_complete": "episode.duration < 180 s"},
    ],
    instructions={  # language-conditioned policies are tested
        "sampled_per_episode": [  # against a distribution, not one phrasing
            "make an espresso", "brew me a coffee", "fais un espresso",
        ],
        "held_out": ["prepare a single shot"],  # reported separately
    },
    stages=[                           # long-horizon tasks score progress
        {"grasp_cup": "gt.pose(cup) in gripper"},
        {"cup_placed": "gt.pose(cup) within drip_zone"},
        {"extraction": "scale.delta > 0 within 60s"},
        {"served": "all success_predicates"},
    ],
    envelope={                         # the claim's declared boundaries
        "lighting": ["3000K", "5600K"], "distractors": "set-B",
        "out_of_scope": ["oat_milk"],
    },
    baseline={"internal_trials": 30, "internal_rate": 0.87},  # private
)
print(task.status)  # "draft"

# Lifecycle — the protocol agreement, encoded:
pilot = task.pilot(robot="g1-edu-pro@fw2.3", episodes=25)  # calibration
print(pilot.stream_url)  # customer watches live

task.amend(  # iterate on the method while piloting
    success_predicates=[
        {"cup_on_tray": "gt.pose(cup) within tray_zone"},
        # widened after the pilot:
        {"liquid_mass": "scale.delta between 22 and 42 g"},
        {"no_spill": "vision.spill_area < 2 cm2"},
        {"t_complete": "episode.duration < 180 s"},
    ],
)

task.freeze()   # -> "espresso@v1": immutable, mutually signed;
                #    every verification report cites the frozen rev
print(task.status)  # "frozen", rev "espresso@v1"
```

**TypeScript**

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

const roborama = new Roborama();

// A claim becomes testable as a Task Spec: declarative, versioned, with
// success predicates measurable by the cell's instrumentation.
const task = await roborama.tasks.create({
  name: "espresso",
  visibility: "private", // "private" | "published" (comparable, citable)
  initial_conditions: {
    machine: { object: "kit/acme-breville", pose: "P1", tol_mm: 20 },
    cup: { object: "catalogue/cup-std-08", randomize: "zone-A" },
  },
  success_predicates: [
    { cup_on_tray: "gt.pose(cup) within tray_zone" }, // mocap
    { liquid_mass: "scale.delta between 25 and 40 g" }, // load cell
    { no_spill: "vision.spill_area < 2 cm2" }, // scene cams
    { t_complete: "episode.duration < 180 s" },
  ],
  instructions: {
    sampled_per_episode: ["make an espresso", "brew me a coffee",
                          "fais un espresso"],
    held_out: ["prepare a single shot"], // unseen, reported separately
  },
  stages: [
    { grasp_cup: "gt.pose(cup) in gripper" },
    { cup_placed: "gt.pose(cup) within drip_zone" },
    { extraction: "scale.delta > 0 within 60s" },
    { served: "all success_predicates" },
  ],
  envelope: {
    lighting: ["3000K", "5600K"],
    distractors: "set-B",
    out_of_scope: ["oat_milk"],
  },
  baseline: { internal_trials: 30, internal_rate: 0.87 },
});
console.log(task.status); // "draft"

// Lifecycle — the protocol agreement, encoded:
const pilot = await task.pilot({ robot: "g1-edu-pro@fw2.3", episodes: 25 });
console.log(pilot.stream_url); // customer watches the calibration live

await task.amend({
  success_predicates: [
    { cup_on_tray: "gt.pose(cup) within tray_zone" },
    // widened after the pilot:
    { liquid_mass: "scale.delta between 22 and 42 g" },
    { no_spill: "vision.spill_area < 2 cm2" },
    { t_complete: "episode.duration < 180 s" },
  ],
});

await task.freeze(); // -> "espresso@v1": immutable, mutually signed
console.log(task.status); // "frozen", rev "espresso@v1"
```

**cURL**

```bash
# 1. Create the Task Spec (status: draft)
curl https://api.roborama.com/v1/tasks \
  -H "Authorization: Bearer $ROBORAMA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "espresso",
    "visibility": "private",
    "initial_conditions": {
      "machine": {"object": "kit/acme-breville",
                  "pose": "P1", "tol_mm": 20},
      "cup": {"object": "catalogue/cup-std-08", "randomize": "zone-A"}
    },
    "success_predicates": [
      {"cup_on_tray": "gt.pose(cup) within tray_zone"},
      {"liquid_mass": "scale.delta between 25 and 40 g"},
      {"no_spill": "vision.spill_area < 2 cm2"},
      {"t_complete": "episode.duration < 180 s"}
    ],
    "instructions": {
      "sampled_per_episode": ["make an espresso", "brew me a coffee",
                              "fais un espresso"],
      "held_out": ["prepare a single shot"]
    },
    "stages": [
      {"grasp_cup": "gt.pose(cup) in gripper"},
      {"cup_placed": "gt.pose(cup) within drip_zone"},
      {"extraction": "scale.delta > 0 within 60s"},
      {"served": "all success_predicates"}
    ],
    "envelope": {
      "lighting": ["3000K", "5600K"],
      "distractors": "set-B",
      "out_of_scope": ["oat_milk"]
    },
    "baseline": {"internal_trials": 30, "internal_rate": 0.87}
  }'

# 2. Pilot — a calibration session the customer watches live
curl https://api.roborama.com/v1/tasks/task_espresso_01/pilot \
  -H "Authorization: Bearer $ROBORAMA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"robot": "g1-edu-pro@fw2.3", "episodes": 25}'

# 3. Amend — iterate on the method while piloting
curl https://api.roborama.com/v1/tasks/task_espresso_01/amend \
  -H "Authorization: Bearer $ROBORAMA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "success_predicates": [
      {"cup_on_tray": "gt.pose(cup) within tray_zone"},
      {"liquid_mass": "scale.delta between 22 and 42 g"},
      {"no_spill": "vision.spill_area < 2 cm2"},
      {"t_complete": "episode.duration < 180 s"}
    ]
  }'

# 4. Freeze — immutable, mutually signed; reports cite espresso@v1
curl https://api.roborama.com/v1/tasks/task_espresso_01/freeze \
  -H "Authorization: Bearer $ROBORAMA_API_KEY" \
  -X POST
```

**Agent (tool-use payload)**

```json
{
  "type": "tool_use",
  "name": "roborama_tasks_create",
  "input": {
    "name": "espresso",
    "visibility": "private",
    "initial_conditions": {
      "machine": { "object": "kit/acme-breville", "pose": "P1",
                   "tol_mm": 20 },
      "cup": { "object": "catalogue/cup-std-08", "randomize": "zone-A" }
    },
    "success_predicates": [
      { "cup_on_tray": "gt.pose(cup) within tray_zone" },
      { "liquid_mass": "scale.delta between 25 and 40 g" },
      { "no_spill": "vision.spill_area < 2 cm2" },
      { "t_complete": "episode.duration < 180 s" }
    ],
    "instructions": {
      "sampled_per_episode": ["make an espresso", "brew me a coffee",
                              "fais un espresso"],
      "held_out": ["prepare a single shot"]
    },
    "stages": [
      { "grasp_cup": "gt.pose(cup) in gripper" },
      { "cup_placed": "gt.pose(cup) within drip_zone" },
      { "extraction": "scale.delta > 0 within 60s" },
      { "served": "all success_predicates" }
    ],
    "envelope": {
      "lighting": ["3000K", "5600K"],
      "distractors": "set-B",
      "out_of_scope": ["oat_milk"]
    },
    "baseline": { "internal_trials": 30, "internal_rate": 0.87 }
  }
}
```

| Field | Notes |
| --- | --- |
| `visibility` | private tasks stay the customer's method; published tasks join the public catalogue, become cross-customer comparable, and amortize onboarding |
| `initial_conditions` | named objects with poses, tolerances, and randomization zones; kit hardware is referenced as `kit/<name>` |
| `success_predicates` | validated at creation: every predicate must bind to an available instrument in the target environment class |
| `instructions` | the phrasing distribution for language-conditioned policies; `held_out` paraphrases are never sampled during iteration and are reported separately |
| `stages` | ordered progress predicates; per-stage rates ship in results, so long-horizon tasks score progress, not just end-state |
| `envelope` | the claim's declared boundaries; out-of-scope declarations appear verbatim on reports |
| `baseline` | customer's internal trials, used to sanity-check the pilot and set expectations, never published |

Predicates bind to instruments, not to intentions: `cup_on_tray` reads mocap,
`liquid_mass` reads the load cell, `no_spill` reads the scene cameras. A
predicate that binds to no instrument in the target environment class fails
at creation with `contract_validation_failed` — the same before-any-motor-moves
discipline as [policy contracts](/docs/concepts/policies/).

## Lifecycle: draft → piloting → frozen

The lifecycle is a protocol agreement, encoded. `task.pilot()` runs a small
calibration session the customer watches live; `task.amend()` iterates on the
method while piloting — the worked example widens the `liquid_mass` band
after watching real pulls; `task.freeze()` produces `espresso@v1`: immutable
and mutually signed.

Frozen means frozen. Changes create `espresso@v2`, and results across
revisions are not silently comparable. A report against a frozen spec is
reproducible by construction and contestable only by contesting a protocol
the customer approved — the ISO 17025 method-transfer mechanism, as an API.

> **Pilot episodes are not evidence:** The pilot calibrates the method — the 25 episodes it runs never appear in a
> verification report. Evidence starts after the freeze, against the signed
> revision.

## Staged and language-conditioned results

A spec with `stages` gets `run.stage_rates` in results: per-stage rates,
each with its n and Wilson interval, so you see *where* a long-horizon task
degrades. A spec with `instructions` gets `run.instruction_breakdown`, with
held-out paraphrases reported separately. The canonical espresso run in
[/fixtures/runs.json](/fixtures/runs.json) shows both — held-out phrasing
0.795 (n=83, ci95 0.696–0.868) against the sampled set's best of 0.873
(n=118, ci95 0.801–0.921). The shapes are in the
[data contract](/docs/concepts/data-contract/).

## Object kits

When the claim involves the customer's own hardware, the hardware ships in
as a kit.

*Example: roborama.kits.register()*

**Python**

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

# When the claim involves the customer's own hardware, ship it as a kit.
kit = roborama.kits.register(
    name="acme-breville",
    items=[{"desc": "Breville BES870", "qty": 1}],
)

print(kit.shipping_label())  # inbound label to the facility
print(kit.status)
# "received"  -> "tracked" (mocap markers, mass, mesh scan)
#             -> "available" as kit/acme-breville in environments

# Once available, reference it from a Task Spec's initial conditions:
#   {"machine": {"object": "kit/acme-breville", "pose": "P1", "tol_mm": 20}}
```

**TypeScript**

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

const roborama = new Roborama();

// When the claim involves the customer's own hardware, ship it as a kit.
const kit = await roborama.kits.register({
  name: "acme-breville",
  items: [{ desc: "Breville BES870", qty: 1 }],
});

console.log(await kit.shippingLabel()); // inbound label to the facility
console.log(kit.status);
// "received"  -> "tracked" (mocap markers, mass, mesh scan)
//             -> "available" as kit/acme-breville in environments

// Once available, reference it from a Task Spec's initial conditions:
//   { machine: { object: "kit/acme-breville", pose: "P1", tol_mm: 20 } }
```

**cURL**

```bash
# Register the kit — the response includes the inbound shipping label URL
curl https://api.roborama.com/v1/kits \
  -H "Authorization: Bearer $ROBORAMA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "acme-breville",
    "items": [{"desc": "Breville BES870", "qty": 1}]
  }'

# Poll the status progression: received -> tracked -> available
curl https://api.roborama.com/v1/kits/kit_acme_breville \
  -H "Authorization: Bearer $ROBORAMA_API_KEY"
```

**Agent (tool-use payload)**

```json
{
  "type": "tool_use",
  "name": "roborama_kits_register",
  "input": {
    "name": "acme-breville",
    "items": [{ "desc": "Breville BES870", "qty": 1 }]
  }
}
```

Status progresses `received → tracked → available`: on arrival the machine
gets mocap markers, a mass measurement, and a mesh scan, then becomes
referenceable from Task Specs as `kit/acme-breville`. Kits integrate into
the standard fixture/tracking/reset system — the system is never customized
around a kit.

## Where next

- [Advanced quickstart](/docs/quickstart-advanced/) — the full flow: kit → spec → freeze → verify → matrix.
- [run()](/docs/primitives/run/) — where the frozen `task` pin gets used.
- [API reference: Tasks](/docs/api-reference/tasks/) — create, pilot, amend, freeze on the wire.
