# Robot description files

> Field reference for a RosieOS robot description: the manifest and its identity algorithm, config.json, rtcore_definition.json, spheres.json and a cell's machine_planning_calibration.json, with units, rules and the tools that check them.

URL: https://advancedmetalresearch.com/docs/reference/robot-description
Section: RosieOS docs / Reference
Last updated: 2026-10-10

This page lists every file in a robot description directory, `robot_description/robots/<model_id>/`, with its fields, units and the rules the loaders enforce. For what a description is and how its identity is used, read [Robot description and coordinate frames](https://advancedmetalresearch.com/docs/concepts/robot-description-and-frames) first.

Check every description in the repository:

```bash
python3 robot_description/tools/manifest.py --check
# manifest: rosie_1400_v3 ok, <n> files   (one line per model)
```

## Loaders and tools

| Tool | What it does |
|---|---|
| `python3 robot_description/tools/manifest.py --check [model ...]` | Reports a registered file that is missing, a needed file that is not registered, and a mesh the URDF references that is not in `meshes/`. It does not fail on unregistered extra files. Exit 1 on any problem. CI runs this. |
| `python3 robot_description/tools/manifest.py <model>` | Rewrites that model's manifest and prints its identity. It imports `weldplan` from `weld_planner/v1/python` to compute the hash. |
| `go run ./cmd/identity <dir> ...` (in `robot_description/go`) | Prints `sha256:<hex> <dir>` for each directory. Exit 1 if any fails, 2 with no arguments. |
| Go module `rosieos/robotdesc` | `DescriptionFiles`, `RobotDescriptionSHA256`, `HashFiles`, `HashEntries`, `MachinePlanningCalibrationSHA256`, `Store.Load`, `Resolve`. Used by `rtctl` and OLP. |
| `rtctl compile` | Checks the machine's pin against the identity, reads `rtcore_definition.json`, and refuses with a [robot compile reason](https://advancedmetalresearch.com/docs/reference/configuration#robot-compile-refusals). |

## `robot_description_manifest.json`

robot_description/robots/rosie_1420_v1/robot_description_manifest.json (shape):

```json
{
  "schema": "rosie.robot-manifest.v1",
  "model_id": "rosie_1420_v1",
  "files": ["config.json", "meshes/link_1.dae", "robot.srdf", "robot.urdf", "rtcore_definition.json", "spheres.json"]
}
```

| Field | Type | Required | Description |
|---|---|---|---|
| `schema` | string | Yes | Exactly `rosie.robot-manifest.v1`. |
| `model_id` | string | Yes | Must equal the directory name. |
| `files` | string[] | Yes | Non-empty. Paths relative to the description directory, with `/` separators. |

Rules for `files`, applied by `DescriptionFiles`:

- No empty, absolute or backslash path, no `.` or `..` segment, no duplicates.
- The manifest cannot list itself.
- It cannot list `machine_planning_calibration.json`. That name belongs to the cell's own file, which is served beside the description.
- Every entry must be a regular file.

`manifest.py` registers `robot.urdf`, `robot.srdf`, `config.json`, `spheres.json` and `rtcore_definition.json` when present, plus every file in `meshes/`. Everything else in the directory is provenance.

### Identity algorithm

The identity is a domain-separated SHA-256 over the manifest and every registered file:

1. Build the entry list: the manifest itself, plus every file in `files`. Each entry is its path and the SHA-256 of its bytes.
2. Sort the entries by path (byte order).
3. Hash, in order:
   - the ASCII domain `rosie.robot-description.identity.v1` followed by one `0x00` byte
   - the entry count, as a big-endian uint64
   - for each entry: the path length as a big-endian uint64, the path bytes, then the 32-byte file digest
4. Write the result as `sha256:` followed by 64 lowercase hex digits.

Because the manifest is an entry, removing a name from it changes the identity even if the file stays on disk.

## `robot.urdf` and `robot.srdf`

Standard URDF and SRDF, with these constraints:

- The URDF `<robot name>` must equal the model id.
- Every movable joint needs bounded `<limit lower upper>`. Revolute limits are in rad and `velocity` in rad/s.
- Mesh references use `package://.../meshes/<file>`, and each named mesh must be registered.
- The SRDF defines a `manipulator` group for Tesseract. On the Rosie models it also defines a `home` group state with every joint at 0.

## `config.json`

Schema `rosie.robot-config.v1`. Unknown fields are rejected. A description with no `config.json` still loads, with zero correction caps (no cell correction can be accepted).

| Field | Type | Unit | Description |
|---|---|---|---|
| `schema` | string | — | `rosie.robot-config.v1`. |
| `model_id` | string | — | Must equal the directory name. |
| `frames.base` | link name | — | Frame everything is measured in. |
| `frames.tool` | link name | — | Tool centre point. |
| `frames.work` | frame name | — | Frame a workpiece is fixtured to. |
| `frames.work_surface.parent` | link name | — | Optional. The link the work frame is attached to. |
| `frames.work_surface.xyz_m` | [3]number | m | Offset of the work frame from `parent`. |
| `kinematic_correction_caps.xyz_m` | number | m | Largest per-component translation a cell calibration may apply. Not negative. |
| `kinematic_correction_caps.rpy_rad` | number | rad | Largest per-component rotation a cell calibration may apply. Not negative. |
| `planning.speed_ceiling_rad_s` | number | rad/s | Planning speed ceiling. Must not exceed the URDF's fastest revolute joint. |
| `planning.joints.<J>.velocity_rad_s` | number | rad/s | Planning velocity. Positive, and not above the URDF `velocity` of that joint. |
| `planning.joints.<J>.acceleration_rad_s2` | number | rad/s² | Planning acceleration. Positive. rt-core also uses it for the axis's trajectory and jog acceleration, and requires it for every robot-bound axis. |
| `axes.driven` | string[] | — | Joints the planner moves. Each must be a revolute URDF joint. |
| `axes.held` | map | rad | Joints the planner keeps fixed, with their value. Finite. |
| `reset_pose` | map | rad | Named reset position per joint. |
| `torch.tool_frame` | link name | — | The torch's frame, normally `tool0`. |
| `torch.electrode_axis` | [3]number | unit vector | Wire direction in the tool frame. |

Every joint named in `planning.joints`, `axes` and `reset_pose` must be a revolute joint in the URDF. Do not restate a number the URDF already holds: state only what the URDF cannot.

## `rtcore_definition.json`

Schema `rosie.robot-definition.v1`. It tells rt-core how each joint maps to a drive. The annotated template is `rt-core/config/templates/robot.json`, where every field has a `_doc` sibling with its unit, range and default.

> [!NOTE] This file carries each joint's gearing and encoder scale. These docs list the fields only. Take the values from the robot's own definition file.

Every numeric robot fact needs a `<field>_source` or `<field>_unverified` sibling that says where the number came from. Duplicate JSON keys and unexpected fields are rejected.

| Field | Type | Description |
|---|---|---|
| `schema` | string | `rosie.robot-definition.v1`. |
| `robot_id` | token | Must equal the description directory name and the URDF robot name. |
| `name` | string | Display name. Non-empty. |
| `revision` | integer | Model revision, 1 to 2⁶⁴−1. |
| `calibration.identity` | token | Must be `home_calibration`. Home offsets and encoder anchors live in a separate Home calibration record, never here. |
| `calibration.note` | string | Non-empty. |
| `defaults.drive_profile` | drive config name | Default drive config (a basename in `rt-core/config/drives/`). |
| `defaults.coordinate_evidence_mode` | `multi_turn` \| `single_turn_absolute` | How absolute position evidence is judged. |
| `defaults.home_policy` | `required` \| `optional` | Whether joints need Home before motion. Default `required`. |
| `joints[]` | array | 1 to 16 uniquely named joints that together map every movable URDF joint. |
| `joints[].name` | token | Joint name, for example `J1`. |
| `joints[].kind` | `arm` \| `positioner` \| `external` | Joint role. |
| `joints[].urdf_joint` | string \| null | The URDF joint this drive turns. `null` only for a non-Cartesian external or positioner joint. `arm` joints require a mapping. |
| `joints[].cartesian_participates` | boolean | `true` only with a valid URDF mapping. |
| `joints[].drive_profile` | string \| null | Per-joint drive config; `null` inherits `defaults.drive_profile`. |
| `joints[].gear_ratio.numerator`, `.denominator` | integer | Mechanical ratio, each 1 to 4294967295. Requires a source. |
| `joints[].motor_encoder_counts_per_rev` | integer | Encoder counts per motor revolution, 1 to 2147483647. |
| `joints[].sign` | −1 \| +1 | Direction multiplier from logical to drive motion. |
| `joints[].encoder_battery` | `present` \| `absent` | Whether the absolute encoder has battery backup. Requires `encoder_battery_source`. |
| `joints[].mechanical_travel.min`, `.max` | number | rad or m. Only for an external joint with no URDF joint. URDF-mapped joints inherit their URDF limits. |

## `spheres.json`

The arm's collision spheres, used by the weld planner's screen. The verifier re-checks results against the exact meshes.

| Field | Type | Description |
|---|---|---|
| `schema` | string | `amr-weld-planner-v1.sphere-model.v1`. |
| `cell_id` | string | The model id. |
| `provenance.robot_urdf_sha256` | hex | SHA-256 of the URDF the spheres were fitted against. |
| `provenance.meshes_sha256` | `sha256:<hex>` | Hash of the meshes they were fitted against. |
| `note` | string | Free text. |
| `links.<link>[]` | array | Spheres in that link's own frame: `center_m` [3]number (m) and `radius_m` number (m). |
| `coverage` | object | The fit's coverage report. |

A URDF or mesh change that leaves `provenance` stale is caught by the planner's provenance test. Refit or restamp with `weld_planner/v1/tools/fit_spheres.py` (see [Add a robot model](https://advancedmetalresearch.com/docs/guides/add-a-robot-model#spheres)).

## `machine_planning_calibration.json`

Per cell, not in the repository. Schema `rosie.machine-planning-calibration.v1`. A machine config pins it with `robot.machine_planning_calibration{path, sha256, source}`, and the compiled configuration serves it beside the description. Unknown fields are rejected.

machine_planning_calibration.json (example shape, values illustrative):

```json
{
  "schema": "rosie.machine-planning-calibration.v1",
  "model_id": "rosie_1420_v1",
  "limits": {
    "speed_ceiling_rad_s": 2.0,
    "joints": { "J1": { "lower_rad": -3.0, "upper_rad": 3.0, "velocity_rad_s": 1.5 } },
    "held": {}
  },
  "kinematics": { "J2": { "xyz_m": [0.0005, 0, 0], "rpy_rad": [0, 0.001, 0] } }
}
```

| Field | Type | Unit | Rule |
|---|---|---|---|
| `schema` | string | — | `rosie.machine-planning-calibration.v1`. |
| `model_id` | string | — | Must equal the description's model id. |
| `limits.speed_ceiling_rad_s` | number | rad/s | Positive, and not above the URDF's fastest revolute joint. |
| `limits.joints.<J>.lower_rad` | number | rad | Revolute URDF joint with stated limits. Must not be below the URDF lower limit. |
| `limits.joints.<J>.upper_rad` | number | rad | Must not be above the URDF upper limit, and must stay above `lower_rad`. |
| `limits.joints.<J>.velocity_rad_s` | number | rad/s | In (0, URDF velocity]. |
| `limits.held.<J>` | number | rad | Finite, and inside the joint's limits (narrowed ones if given). |
| `kinematics.<J>.xyz_m` | [3]number | m | Each component's magnitude at most `kinematic_correction_caps.xyz_m`. |
| `kinematics.<J>.rpy_rad` | [3]number | rad | Each component's magnitude at most `kinematic_correction_caps.rpy_rad`. |

A correction composes onto the URDF joint origin as T' = T_origin · Trans(xyz) · R(rpy), where R follows URDF convention Rz(yaw)·Ry(pitch)·Rx(roll).

### The empty document

A cell with no calibration serves this exact document for its model, and planners hash it the same way:

```text
{"schema":"rosie.machine-planning-calibration.v1","model_id":"<model_id>"}
```

followed by one newline (`\n`).

### Calibration identity

The identity is the SHA-256 of the file's exact bytes, never a re-serialisation. Planners and program headers write it `sha256:<hex>`. The machine config pin (`robot.machine_planning_calibration.sha256`) is the same digest as 64 bare lowercase hex digits.

## Program header identities

A dense program's `robot_cell` header block carries these fields, which OLP Load compares with the cell:

| Field | Compared at Load | Description |
|---|---|---|
| `model_id` | Yes | The model the plan was made for. |
| `robot_description_sha256` | Yes | The description identity. |
| `machine_planning_calibration_sha256` | Yes | The calibration identity, or the empty document's. |
| `machine_planning_calibration_source` | No | `target` if the bytes came from the cell, `empty` if the plan used the empty document. |
| `machine_configuration_sha256` | No | `sha256:` plus the machine's configuration digest, when known. A difference only adds a note. |

See [Load refusals](https://advancedmetalresearch.com/docs/concepts/robot-description-and-frames#load-refusals) and [Dense trajectory format](https://advancedmetalresearch.com/docs/reference/rdt-format).

## Sources

Written from these files in the RosieOS repository (https://github.com/advanced-metal-research/RosieOS):

- `robot_description/go/identity.go:32-167`
- `robot_description/go/cmd/identity/main.go:1-32`
- `robot_description/go/description.go:20-285`
- `robot_description/go/cell.go:26-300`
- `robot_description/tools/manifest.py:1-131`
- `robot_description/robots/rosie_1400_v3/robot.srdf`
- `robot_description/robots/rosie_1400_v3/spheres.json:1-8`
- `robot_description/robots/rosie_1420_v1/spheres.json`
- `robot_description/robots/rosie_1400_v3/rtcore_definition.json (keys only)`
- `rt-core/config/templates/robot.json:1-109`
- `rt-core/tools/rtctl/robot_definition.go:66-160,200-230,313-420,426-500,650-735`
- `rt-core/config/templates/machine.json (robot.machine_planning_calibration)`
- `offline-programming/v1/internal/denseexec/robot.go:20-110`
- `weld_planner/v1/tools/fit_spheres.py:28-33,168-226`
