# Registering a robot and its capability manifest

> How a robot record and its capability manifest are written, what each field drives downstream, and how editing, retiring, and tagging behave.

A robot has to exist as a record before an operator can be offered it.
That record has two halves, and the fleet screen writes both:

- **The robot record** — the operator-facing id, model, site, edge PoP,
  RTT floor, and required cert. These are the columns the fleet table
  shows and the fields the console owns.
- **The capability manifest** — what the robot *is*: degrees of freedom,
  which capabilities it declares, and which topics carry its body state,
  video, and map. One manifest drives the cockpit controls, the Foxglove
  layout, and the attach grant, whatever shape the robot is.

You write both at once from **+ Register robot** on
**Admin · fleet**. The register call goes to `api.registerRobot`, which
in a live deployment writes the `dim_robot` row plus the manifest
`jsonb` through the teleop BFF. In simulation mode it writes an
in-memory fixture instead.

## The robot record: id, model, site, edge PoP, RTT floor, required cert

| Field | Required | What it does |
| ----- | -------- | ------------ |
| **Robot ID** | yes | `external_robot_id`. This is the operator-facing id and it becomes a topic path segment (`robot/<id>`), so it is restricted to letters, numbers, dashes and underscores. Spaces and slashes are rejected at the form rather than allowed onto the wire. |
| **Model** | yes | Free text, shown in the fleet table's Model column. |
| **Display name** | no | Defaults to the robot ID if left empty. |
| **Site** | no | Upserts a site by name. A site that does not exist yet is created from this value. |
| **Edge PoP** | no | The edge agent this robot binds to. |
| **RTT floor (ms)** | no | The physics floor for the operator↔robot round trip. Whole number. |
| **Required cert** | no | The certification an operator must hold before this robot is offered to them. Shown as a chip in the fleet table. |

Every field except Robot ID and Model may be left blank; the fleet table
renders an em dash for anything unset.

### Edge PoP binding

Each robot binds to an edge agent at a PoP close to the arm — the fleet
table annotates the PoP column with `· ~5ms to arm`. That placement is
deliberate: it puts the watchdog downstream of every link that can fail,
so the component that can stop the robot is not on the far side of the
link whose failure it has to survive. The record is where that binding
is visible: if the PoP column is empty, nothing has been declared about
where this robot's edge agent lives.

### RTT floor

The RTT floor is the *floor*, not a measurement of current conditions —
the best round trip physics allows between the operator and this robot,
given where they each are. It is recorded per robot so that observed
latency can be read against something. A session sitting at the floor is
as good as that pairing gets; a session well above it has a problem
somewhere other than distance.

## The capability manifest and what each capability enables

Capabilities are chosen from a fixed set:

`arm` · `gripper` · `nav` · `base` · `camera` · `lift`

The hint on the field states what the set is for: capabilities drive the
cockpit controls, the Foxglove panels, and job matching. Two of them also
change what the register form itself asks for:

- **`camera`** reveals the **Video topics** field. Without it, the
  manifest's `video` list is written empty.
- **`nav`** reveals the **Map topic** field, which expects an
  `OccupancyGrid` topic and defaults to `map`. Without `nav`, no map
  entry is written to the manifest at all.

The capabilities you select are also what the attach grant advertises,
which is why declaring one the hardware does not have is not a cosmetic
error — it advertises a camera the arm does not have.

**DoF** records the robot's degrees of freedom as a whole number and is
part of the manifest, not the record.

## Body topic, URDF and video topics: how panels are chosen

| Manifest field | Default | Drives |
| -------------- | ------- | ------ |
| **Body topic** | `joint_states` | The 3D / URDF panel. |
| **URDF URL** | (none) | The Foxglove Urdf panel source. Optional — if you leave it empty, the URDF is discovered from the topic instead. Must be an `http` or `https` URL. |
| **Video topics** | `camera/wrist/image_raw, camera/overhead/image_raw` | One video panel per topic. Only collected when `camera` is declared. |
| **Map topic** | `map` | The occupancy-grid panel. Only collected when `nav` is declared. |

Video topics are entered comma-separated. Each one becomes a video entry
whose label is derived from the topic path: the trailing path segment is
used, skipping `image_raw`. So `camera/wrist/image_raw` labels the panel
`wrist` and `camera/overhead/image_raw` labels it `overhead`. The codec
recorded for each entry is `h264`.

This is the marketplace model at work: the panels an operator sees are
not configured per robot in the cockpit. They are derived from the
manifest, so a newly registered robot of an unfamiliar shape gets a
working layout from its declaration alone.

## Editing vs re-registering a robot

**Edit** reuses the same drawer, prefilled from the existing record, but
it submits a different call — `api.updateRobot` — and it patches only
the fields the console owns:

- display name
- model
- site
- edge PoP
- RTT floor
- required cert

**The capability manifest is deliberately not editable here.** Changing
what a robot can do is a re-registration, not a rename. Rewriting a
manifest from a form that was only partly filled in is how a fleet ends
up advertising hardware that is not there, so the edit path never sends
the manifest fields at all. Consistently, DoF and URDF are validated only
on the register path, because the edit path does not submit them.

The Robot ID is likewise not editable. It is already on the wire as a
topic path segment and in every session and audit row recorded against
the robot.

## Retiring and restoring a robot

Retiring is reversible and keeps history. `dim_robot` is referenced by
every session and audit row ever recorded against the robot, so the row
is not deleted. The confirmation states the effect plainly: the robot
leaves the fleet and stops being offered to operators, session history is
kept, and it can be restored later.

A retired robot keeps its record, its manifest, its tags, and everything
that points at it. It renders in the muted `retired` status in the fleet
table.

## Reading the status column

Status is not part of what you fill in — it reflects what has been heard
from the robot.

| Status | Meaning |
| ------ | ------- |
| `online` | Reported within the last minute. |
| `offline` | Reported previously, but not recently. |
| `unknown` | Registered, but nothing has ever reported from the robot. Liveness is unknown, not healthy. |
| `maintenance` | Flagged for maintenance. |
| `retired` | Retired out of the fleet. |

`unknown` is rendered muted rather than green on purpose. This is the
screen on which someone decides whether an arm can be driven, and "we
have never heard from this arm" must not read as healthy.

## Tagging robots

Tags attach to the robot's `dim_robot` primary key, which the API returns
as `robotRowId`. This is distinct from `id`, the operator-facing external
robot id you typed at registration. A robot whose row id is not available
shows an em dash in the Tags column instead of a tag editor.

Because tags hang off the row, they survive editing the record and they
survive retirement.

## Related

- [Telemetry](/platform/telemetry)
- [Authentication](/concepts/authentication)
