# rt-control HTTP API

> Reference for rt-control, the public control API of RosieOS. It covers all 33 operations, the request and response envelopes, every schema type and the reason codes each operation can return.

URL: https://advancedmetalresearch.com/docs/apis/rt-control-http
Section: RosieOS docs / APIs
Last updated: 2026-10-10

rt-control is the only public way to command a RosieOS cell. It serves HTTP/1.1 and JSON over a Unix socket on the cell host, and optionally over mutual TLS for remote clients. Every client uses it: the offline programming server, the motion servers, the pendant, `rtctl` and your own code. Behind it, rt-control talks to the real-time core over a private IPC channel, which is internal and not part of this API.

This page is generated from the contract file `rt-core/protocol/application-v1.schema.json` (`contract_version` 2) and the adapter code. It lists all 33 capabilities, every request and response field, and the reason codes each operation can return. The full catalogue of 153 reason codes is on [Error codes and fault states](https://advancedmetalresearch.com/docs/reference/error-codes).

> [!WARNING] **Commands on this page move hardware.** `enable`, `arm`, `home`, jog and every Start energise the drives. The hardware E-stop is the only emergency stop: RosieOS has no software E-stop, and `stop` is not one. Only planned weld programs pass the planner's collision and limit check before they can be loaded. Jog, Home and point-list moves rely on the core's limit and readiness checks only. Read the [safety model](https://advancedmetalresearch.com/docs/get-started/safety-model) before you arm a real cell.

> [!TIP] **Machine-readable.** The same contract as an [OpenAPI 3.1 document](https://advancedmetalresearch.com/docs/openapi/rt-control.json), the [contract file itself](https://advancedmetalresearch.com/docs/openapi/rt-control.contract.json), and every reason code as [JSON](https://advancedmetalresearch.com/docs/data/error-codes.json).

## Quick start

Read the description and status, then take and release authority, from a shell on the cell host (or on a machine running the simulated core). The socket path is the installed default.

rt-control from curl:

```bash
SOCK=/run/rosie-rt-core/control.sock
rt() { curl -sS --unix-socket "$SOCK" "http://localhost$1" "${@:2}"; }

# 1. Who is this cell? Check the backend, identity and axis order.
rt /v1/describe | jq '.data | {backend, machine_sha256, cycle_ns, axes: [.axes[] | {id, position_unit, min_position, max_position}]}'

# 2. What is it doing now?
rt /v1/status | jq '{armed: .data.core.armed, homed: .data.core.home_valid_mask, faults: .data.core.safety_fault_mask}'

# 3. Take authority, then give it back. Use the pair ID and revision rt-control was started with.
D=$(rt /v1/describe)
GRANT=$(rt /v1/control -H 'Content-Type: application/json' -d "$(jq -n --argjson d "$D" '{
  schema: "rosie.rt-control.request.v1", operation: "acquire", controller: "curl-demo",
  binding: {pair_id: "cell-a", revision: 1,
            configuration_sha256: $d.data.configuration_sha256, machine_sha256: $d.data.machine_sha256}}')")
echo "$GRANT" | jq '.data | {session, generation, lease_ms}'
rt /v1/control -d "$(echo "$GRANT" | jq '{schema: "rosie.rt-control.request.v1", operation: "release",
  session: .data.session, generation: .data.generation}')" | jq '.data.session == ""'
```

The default lease is 500 ms, and a shell cannot renew it between steps. For anything beyond this round trip, use a client that renews in the background: the [Go SDK](https://advancedmetalresearch.com/docs/apis/go-sdk) or the [C++ client](https://advancedmetalresearch.com/docs/apis/cpp-client). `rtctl` wraps the same calls for one-off commands; see [rtctl](https://advancedmetalresearch.com/docs/reference/rtctl).

## Operations at a glance

Each capability has a state. `implemented` means the interface exists and is tested in software; it is not a hardware qualification. `interim` is the current reset API, `test_only` is for fixtures, and `unimplemented` returns `capability_unimplemented`. Describe returns this list at run time.

| Operation | State | Transport | Lease | Result |
|---|---|---|---|---|
| [`telemetry`](#telemetry) | `implemented` | `GET /v1/telemetry?after=<sequence>`<br>`GET /v1/telemetry/stream?after=<sequence>` | none | [`TelemetryBatch`](#type-telemetrybatch) |
| [`mark_telemetry`](#mark-telemetry) | `implemented` | `POST /v1/control` | session and generation | `sequence` |
| [`acquire`](#acquire) | `implemented` | `POST /v1/control` | no fence; checks `binding` | [`Grant`](#type-grant) |
| [`renew`](#renew) | `implemented` | `POST /v1/control` | session and generation | [`Grant`](#type-grant) |
| [`release`](#release) | `implemented` | `POST /v1/control` | session and generation | [`Grant`](#type-grant) |
| [`stop`](#stop) | `implemented` | `POST /v1/control` | session and generation | [`Grant`](#type-grant) |
| [`enable`](#enable) | `implemented` | `POST /v1/control` | session and generation | `sequence` |
| [`arm`](#arm) | `implemented` | `POST /v1/control` | session and generation | `sequence` |
| [`home`](#home) | `implemented` | `POST /v1/control` | session and generation | `sequence` |
| [`reset_fault`](#reset-fault) | `interim` | `POST /v1/control` | session and generation | [`RecoveryStatus`](#type-recoverystatus) |
| [`recovery_status`](#recovery-status) | `implemented` | `POST /v1/control` | session and generation | [`RecoveryStatus`](#type-recoverystatus) |
| [`jog`](#jog) | `test_only` | `POST /v1/control` | session and generation | `sequence` |
| [`begin_jog`](#begin-jog) | `implemented` | `POST /v1/control` | session and generation | `handle` |
| [`end_jog`](#end-jog) | `implemented` | `POST /v1/control` | session and generation | `handle` |
| [`prepare_trajectory`](#prepare-trajectory) | `implemented` | `POST /v1/control` | session and generation | `handle` |
| [`start_trajectory`](#start-trajectory) | `implemented` | `POST /v1/control` | session and generation | `sequence` |
| [`discard_trajectory`](#discard-trajectory) | `implemented` | `POST /v1/control` | session and generation | `sequence` |
| [`start_program`](#start-program) | `implemented` | `POST /v1/control` | session and generation | `sequence` |
| [`describe`](#describe) | `implemented` | `GET /v1/describe` | none | [`Description`](#type-description) |
| [`status`](#status) | `implemented` | `GET /v1/status` | none | [`ProcessStatus`](#type-processstatus) |
| [`jog_clock`](#jog-clock) | `implemented` | `GET /v1/jog/clock` | none | [`LocalJogClock`](#type-localjogclock) |
| [`jog_status`](#jog-status) | `implemented` | `GET /v1/jog` | none | [`JogObservation`](#type-jogobservation) |
| [`jog_ingress`](#jog-ingress) | `implemented` | `GET /v1/jog/ingress` | none | [`JogIngressObservation`](#type-jogingressobservation) |
| [`prepare_program`](#prepare-program) | `implemented` | `POST /v1/program` | session and generation headers | [`Program`](#type-program) |
| [`update_jog`](#update-jog) | `implemented` | `jog.sock datagram or WSS /v1/jog`<br>`protocol/control.json local_jog_update` | session and generation | [`JogObservation`](#type-jogobservation) |
| [`halt`](#halt) | `implemented` | `POST /v1/control` | session and generation | `Response.sequence/native_result` |
| [`abort`](#abort) | `unimplemented` | none | none | `none` |
| [`subscribe_events`](#subscribe-events) | `implemented` | `GET /v1/events?after=<sequence>`<br>`GET /v1/events/stream?after=<sequence> (SSE)` | none | [`EventBatch`](#type-eventbatch) |
| [`readiness`](#readiness) | `unimplemented` | none | none | `none` |
| [`restore_anchor`](#restore-anchor) | `implemented` | `POST /v1/control` | session and generation | [`RecoveryStatus`](#type-recoverystatus) |
| [`io_arm`](#io-arm) | `implemented` | `POST /v1/control` | session and generation | `sequence` |
| [`io_disarm`](#io-disarm) | `implemented` | `POST /v1/control` | session and generation | `sequence` |
| [`resource`](#resource) | `implemented` | `GET /v1/resources/<sha256>` | none | `binary` |

## Connecting

| Transport | Address | Who can use it |
|---|---|---|
| HTTP over a Unix socket | `/run/rosie-rt-core/control.sock` (a link to `public/control.sock` in the same directory) | Local processes in the socket's group. File permissions are the authentication. |
| Jog datagrams | `/run/rosie-rt-core/jog.sock` | Local jog producers. Binary frames only; see [`update_jog`](#update-jog). |
| HTTPS with mutual TLS 1.3 | `--remote-listen`, which cell configs set to `127.0.0.1:8443` | Remote clients with a certificate from the cell's component CA. See [Remote access](https://advancedmetalresearch.com/docs/apis/remote-access-mtls). |

rt-control's own flags set these paths: `--socket` (default `/run/rosie-rt-core/control.sock`; `jog.sock` is created beside it) and `--remote-listen`. Use `localhost` as the HTTP host name on the Unix socket. Keep connections alive: the listener's idle timeout is `control_idle_timeout_ns` from Describe (90 s).

## Request envelope

Every JSON command is a `POST /v1/control` whose body is one `Request` object. `schema` and `operation` are always required. After `acquire`, every command also carries the `session` and `generation` of the current grant (the *fence*). Fields an operation does not use can be omitted. The server decodes strictly: unknown fields, duplicate fields and trailing data are refused.

| Name | Type | Required | Description |
|---|---|---|---|
| `request_id` | `string` | No | Optional, non-null string of 1..64 printable ASCII bytes (0x20..0x7e). |
| `schema` | `string` | Yes | Exactly `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | Operation name, for example `acquire`. Only `POST /v1/control` operations dispatch here. |
| *(embedded)* | [`Fence`](#type-fence) | Yes | All fields of `Fence` appear at this level of the object. |
| `label` | `string` | No | mark_telemetry requires 1..128 UTF-8 bytes; free-text dump label. |
| `controller` | `string` | No | Acquire requires 1..63 bytes; opaque controller name. |
| `binding` | [`Binding`](#type-binding) | No | Acquire requires exact equality with the configured Binding. |
| `axis_mask` | `uint32` | No | Uint32 configured axis group mask; interpretation is native operation-specific; reset_fault defaults zero to all axes. |
| `velocity` | `float64[]` | No | Finite logical velocities in described axis order for legacy jog; native vector and velocity-limit validation applies. |
| `timeout_ms` | `uint32` | No | Legacy jog requires an integer 1..250 milliseconds. |
| `handle` | `uint64` | No | Nonzero opaque uint64 handle for start/discard, owned by the current daemon incarnation and grant. |
| `points` | [`Point[]`](#type-point) | No | 2..250000 Point records; declared axis order, native limits and continuity apply. Large bodies require the envelope before this array. |
| `identity` | [`Identity`](#type-identity) | No | Exact prepared program Identity for start_program. |
| `jog_generation` | `uint64` | No | EndJog requires the exact current nonzero independent jog generation. |
| `source_sequence` | `uint64` | No | Accepted envelope field retained for compatibility; current /v1/control dispatch does not consume it. Independent updates use the binary lane. |
| `source_origin_host_ns` | `uint64` | No | BeginJog origin in the negotiated host monotonic clock, uint64 nanoseconds; native freshness validation applies. |
| `deadline_host_ns` | `uint64` | No | BeginJog producer deadline in host monotonic nanoseconds; after origin and never extended by admission or renewal. |
| `clock_incarnation` | `string` | No | BeginJog requires exact equality with GET /v1/jog/clock incarnation. |
| `requested_lease_ms` | `int` | No | Acquire requested lease in whole ms, 1..10000; omitted or zero retains the LAN default capped by the cell. Adapter returns min(request, cell ceiling) in Grant.lease_ms. Clients require at least three measured round trips plus renewal cadence. |

`session` and `generation` come from the embedded `Fence`, so they sit at the top level of the object.

- **Large uploads.** For a large `prepare_trajectory`, send `schema`, `operation`, `session` and a non-zero `generation` before `points`, within the first 16384 bytes. rt-control admits the fence and reserves the upload slot before it reads the rest.
- **Size caps.** 16384 bytes for ordinary commands, 222516384 bytes for `prepare_trajectory` and 39298580 bytes for a `.rdt` upload to `/v1/program`. Larger bodies get HTTP 413 `body_too_large`.
- **Units.** Positions are in each axis's `position_unit` (rad or m), velocities per second, times in ns. Host times use the cell's `CLOCK_MONOTONIC`.

### Idempotent retries

Add `request_id` (1..64 printable ASCII bytes) to make a JSON command safe to retry. The current session remembers its last 256 outcomes. Resending the same decoded payload with the same ID joins the in-flight request or replays its final reply. Changing the payload under the same ID returns `request_id_conflict`. Release and lease expiry drop the cache, and so does an adapter restart.

Keep one ID for one logical attempt. Use a fresh ID for every renewal and after you correct a refused request. `/v1/program` uploads have no deduplication. After an ambiguous Start, inspect handle state and incarnations before you try new motion. The Go and C++ clients add a random `request_id` to every command.

## Responses and errors

Ordinary replies are one `Response` object with `schema: rosie.rt-control.response.v1`. `data` holds the operation's result type; `sequence` and `handle` are top-level fields. Fields that are zero or empty are omitted.

| Name | Type | Required | Description |
|---|---|---|---|
| `native_jog_result` | [`JogObservation`](#type-jogobservation) | No | The core's `JogObservation` when the jog lane refused. |
| `native_result` | [`CommandResult`](#type-commandresult) | No | The core's `CommandResult` when the core refused a command. Field names are case-sensitive (`Reason`, `Sequence` …). |
| `schema` | `string` | Yes | `rosie.rt-control.response.v1`. |
| `operation` | `string` | Yes | The operation this reply answers. |
| `sequence` | `uint64` | No | Native command sequence, for operations that return one. Exact uint64. |
| `handle` | `uint64` | No | Trajectory handle, or jog generation for jog calls. Exact uint64. |
| `data` | `any` | No | The operation's result type (see each operation). On some refusals, structured evidence such as `limit_violation`. |
| `error` | `string` | No | Present only on failure: a reason code, or a diagnostic string that starts with one. |

| HTTP status | Meaning |
|---|---|
| 200 | Admitted. For motion this acknowledges admission, not physical completion. |
| 409 | Refused. `error` holds the reason. This covers every refusal except the two below. |
| 413 | `body_too_large`. |
| 404 | `resource_unknown`, from `/v1/resources/<sha256>` only. |

`error` is usually one catalogue label. It can also be an open diagnostic: JSON decoder errors, I/O and context errors, native receipts such as `RTCore rejected operation 0x124: reason 2`, or two causes joined with a newline. Some labels carry detail after a colon, for example `native_limit_exceeded: segment=<index> sample=<index> axis=<id>`. Match on the leading label. Treat anything you do not recognise, and any transport failure, as an unknown outcome: stop producing motion, issue an authenticated `stop` if you can, and reconcile Status before you acquire again.

### Common reasons

These sets apply in addition to each operation's own table. The operation sections say which sets apply.

#### Envelope reasons (every `POST /v1/control`)

| Reason | When |
|---|---|
| [`body_too_large`](/docs/reference/error-codes#reasons-envelope) | The body is larger than the operation's cap. HTTP 413. |
| [`invalid_request_envelope`](/docs/reference/error-codes#reasons-envelope) | The body is not a JSON object, or a key is not a string. |
| [`duplicate_request_field`](/docs/reference/error-codes#reasons-envelope) | A field appears twice in the envelope prefix. |
| [`schema_mismatch`](/docs/reference/error-codes#reasons-envelope) | `schema` is not `rosie.rt-control.request.v1`. |
| [`unknown_operation`](/docs/reference/error-codes#reasons-envelope) | `operation` is not a POST `/v1/control` operation. |
| [`trailing_request_data`](/docs/reference/error-codes#reasons-envelope) | Data follows the JSON object. |
| [`request_envelope_changed`](/docs/reference/error-codes#reasons-envelope) | The fully decoded envelope differs from the admitted prefix. |
| [`invalid_request_id`](/docs/reference/error-codes#reasons-envelope) | `request_id` is null, not a string, or not 1..64 printable ASCII bytes. |
| [`request_id_conflict`](/docs/reference/error-codes#reasons-envelope) | The `request_id` was already used in this session with a different payload. |
| [`session_principal_mismatch`](/docs/reference/error-codes#reasons-authority) | The session belongs to another TLS principal or to the local transport. |

#### Authority reasons (every fenced call)

| Reason | When |
|---|---|
| [`control_session_stale`](/docs/reference/error-codes#reasons-authority) | Wrong or stale session or generation, lease expired, or a Stop is in flight. |
| [`daemon_restarted`](/docs/reference/error-codes#reasons-authority) | The session belongs to a previous native daemon incarnation. |
| [`fence`](/docs/reference/error-codes#reasons-authority) | A well-formed session this adapter never issued (for example, from before an adapter restart), or authority was revoked during Start. |

#### Native reasons (calls the core admits)

| Reason | When |
|---|---|
| [`native_rejected`](/docs/reference/error-codes#reasons-native) | The core refused the command. Read the numeric [native reason](https://advancedmetalresearch.com/docs/reference/error-codes#native-command-reasons) in `native_result.Reason`. |
| [`outside_limits_outward`](/docs/reference/error-codes#reasons-native) | The command would move an axis further outside its limits (native reason 8). |

#### Cell I/O reasons (calls that can carry outputs)

| Reason | When |
|---|---|
| [`no_grant`](/docs/reference/error-codes#reasons-authority) | Native cell I/O refusal: no current grant. |
| [`wrong_generation`](/docs/reference/error-codes#reasons-authority) | Native cell I/O refusal: generation mismatch. |
| [`inhibited`](/docs/reference/error-codes#reasons-authority) | Native cell I/O refusal: outputs are inhibited. |
| [`io_not_configured`](/docs/reference/error-codes#reasons-io) | No cell I/O is configured. |
| [`io_not_armed`](/docs/reference/error-codes#reasons-io) | An ON intent needs `io_arm` first. |
| [`io_fast_input_unsatisfied`](/docs/reference/error-codes#reasons-io) | A cyclic fast input contact is invalid or not satisfied. |
| [`io_readback_disagreement`](/docs/reference/error-codes#reasons-io) | Physical feedback disagrees with the commanded output. |
| [`io_torch_unqualified`](/docs/reference/error-codes#reasons-io) | A torch-class output was requested. Always refused. |
| [`io_marker_late`](/docs/reference/error-codes#reasons-io) | A process marker missed its one-cycle delivery bound. |
| [`io_exchange_lost`](/docs/reference/error-codes#reasons-io) | Cell I/O has no current complete exchange. |

## Observation

Reads need no lease. On the remote listener they still need a valid client certificate.

### `describe`

**Endpoint: `GET /v1/describe`**

Read the machine description, identities and capability states. State: `implemented`. Lease: none.

Returns the native description (backend, protocol version, digests, cycle period, axis order, units and limits), the contract version and digest, every capability with its state, the prepared program if there is one, and the compiled robot description and drive identities.

Call it first. Check `backend`, `machine_sha256`, the axis order and `capabilities_digest` against what your client was built for before you acquire authority. `max_grant_lease_ns` and `max_jog_input_age_ns` are the cell's timing ceilings.

#### Request

No parameters.

#### Response

`data` is a [`Description`](#type-description).

| Name | Type | Required | Description |
|---|---|---|---|
| *(embedded)* | [`NativeDescription`](#type-nativedescription) | Yes | All fields of `NativeDescription` appear at this level of the object. |
| `contract_version` | `uint32` | Yes | Exactly 1. |
| `capabilities_digest` | `string` | Yes | Lowercase SHA-256 of the exact committed UTF-8 LF schema file bytes, including its final newline; no self-referential digest field. |
| `capabilities` | [`CapabilityInfo[]`](#type-capabilityinfo) | Yes | Every target capability with its implementation state and transport. |
| `control_idle_timeout_ns` | `uint64` | No | Compiled link idle timeout in ns; clients keep reserved connections alive at one third of this bound, independently of grants. Unverified LAN and internet policy: 90000000000 ns. An omitted legacy field uses 30000000000 ns. |
| `program` | [`Program`](#type-program) | No | Detached prepared program metadata including both identity digests; absent when no program is prepared. |
| `robot` | [`RobotDescription`](#type-robotdescription) | No | Null when no robot definition is bound or no compiled artefact is loaded; never guessed from axis count. |
| `drives` | [`DriveDescription[]`](#type-drivedescription) | Yes | Per-axis compiled drive configuration and current verified bring-up identity; empty without a compiled artefact. |

#### Example

```http
GET /v1/describe HTTP/1.1
Host: localhost
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "describe",
  "data": {
    "backend": "simulation",
    "schema": "…",
    "protocol_major": 1,
    "protocol_minor": 10,
    "configuration_sha256": "c2b097096fef301cad3b9c3e594f32266c625feaccba02be3410ed08df2b63ea",
    "machine_sha256": "4f1d9a7c2e6b8d0f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f",
    "deployment_sha256": "…",
    "max_trajectory_points": 250000,
    "resident_plans": 3,
    "cycle_ns": 1000000,
    "interpolation": "…",
    "stop": "…",
    "axes": [
      {
        "index": 0,
        "id": "J1",
        "position_unit": "rad",
        "min_position": -2.96,
        "max_position": 2.96,
        "max_velocity": 1.5,
        "…": "…"
      }
    ],
    "bus": {},
    "max_grant_lease_ns": 500000000,
    "max_jog_input_age_ns": 250000000,
    "contract_version": 2,
    "capabilities_digest": "d63b2aee7bbcb7e4f11eca7148ce9ffda0e5d8db421f165d20918ccb4f119158",
    "capabilities": [
      {
        "name": "acquire",
        "state": "implemented",
        "transport": "POST /v1/control"
      },
      "…"
    ],
    "control_idle_timeout_ns": 90000000000,
    "robot": null,
    "drives": []
  }
}
```

Axis values in the example are illustrative. Read the real limits from your cell.

#### Reason codes

No operation-specific reason codes. Unknown diagnostics mean the request failed.

**Client libraries.** Go: `Client.Describe`. C++: `describe()`.

### `status`

**Endpoint: `GET /v1/status`**

Read one timestamped snapshot of the core, the grant, jog, execution and every axis. State: `implemented`. Lease: none.

Every successful snapshot belongs to one daemon incarnation: `daemon_incarnation` equals `core.daemon_incarnation`. `time_ns` is the native publication time, not an adapter estimate. Check validity flags and timestamps before you treat a logical position as valid.

`execution.state` reports the program lifecycle: `prepared`, `executing`, `completed`, `faulted`, `cancelled`, `discarded` or `released` (empty before any observation). A faulted program carries `native_execution_failed` in `execution.error`.

#### Request

No parameters.

#### Response

`data` is a [`ProcessStatus`](#type-processstatus).

| Name | Type | Required | Description |
|---|---|---|---|
| `daemon_incarnation` | `string` | Yes | Core process identity for this snapshot. |
| `adapter_incarnation` | `string` | Yes | rt-control process identity. |
| `grant` | [`GrantObservation`](#type-grantobservation) | Yes | Native grant observation. |
| `jog` | [`JogObservation`](#type-jogobservation) | Yes | Native jog observation. |
| `jog_ingress` | [`JogIngressObservation`](#type-jogingressobservation) | Yes | Adapter-lifetime ingress outcomes across datagram, WSS and UpdateJog; counters and latest refusal match GET /v1/jog/ingress. |
| `core` | [`NativeStatus`](#type-nativestatus) | Yes | Native core status. |
| `motion` | [`MotionState`](#type-motionstate) | Yes | Native motion state. |
| `execution` | [`Execution`](#type-execution) | Yes | Program and handle lifecycle. |
| `time_ns` | `uint64` | Yes | Native publication time, ns. |
| `axes` | [`LogicalAxisStatus[]`](#type-logicalaxisstatus) | Yes | Per-axis logical status, in Describe order. |
| `generations` | [`StatusGenerations`](#type-statusgenerations) | Yes | Current generations and epochs. |
| `plan_cursor` | [`PlanCursor`](#type-plancursor) | Yes | Native plan cursor. |
| `buffer_health` | [`BufferHealth`](#type-bufferhealth) | Yes | Native buffer health. |
| `adapter` | [`AdapterStatus`](#type-adapterstatus) | Yes | Adapter runtime observations; not native motion state. |

#### Example

```http
GET /v1/status HTTP/1.1
Host: localhost
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "status",
  "data": {
    "daemon_incarnation": "3b9d…",
    "adapter_incarnation": "a41c…",
    "grant": {},
    "jog": {},
    "jog_ingress": {},
    "core": {
      "armed": 0,
      "axis_enable_mask": 0,
      "home_valid_mask": 511,
      "safety_fault_mask": 0,
      "…": "…"
    },
    "motion": {
      "mode": 0,
      "state": 0,
      "done": false,
      "…": "…"
    },
    "execution": {
      "state": "",
      "generation": 0,
      "handles": [],
      "…": "…"
    },
    "time_ns": 81234567890123,
    "axes": [
      {
        "logical_position": 0,
        "logical_valid": true,
        "readiness": "not_enabled",
        "…": "…"
      }
    ],
    "generations": {},
    "plan_cursor": {},
    "buffer_health": {},
    "adapter": {}
  }
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`daemon_restarted`](/docs/reference/error-codes#reasons-authority) | The native daemon was replaced while the snapshot was read. Re-read Describe and reconcile before any motion. |

**Client libraries.** Go: `Client.Status`. C++: `status()`.

### `jog_clock`

**Endpoint: `GET /v1/jog/clock`**

Sample the host monotonic clock and its incarnation for `begin_jog`. State: `implemented`. Lease: none.

Returns `domain` (`CLOCK_MONOTONIC`), the clock `incarnation`, `mapping_generation` and `now_host_ns`. Pass `incarnation` to `begin_jog` as `clock_incarnation`; every jog time you send uses this clock, in ns.

#### Request

No parameters.

#### Response

`data` is a [`LocalJogClock`](#type-localjogclock).

| Name | Type | Required | Description |
|---|---|---|---|
| `domain` | `string` | Yes | `CLOCK_MONOTONIC`. |
| `incarnation` | `string` | Yes | Clock incarnation; pass it to `begin_jog`. |
| `mapping_generation` | `uint64` | Yes | Clock mapping generation (1 for the local lane). |
| `now_host_ns` | `uint64` | Yes | Current host monotonic time, ns. |

#### Example

```http
GET /v1/jog/clock HTTP/1.1
Host: localhost
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "jog_clock",
  "data": {
    "domain": "CLOCK_MONOTONIC",
    "incarnation": "0f3c9a…",
    "mapping_generation": 1,
    "now_host_ns": 81234567890123
  }
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`independent_jog_unavailable`](/docs/reference/error-codes#reasons-jog) | The native client offers no independent jog lane. |

**Client libraries.** Go: `Client.JogClock`. C++: `jog_clock()`.

### `jog_status`

**Endpoint: `GET /v1/jog`**

Read the native jog-lane observation (local socket only). State: `implemented`. Lease: none.

On the local socket this returns the native `JogObservation`. Its fields keep their native, case-sensitive names (`Open`, `JogGeneration`, `InputDeadlineHostNs`, `StateReason` …). The reasons are the numeric [native jog reasons](https://advancedmetalresearch.com/docs/reference/error-codes#native-jog-reasons).

On the mutual-TLS listener the same path is the WebSocket upgrade for remote jog. See [Remote access](https://advancedmetalresearch.com/docs/apis/remote-access-mtls#remote-jog-over-websocket).

#### Request

No parameters.

#### Response

`data` is a [`JogObservation`](#type-jogobservation).

| Name | Type | Required | Description |
|---|---|---|---|
| `Ticket` | `uint64` | Yes | — |
| `ConnectionId` | `uint64` | Yes | — |
| `NativeGeneration` | `uint64` | Yes | — |
| `CapabilityId` | `uint8[16]` | Yes | — |
| `AppGrantGeneration` | `uint64` | Yes | — |
| `ConfigurationEpoch` | `uint64` | Yes | — |
| `HomeEpoch` | `uint64` | Yes | — |
| `ClockMappingGeneration` | `uint64` | Yes | — |
| `JogGeneration` | `uint64` | Yes | Current jog generation. |
| `SourceSequence` | `uint64` | Yes | Sequence of the latest applied input. |
| `InputDeadlineHostNs` | `uint64` | Yes | Deadline of the latest applied input, host ns. |
| `NowHostNs` | `uint64` | Yes | Publication time, host ns. |
| `ObservedSourceSequence` | `uint64` | Yes | — |
| `ObservedOriginHostNs` | `uint64` | Yes | — |
| `FirstObservedHostNs` | `uint64` | Yes | — |
| `ControlReason` | `uint32` | Yes | — |
| `ControlAccepted` | `uint32` | Yes | — |
| `UpdateReason` | `uint32` | Yes | — |
| `StateReason` | `uint32` | Yes | Native jog reason for the current state. |
| `AxisMask` | `uint32` | Yes | Axes of the jog session. |
| `Open` | `uint32` | Yes | 1 while the jog session accepts input. |
| `HasInput` | `uint32` | Yes | 1 once an input has been applied. |
| `VelocityScalePpm` | `uint32` | Yes | — |

#### Example

```http
GET /v1/jog HTTP/1.1
Host: localhost
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "jog_status",
  "data": {
    "Open": 1,
    "JogGeneration": 4,
    "AxisMask": 1,
    "InputDeadlineHostNs": 81234817890123,
    "StateReason": 0,
    "…": "…"
  }
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`independent_jog_unavailable`](/docs/reference/error-codes#reasons-jog) | The native client offers no independent jog lane. |

**Client libraries.** Go: `Client.JogStatus`. C++: `jog_status()`.

### `jog_ingress`

**Endpoint: `GET /v1/jog/ingress`**

Read cumulative jog-input refusal counters for this adapter process. State: `implemented`. Lease: none.

Counts every refused jog input across `jog.sock` datagrams, WebSocket frames and internal updates. `refused_by_reason` is indexed by [native jog reason](https://advancedmetalresearch.com/docs/reference/error-codes#native-jog-reasons) 0..14. The counters reset only when rt-control restarts.

Local datagrams get no reply, so this endpoint is how a local jog producer sees its refusals.

#### Request

No parameters.

#### Response

`data` is a [`JogIngressObservation`](#type-jogingressobservation).

| Name | Type | Required | Description |
|---|---|---|---|
| `source_sequence` | `uint64` | Yes | — |
| `reason` | `uint32` | Yes | — |
| `now_host_ns` | `uint64` | Yes | — |
| `refused` | `uint64` | Yes | Cumulative refused ingress count for this adapter process; accepted frames do not increment it. |
| `refused_by_reason` | `uint64[15]` | Yes | Cumulative refusal counts indexed by protocol/control.json jog reason 0..14; index 0 remains zero. Unverified fixed capacity: 15 reason slots. |
| `latest_refusal` | [`JogIngressRefusal`](#type-jogingressrefusal) | Yes | Most recent refusal across all ingress transports; zero before any refusal and preserved through acceptance and grant changes. |

#### Example

```http
GET /v1/jog/ingress HTTP/1.1
Host: localhost
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "jog_ingress",
  "data": {
    "source_sequence": 120,
    "reason": 0,
    "now_host_ns": 81234567890123,
    "refused": 2,
    "refused_by_reason": [
      0,
      0,
      0,
      0,
      2,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0,
      0
    ],
    "latest_refusal": {
      "source_sequence": 7,
      "reason": 4,
      "now_host_ns": 81230000000000
    }
  }
}
```

#### Reason codes

No operation-specific reason codes. Unknown diagnostics mean the request failed.

**Client libraries.** Go: `Client.JogIngress`. C++: `jog_ingress()`.

### `subscribe_events`

**Endpoint: `GET /v1/events?after=<sequence>`**

Poll or stream the event log from a cursor. State: `implemented`. Lease: none.

**Endpoint: `SSE /v1/events/stream?after=<sequence>`**

Same operation, alternative transport.

`/v1/events` returns one `EventBatch` inside the normal response envelope. `/v1/events/stream` is Server-Sent Events: each message has `id` (the sequence), `event` (the type) and `data` (one `Event` as JSON, with no envelope). `after` is the last sequence you handled; omit it to start from 0.

The ring keeps 256 events and a batch holds at most 64. The full model, including loss handling, is in [Events and telemetry](https://advancedmetalresearch.com/docs/apis/rt-control-events-telemetry#events).

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `after` (query) | `uint64` | No | Last consumed sequence, as unsigned decimal text. Default 0. |

#### Response

`data` is a [`EventBatch`](#type-eventbatch).

| Name | Type | Required | Description |
|---|---|---|---|
| `events` | [`Event[]`](#type-event) | Yes | At most 64 records, including at most one leading events_dropped record; 256 retained events. |
| `next_sequence` | `uint64` | Yes | Resume cursor after the last returned event; unchanged when empty. |
| `latest_sequence` | `uint64` | Yes | Newest retained sequence at batch capture. |
| `adapter_incarnation` | `string` | Yes | — |

#### Example

```http
GET /v1/events?after=41 HTTP/1.1
Host: localhost
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "subscribe_events",
  "data": {
    "events": [
      {
        "sequence": 42,
        "time_ns": 81234567890123,
        "type": "grant_acquired",
        "daemon_incarnation": "3b9d…",
        "adapter_incarnation": "a41c…",
        "grant": {
          "…": "…"
        }
      }
    ],
    "next_sequence": 42,
    "latest_sequence": 42,
    "adapter_incarnation": "a41c…"
  }
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`invalid_event_cursor`](/docs/reference/error-codes#reasons-observation) | `after` is duplicated, empty or not one unsigned decimal uint64. |
| [`event_cursor_ahead`](/docs/reference/error-codes#reasons-observation) | `after` is newer than this adapter incarnation's newest event. |
| [`session_principal_mismatch`](/docs/reference/error-codes#reasons-authority) | An `X-Control-Session` header or `session` query names a session owned by another principal. |
| [`event_cursor_lost`](/docs/reference/error-codes#reasons-observation) | Raised by the C++ client (`EventCursorLost`) when a stream reports `events_dropped`. The server never returns it. |

**Client libraries.** Go: `Client.Events`, `Client.StreamEvents`. C++: `events()`, `events_stream()`.

### `telemetry`

**Endpoint: `GET /v1/telemetry?after=<sequence>`**

Read full-rate binary cycle records from a cursor. State: `implemented`. Lease: none.

**Endpoint: `GET /v1/telemetry/stream?after=<sequence>`**

Same operation, alternative transport.

Returns `application/octet-stream`: one 312-byte `TelemetryBatchHeaderV1` followed by `record_count` 5392-byte `CycleCaptureRecordV2` records. It is never JSON. `/v1/telemetry` returns one batch (gzip if you send `Accept-Encoding: gzip`). `/v1/telemetry/stream` writes one complete batch per flush, about every 20 ms, including empty batches.

Errors come back as a JSON `Response`. The binary layout and decoders are in [Events and telemetry](https://advancedmetalresearch.com/docs/apis/rt-control-events-telemetry#telemetry).

The contract describes it as: “Full-rate binary cycle records; observation needs no session or authority.”

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `after` (query) | `uint64` | No | Last consumed record sequence. 0 (the default) starts at sequence 1 and reports overwritten history in `dropped`. |

#### Response

The body is binary: a [`TelemetryBatch`](#type-telemetrybatch).

#### Example

```http
GET /v1/telemetry?after=0 HTTP/1.1
Host: localhost
Accept-Encoding: gzip
```

200 OK:

```text
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Encoding: gzip
Cache-Control: no-store

<312-byte header><record_count × 5392-byte records>
```

#### Reason codes

| Reason | When |
|---|---|
| [`invalid_telemetry_cursor`](/docs/reference/error-codes#reasons-observation) | `after` is duplicated, empty or not one unsigned decimal uint64. |
| [`telemetry_cursor_ahead`](/docs/reference/error-codes#reasons-observation) | `after` is beyond the ring's current sequence. Reconcile the incarnation first. |
| [`telemetry_busy`](/docs/reference/error-codes#reasons-observation) | The ring header stayed torn after bounded retries. Retry the same cursor; the reply carries `Retry-After: 1`. |
| [`telemetry_unavailable`](/docs/reference/error-codes#reasons-observation) | No compatible live telemetry ring could be observed. |
| [`session_principal_mismatch`](/docs/reference/error-codes#reasons-authority) | A supplied session header or query belongs to another principal. |

**Client libraries.** Go: `Client.TelemetryBatches`, `Client.TelemetryStream`. C++: `telemetry()`, `telemetry_stream()`.

### `resource`

**Endpoint: `GET /v1/resources/<sha256>`**

Download one immutable compiled robot resource by digest. State: `implemented`. Lease: none.

Serves the files of the compiled robot description (`robot_description_manifest.json`, `robot.urdf`, meshes, `machine_planning_calibration.json` …) listed in Describe `robot.resources`. The path component is the lowercase SHA-256 of the exact bytes.

The reply is the raw bytes with `Content-Type`, `Content-Length` and a quoted-digest `ETag`. rt-control never looks anything up in a repository at request time: only the loaded compiled resource set is served. Remote clients need the mutual-TLS listener, like every remote call.

The contract describes it as: “Read one bounded content-addressed compiled resource without a lease; remote mutual TLS required.”

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `sha256` (path) | `string` | Yes | 64 lowercase hex characters, from `ResourceInfo.sha256`. |

#### Response

The body is the resource itself. `ResourceInfo` (from Describe) describes it.

#### Example

```http
GET /v1/resources/9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 HTTP/1.1
Host: localhost
```

200 OK:

```text
HTTP/1.1 200 OK
Content-Type: application/xml
Content-Length: 48213
ETag: "9f86d081…"
X-Content-Type-Options: nosniff

<robot name="…">…
```

#### Reason codes

| Reason | When |
|---|---|
| [`resource_unknown`](/docs/reference/error-codes#reasons-observation) | The digest is malformed or not in the loaded resource set. HTTP 404. |

**Client libraries.** Go: `Client.Resource`. C++: none.

## Authority

One controller at a time. See [Control authority](https://advancedmetalresearch.com/docs/concepts/control-authority) for the model behind these four calls.

### `acquire`

**Endpoint: `POST /v1/control`**

Take control of the cell and receive a session and fence. State: `implemented`. Lease: no fence; checks `binding`.

Only one controller holds authority at a time. `acquire` checks your `binding` against the pair and digests rt-control was started with. When you supply `machine_sha256` it must match exactly and there is no fallback; without it, `configuration_sha256` must match. On success you get a fresh 64-hex `session`, a `generation` one higher than the last, and the effective `lease_ms`.

The effective lease is `min(requested_lease_ms, cell ceiling)`. Omit `requested_lease_ms` (or send 0) for the 500 ms default, still capped by the cell. Renew before it runs out. Local socket permissions or the remote TLS identity authenticate the connection; `controller` is only a label.

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `schema` | `string` | Yes | `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | `acquire`. |
| `controller` | `string` | Yes | Opaque controller name, 1..63 bytes. Not a credential. |
| `binding` | [`Binding`](#type-binding) | Yes | Pair ID and revision rt-control was started with, plus `configuration_sha256` and, preferably, `machine_sha256` from Describe. |
| `requested_lease_ms` | `int` | No | Requested lease in whole ms, 1..10000. Default 0: keep the 500 ms LAN default, capped by the cell ceiling. |
| `request_id` | `string` | No | Idempotence key, 1..64 printable ASCII bytes. See [retries](https://advancedmetalresearch.com/docs/apis/rt-control-http#idempotent-retries). |

The contract's semantically required fields: `schema`, `operation`, `controller`, `binding`.

#### Response

`data` is a [`Grant`](#type-grant).

| Name | Type | Required | Description |
|---|---|---|---|
| `stopping` | `bool` | Yes | True means renewal keeps the lease alive while Stop is in flight; it grants no movement permission. |
| `deadline_host_ns` | `uint64` | Yes | Uint64 absolute lease deadline in host monotonic nanoseconds. |
| `session` | `string` | Yes | 64-character unpredictable lowercase hexadecimal token while owned; empty after release/expiry. |
| `generation` | `uint64` | Yes | Monotonically increasing uint64 application generation, fenced by Stop and acquisition. |
| `controller` | `string` | Yes | The `controller` label sent to `acquire`. |
| `binding` | [`Binding`](#type-binding) | Yes | The binding, completed with the adapter's digests. |
| `lease_ms` | `int` | Yes | Effective negotiated lease in whole milliseconds: min(client requested lease, compiled cell ceiling); zero/omitted request retains 500 ms capped by the cell. Renew preserves this value. Longer leases increase worst-case unattended-stop delay after link loss. Unverified absolute bound: 10000 ms. |

#### Example

```http
POST /v1/control HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "schema": "rosie.rt-control.request.v1",
  "operation": "acquire",
  "controller": "my-app",
  "binding": {
    "pair_id": "cell-a",
    "revision": 1,
    "configuration_sha256": "c2b097096fef301cad3b9c3e594f32266c625feaccba02be3410ed08df2b63ea",
    "machine_sha256": "4f1d9a7c2e6b8d0f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f"
  },
  "requested_lease_ms": 500
}
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "acquire",
  "data": {
    "stopping": false,
    "deadline_host_ns": 81234567890123,
    "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
    "generation": 3,
    "controller": "my-app",
    "binding": {
      "pair_id": "cell-a",
      "revision": 1,
      "configuration_sha256": "c2b097096fef301cad3b9c3e594f32266c625feaccba02be3410ed08df2b63ea",
      "machine_sha256": "4f1d9a7c2e6b8d0f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f"
    },
    "lease_ms": 500
  }
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`authority_binding_mismatch`](/docs/reference/error-codes#reasons-authority) | `controller` is empty or longer than 63 bytes, or `binding` does not match the configured pair, revision and digest. |
| [`control_already_owned`](/docs/reference/error-codes#reasons-authority) | Another session holds authority, or a Stop is still draining. |
| [`application_generation_exhausted`](/docs/reference/error-codes#reasons-authority) | The uint64 grant generation is exhausted. Restart and reconcile. |
| [`invalid_requested_lease`](/docs/reference/error-codes#reasons-authority) | `requested_lease_ms` is negative or above 10000. |
| [`control_session_stale`](/docs/reference/error-codes#reasons-authority) | The new grant was lost while it was being mirrored to the core. |
| [`session_principal_mismatch`](/docs/reference/error-codes#reasons-authority) | A retried `request_id` was first used by another transport principal. |

Also the common [envelope](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-envelope) reasons.

**Client libraries.** Go: `Client.Acquire`, `Client.AcquireLease`. C++: `acquire()`.

### `renew`

**Endpoint: `POST /v1/control`**

Extend the lease of the current session. State: `implemented`. Lease: session and generation.

Renew keeps the session alive. The deadline moves to now plus the effective `lease_ms`, which never changes after Acquire. Renew at most every third of the lease, on its own connection, and use a fresh `request_id` each time: a reused ID replays the old receipt.

An older non-zero generation of the same live session may renew and learns the current fence from the reply, but it cannot move the robot. While a Stop is in flight, renew returns `stopping: true`: the lease is kept alive, and no motion permission is granted.

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `schema` | `string` | Yes | `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | `renew`. |
| `session` | `string` | Yes | Session token from the current `Grant`. |
| `generation` | `uint64` | Yes | Current grant generation. |
| `request_id` | `string` | No | Idempotence key, 1..64 printable ASCII bytes. See [retries](https://advancedmetalresearch.com/docs/apis/rt-control-http#idempotent-retries). |

The contract's semantically required fields: `schema`, `operation`, `session`, `generation`.

#### Response

`data` is a [`Grant`](#type-grant).

| Name | Type | Required | Description |
|---|---|---|---|
| `stopping` | `bool` | Yes | True means renewal keeps the lease alive while Stop is in flight; it grants no movement permission. |
| `deadline_host_ns` | `uint64` | Yes | Uint64 absolute lease deadline in host monotonic nanoseconds. |
| `session` | `string` | Yes | 64-character unpredictable lowercase hexadecimal token while owned; empty after release/expiry. |
| `generation` | `uint64` | Yes | Monotonically increasing uint64 application generation, fenced by Stop and acquisition. |
| `controller` | `string` | Yes | The `controller` label sent to `acquire`. |
| `binding` | [`Binding`](#type-binding) | Yes | The binding, completed with the adapter's digests. |
| `lease_ms` | `int` | Yes | Effective negotiated lease in whole milliseconds: min(client requested lease, compiled cell ceiling); zero/omitted request retains 500 ms capped by the cell. Renew preserves this value. Longer leases increase worst-case unattended-stop delay after link loss. Unverified absolute bound: 10000 ms. |

#### Example

```http
POST /v1/control HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "schema": "rosie.rt-control.request.v1",
  "operation": "renew",
  "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
  "generation": 3,
  "request_id": "5f0c2a9e6b1d4c3a8e7f0b2d4c6a8e1f"
}
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "renew",
  "data": {
    "stopping": false,
    "deadline_host_ns": 81234567890123,
    "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
    "generation": 3,
    "controller": "my-app",
    "binding": {
      "pair_id": "cell-a",
      "revision": 1,
      "configuration_sha256": "c2b097096fef301cad3b9c3e594f32266c625feaccba02be3410ed08df2b63ea",
      "machine_sha256": "4f1d9a7c2e6b8d0f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f"
    },
    "lease_ms": 500
  }
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`expired`](/docs/reference/error-codes#reasons-authority) | The lease deadline passed, or the core reported the grant expired. Stop producing, reconcile and acquire again. |
| [`control_session_stale`](/docs/reference/error-codes#reasons-authority) | The session is not the current one, or `generation` is 0 or newer than the current generation. |

Also the common [envelope](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-envelope) and [authority](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-authority) reasons.

**Client libraries.** Go: `Client.Renew`, `Client.StartRenewal`. C++: `renew()`, `start_renewal()`.

### `release`

**Endpoint: `POST /v1/control`**

Stop, then give up authority. State: `implemented`. Lease: session and generation.

Release runs the same inhibiting sequence as Stop and then clears the session: the returned `Grant` has an empty `session`. Stop your renewal loop first. The Go and C++ clients do that for you and never replay a Release automatically after a transport failure.

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `schema` | `string` | Yes | `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | `release`. |
| `session` | `string` | Yes | Session token from the current `Grant`. |
| `generation` | `uint64` | Yes | Current grant generation. |
| `request_id` | `string` | No | Idempotence key, 1..64 printable ASCII bytes. See [retries](https://advancedmetalresearch.com/docs/apis/rt-control-http#idempotent-retries). |

The contract's semantically required fields: `schema`, `operation`, `session`, `generation`.

#### Response

`data` is a [`Grant`](#type-grant).

| Name | Type | Required | Description |
|---|---|---|---|
| `stopping` | `bool` | Yes | True means renewal keeps the lease alive while Stop is in flight; it grants no movement permission. |
| `deadline_host_ns` | `uint64` | Yes | Uint64 absolute lease deadline in host monotonic nanoseconds. |
| `session` | `string` | Yes | 64-character unpredictable lowercase hexadecimal token while owned; empty after release/expiry. |
| `generation` | `uint64` | Yes | Monotonically increasing uint64 application generation, fenced by Stop and acquisition. |
| `controller` | `string` | Yes | The `controller` label sent to `acquire`. |
| `binding` | [`Binding`](#type-binding) | Yes | The binding, completed with the adapter's digests. |
| `lease_ms` | `int` | Yes | Effective negotiated lease in whole milliseconds: min(client requested lease, compiled cell ceiling); zero/omitted request retains 500 ms capped by the cell. Renew preserves this value. Longer leases increase worst-case unattended-stop delay after link loss. Unverified absolute bound: 10000 ms. |

#### Example

```http
POST /v1/control HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "schema": "rosie.rt-control.request.v1",
  "operation": "release",
  "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
  "generation": 3
}
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "release",
  "data": {
    "stopping": false,
    "deadline_host_ns": 81234567890123,
    "session": "",
    "generation": 4,
    "controller": "my-app",
    "binding": {
      "pair_id": "cell-a",
      "revision": 1,
      "configuration_sha256": "c2b097096fef301cad3b9c3e594f32266c625feaccba02be3410ed08df2b63ea",
      "machine_sha256": "4f1d9a7c2e6b8d0f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f"
    },
    "lease_ms": 500
  }
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`control_session_stale`](/docs/reference/error-codes#reasons-authority) | The session was never issued by this adapter, or `generation` is 0 or newer than current. |
| [`expired`](/docs/reference/error-codes#reasons-authority) | The lease expired while the release was being processed. |

Also the common [envelope](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-envelope) and [authority](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-authority) reasons.

**Client libraries.** Go: `Client.Release`. C++: `release()`.

### `stop`

**Endpoint: `POST /v1/control`**

Inhibit outputs immediately and fence all motion. State: `implemented`. Lease: session and generation.

> [!WARNING] This call can energise the drives or move the robot. Keep the hardware E-stop within reach. See the [safety model](https://advancedmetalresearch.com/docs/get-started/safety-model).

Stop cancels execution, uploads, handles and jog at once, inhibits the drive outputs and increments the generation. It does not wait for a ramp. If the session is still valid you get it back with the new generation, and you need a fresh Enable and Arm before moving again.

A session that has expired or been revoked can still send Stop to inhibit, but it cannot regain motion permission. Concurrent Stop and Release calls join one cancellation. A Stop receipt does not prove the robot is at standstill.

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `schema` | `string` | Yes | `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | `stop`. |
| `session` | `string` | Yes | Session token from the current `Grant`. |
| `generation` | `uint64` | Yes | Current grant generation. |
| `request_id` | `string` | No | Idempotence key, 1..64 printable ASCII bytes. See [retries](https://advancedmetalresearch.com/docs/apis/rt-control-http#idempotent-retries). |

The contract's semantically required fields: `schema`, `operation`, `session`, `generation`.

#### Response

`data` is a [`Grant`](#type-grant).

| Name | Type | Required | Description |
|---|---|---|---|
| `stopping` | `bool` | Yes | True means renewal keeps the lease alive while Stop is in flight; it grants no movement permission. |
| `deadline_host_ns` | `uint64` | Yes | Uint64 absolute lease deadline in host monotonic nanoseconds. |
| `session` | `string` | Yes | 64-character unpredictable lowercase hexadecimal token while owned; empty after release/expiry. |
| `generation` | `uint64` | Yes | Monotonically increasing uint64 application generation, fenced by Stop and acquisition. |
| `controller` | `string` | Yes | The `controller` label sent to `acquire`. |
| `binding` | [`Binding`](#type-binding) | Yes | The binding, completed with the adapter's digests. |
| `lease_ms` | `int` | Yes | Effective negotiated lease in whole milliseconds: min(client requested lease, compiled cell ceiling); zero/omitted request retains 500 ms capped by the cell. Renew preserves this value. Longer leases increase worst-case unattended-stop delay after link loss. Unverified absolute bound: 10000 ms. |

#### Example

```http
POST /v1/control HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "schema": "rosie.rt-control.request.v1",
  "operation": "stop",
  "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
  "generation": 3
}
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "stop",
  "data": {
    "stopping": false,
    "deadline_host_ns": 81234567890123,
    "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
    "generation": 4,
    "controller": "my-app",
    "binding": {
      "pair_id": "cell-a",
      "revision": 1,
      "configuration_sha256": "c2b097096fef301cad3b9c3e594f32266c625feaccba02be3410ed08df2b63ea",
      "machine_sha256": "4f1d9a7c2e6b8d0f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f"
    },
    "lease_ms": 500
  }
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`control_session_stale`](/docs/reference/error-codes#reasons-authority) | The session was never issued by this adapter, or `generation` is 0 or newer than current. |
| [`expired`](/docs/reference/error-codes#reasons-authority) | The lease expired while the stop was reacquiring native authority. |

Also the common [envelope](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-envelope) and [authority](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-authority) reasons.

**Client libraries.** Go: `Client.Stop`. C++: `stop()`.

## Machine control

Every call here needs the current `session` and `generation`.

### `enable`

**Endpoint: `POST /v1/control`**

Request CiA402 enable for the selected axes. State: `implemented`. Lease: session and generation.

> [!WARNING] This call can energise the drives or move the robot. Keep the hardware E-stop within reach. See the [safety model](https://advancedmetalresearch.com/docs/get-started/safety-model).

Sends a native enable for `axis_mask` and waits for the native result. The receipt only confirms admission. Poll Status until each axis reports the expected enable bit and readiness. Enable and Arm on their own do not establish Home or permit a Start.

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `schema` | `string` | Yes | `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | `enable`. |
| `session` | `string` | Yes | Session token from the current `Grant`. |
| `generation` | `uint64` | Yes | Current grant generation. |
| `axis_mask` | `uint32` | Yes | Axes to enable, by Describe index (bit 0 = first axis). A nine-axis cell uses 511. |
| `request_id` | `string` | No | Idempotence key, 1..64 printable ASCII bytes. See [retries](https://advancedmetalresearch.com/docs/apis/rt-control-http#idempotent-retries). |

The contract's semantically required fields: `schema`, `operation`, `session`, `generation`, `axis_mask`.

#### Response

`sequence` holds the native command sequence. There is no `data`.

#### Example

```http
POST /v1/control HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "schema": "rosie.rt-control.request.v1",
  "operation": "enable",
  "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
  "generation": 3,
  "axis_mask": 511
}
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "enable",
  "sequence": 17
}
```

#### Reason codes

Only the common [envelope](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-envelope), [authority](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-authority) and [native](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-native) reasons.

**Client libraries.** Go: `Client.Enable`. C++: `enable()`.

### `arm`

**Endpoint: `POST /v1/control`**

Arm the core so that motion commands can be admitted. State: `implemented`. Lease: session and generation.

> [!WARNING] This call can energise the drives or move the robot. Keep the hardware E-stop within reach. See the [safety model](https://advancedmetalresearch.com/docs/get-started/safety-model).

Sends a native arm and waits for its result. Observe `core.armed == 1` in Status. A successful Arm does not mean every axis is ready: the [motion start gate](https://advancedmetalresearch.com/docs/concepts/real-time-core#the-motion-start-gate) is still checked at every Start and jog.

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `schema` | `string` | Yes | `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | `arm`. |
| `session` | `string` | Yes | Session token from the current `Grant`. |
| `generation` | `uint64` | Yes | Current grant generation. |
| `request_id` | `string` | No | Idempotence key, 1..64 printable ASCII bytes. See [retries](https://advancedmetalresearch.com/docs/apis/rt-control-http#idempotent-retries). |

The contract's semantically required fields: `schema`, `operation`, `session`, `generation`.

#### Response

`sequence` holds the native command sequence. There is no `data`.

#### Example

```http
POST /v1/control HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "schema": "rosie.rt-control.request.v1",
  "operation": "arm",
  "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
  "generation": 3
}
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "arm",
  "sequence": 18
}
```

#### Reason codes

Only the common [envelope](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-envelope), [authority](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-authority) and [native](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-native) reasons.

**Client libraries.** Go: `Client.Arm`. C++: `arm()`.

### `home`

**Endpoint: `POST /v1/control`**

Run the drives' native Home on the selected axes. State: `implemented`. Lease: session and generation.

> [!WARNING] This call can energise the drives or move the robot. Keep the hardware E-stop within reach. See the [safety model](https://advancedmetalresearch.com/docs/get-started/safety-model).

Runs native Home (commissioning) on `axis_mask` and waits up to 60 s for the result. Home leaves the selected axes disabled: observe the new `home_epoch`, an idle `commissioning_phase` and every selected bit in `home_valid_mask`, then Enable and Arm again. If Home fails, rt-control issues a Stop.

When `ROSIE_RT_ANCHOR_DIR` is set in rt-control's environment, a successful Home also saves one anchor file per selected axis, for later use by `restore_anchor`. Losing the HTTP reply does not cancel Home; Stop, lease expiry or transport loss do.

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `schema` | `string` | Yes | `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | `home`. |
| `session` | `string` | Yes | Session token from the current `Grant`. |
| `generation` | `uint64` | Yes | Current grant generation. |
| `axis_mask` | `uint32` | Yes | Non-zero mask of configured axes whose Describe entry has `native_home: true`. |
| `request_id` | `string` | No | Idempotence key, 1..64 printable ASCII bytes. See [retries](https://advancedmetalresearch.com/docs/apis/rt-control-http#idempotent-retries). |

The contract's semantically required fields: `schema`, `operation`, `session`, `generation`, `axis_mask`.

#### Response

`sequence` holds the native command sequence. There is no `data`.

#### Example

```http
POST /v1/control HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "schema": "rosie.rt-control.request.v1",
  "operation": "home",
  "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
  "generation": 3,
  "axis_mask": 511
}
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "home",
  "sequence": 19
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`anchor_source_invalid`](/docs/reference/error-codes#reasons-recovery) | Anchor capture after Home found the machine armed or enabled, or a selected axis had no valid source. |
| [`anchor_identity_mismatch`](/docs/reference/error-codes#reasons-recovery) | The captured anchor does not match the adapter's pair ID and revision. |
| [`anchor_store_io`](/docs/reference/error-codes#reasons-recovery) | The anchor directory could not be written. Details go to the local log only. |

Also the common [envelope](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-envelope), [authority](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-authority) and [native](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-native) reasons.

**Client libraries.** Go: `Client.Home`. C++: `home()`.

### `halt`

**Endpoint: `POST /v1/control`**

Decelerate to an enabled hold, keeping authority and Arm. State: `implemented`. Lease: session and generation.

> [!WARNING] This call can energise the drives or move the robot. Keep the hardware E-stop within reach. See the [safety model](https://advancedmetalresearch.com/docs/get-started/safety-model).

Halt requests a controlled deceleration using the active trajectory's acceleration, or the jog acceleration when no trajectory bound is declared. It retires the active trajectory handle and jog generation, but keeps the grant, Enable and Arm. A new Start must begin at the held target; jogging needs a new `begin_jog`.

Halt does not replace Stop. Stop, faults and lease expiry always inhibit immediately. The receipt confirms admission, not a completed hold. A native refusal also carries `native_result`.

The contract describes it as: “Controlled deceleration to enabled hold using the active trajectory acceleration, or jog acceleration when no trajectory bound is declared. Retires active motion; preserves authority and Arm. Stop and faults always inhibit immediately.”

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `schema` | `string` | Yes | `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | `halt`. |
| `session` | `string` | Yes | Session token from the current `Grant`. |
| `generation` | `uint64` | Yes | Current grant generation. |
| `request_id` | `string` | No | Idempotence key, 1..64 printable ASCII bytes. See [retries](https://advancedmetalresearch.com/docs/apis/rt-control-http#idempotent-retries). |

The contract's semantically required fields: `schema`, `operation`, `session`, `generation`.

#### Response

`sequence` holds the native command sequence. There is no `data`.

#### Example

```http
POST /v1/control HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "schema": "rosie.rt-control.request.v1",
  "operation": "halt",
  "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
  "generation": 3
}
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "halt",
  "sequence": 20
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`no_grant`](/docs/reference/error-codes#reasons-authority) | There is no current valid grant for this session, or its lease has expired. |
| [`wrong_generation`](/docs/reference/error-codes#reasons-authority) | `generation` differs from the current application or native generation. |
| [`inhibited`](/docs/reference/error-codes#reasons-authority) | A Stop is in flight, or the core is not armed, enabled and ready (native Home and service also refuse Halt). |
| [`capability_unimplemented`](/docs/reference/error-codes#reasons-envelope) | The native client has no Halt support. |

Also the common [envelope](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-envelope) and [authority](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-authority) reasons.

**Client libraries.** Go: `Client.Command with Operation "halt"`. C++: `command() with operation halt`.

### `restore_anchor`

**Endpoint: `POST /v1/control`**

Re-establish Home from saved absolute-encoder anchors, without moving. State: `implemented`. Lease: session and generation.

An alternative to Home after a restart. The machine must be disarmed, disabled and not commissioning. For every selected axis, the saved anchor's pair, revision, coordinate identity, drive identity and absolute source must match current evidence, and the core checks them again independently. Set `ROSIE_RT_ANCHOR_DIR` to the same directory for rt-control (write) and the core (read).

On success the reply carries the restore receipt in `sequence` and a `RecoveryStatus` read after it, with the selected bits set in `home_valid_mask`. Restoring never enables or arms.

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `schema` | `string` | Yes | `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | `restore_anchor`. |
| `session` | `string` | Yes | Session token from the current `Grant`. |
| `generation` | `uint64` | Yes | Current grant generation. |
| `axis_mask` | `uint32` | Yes | Non-zero mask of configured axes to restore. |
| `request_id` | `string` | No | Idempotence key, 1..64 printable ASCII bytes. See [retries](https://advancedmetalresearch.com/docs/apis/rt-control-http#idempotent-retries). |

The contract's semantically required fields: `schema`, `operation`, `session`, `generation`, `axis_mask`.

#### Response

`sequence` holds the native command sequence and `data` is a [`RecoveryStatus`](#type-recoverystatus).

| Name | Type | Required | Description |
|---|---|---|---|
| `home_valid_mask` | `uint32` | Yes | Native per-axis home evidence remaining after observed recovery. |
| `faults` | [`FaultRecovery[]`](#type-faultrecovery) | Yes | Currently latched fault bits with axis masks and recovery policy. |
| `reset_sequence` | `uint64` | Yes | Exact native command sequence of the observed reset, zero before any reset; response sequence must match for reset_fault. |
| `reason` | `string` | Yes | Native recovery summary: empty when no fault persists, otherwise fault_persists; FaultResetRecovery refuses a persistent fault. |
| `outcomes` | [`FaultRecovery[]`](#type-faultrecovery) | Yes | Per-bit outcomes of the correlated reset; never inferred from elapsed time. |

#### Example

```http
POST /v1/control HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "schema": "rosie.rt-control.request.v1",
  "operation": "restore_anchor",
  "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
  "generation": 3,
  "axis_mask": 511
}
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "restore_anchor",
  "sequence": 21,
  "data": {
    "home_valid_mask": 511,
    "faults": [],
    "reset_sequence": 0,
    "reason": "",
    "outcomes": []
  }
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`invalid_axis_mask`](/docs/reference/error-codes#reasons-recovery) | `axis_mask` is 0 or names an axis outside the configured group. |
| [`mode_conflict`](/docs/reference/error-codes#reasons-native) | The machine is armed, enabled or commissioning. |
| [`anchor_missing`](/docs/reference/error-codes#reasons-recovery) | No saved anchor exists for a selected axis. Run Home. |
| [`anchor_identity_mismatch`](/docs/reference/error-codes#reasons-recovery) | The anchor was saved under another pair, revision, configuration, drive identity or Home epoch. |
| [`anchor_source_invalid`](/docs/reference/error-codes#reasons-recovery) | The drive reports no valid absolute source for the axis. |
| [`anchor_disagrees`](/docs/reference/error-codes#reasons-recovery) | The current absolute source disagrees with the anchor beyond tolerance. Investigate, then Home. |
| [`anchor_store_io`](/docs/reference/error-codes#reasons-recovery) | The anchor directory could not be read. |
| [`recovery_observation_unavailable`](/docs/reference/error-codes#reasons-recovery) | No matching native observation arrived within 1 s of the receipt. |

Also the common [envelope](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-envelope), [authority](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-authority) and [native](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-native) reasons.

**Client libraries.** Go: `Client.RestoreAnchor`. C++: `restore_anchor()`.

### `reset_fault`

**Endpoint: `POST /v1/control`**

Clear latched execution faults (interim). Ends the session. State: `interim`. Lease: session and generation.

`reset_fault` is labelled `interim`. It needs an inhibited, idle machine: not armed, no active jog, no commissioning, no native Home and no executing program. It submits one native fault reset for `axis_mask` (0 means every configured axis) and reports the correlated result: `sequence`, `native_result` and a `RecoveryStatus` whose `reset_sequence` matches.

A submitted reset **retires your session**, even when the core refuses it. Acquire again afterwards. Any persistent condition refuses the whole reset (`fault_persists`); there is no partial clear. Reset never starts motion and never grants Home. See [fault recovery](https://advancedmetalresearch.com/docs/concepts/real-time-core#faults-and-recovery).

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `schema` | `string` | Yes | `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | `reset_fault`. |
| `session` | `string` | Yes | Session token from the current `Grant`. |
| `generation` | `uint64` | Yes | Current grant generation. |
| `axis_mask` | `uint32` | No | Axes to reset. Default 0: all configured axes. |
| `request_id` | `string` | No | Idempotence key, 1..64 printable ASCII bytes. See [retries](https://advancedmetalresearch.com/docs/apis/rt-control-http#idempotent-retries). |

The contract's semantically required fields: `schema`, `operation`, `session`, `generation`.

#### Response

`sequence` holds the native command sequence and `data` is a [`RecoveryStatus`](#type-recoverystatus). `native_result` carries the native `CommandResult` of the reset.

| Name | Type | Required | Description |
|---|---|---|---|
| `home_valid_mask` | `uint32` | Yes | Native per-axis home evidence remaining after observed recovery. |
| `faults` | [`FaultRecovery[]`](#type-faultrecovery) | Yes | Currently latched fault bits with axis masks and recovery policy. |
| `reset_sequence` | `uint64` | Yes | Exact native command sequence of the observed reset, zero before any reset; response sequence must match for reset_fault. |
| `reason` | `string` | Yes | Native recovery summary: empty when no fault persists, otherwise fault_persists; FaultResetRecovery refuses a persistent fault. |
| `outcomes` | [`FaultRecovery[]`](#type-faultrecovery) | Yes | Per-bit outcomes of the correlated reset; never inferred from elapsed time. |

#### Example

```http
POST /v1/control HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "schema": "rosie.rt-control.request.v1",
  "operation": "reset_fault",
  "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
  "generation": 3,
  "axis_mask": 0
}
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "reset_fault",
  "sequence": 22,
  "native_result": {
    "Sequence": 22,
    "Handle": 0,
    "Generation": 5,
    "Operation": 260,
    "Result": 0,
    "Reason": 0,
    "AxisMask": 511
  },
  "data": {
    "home_valid_mask": 511,
    "faults": [],
    "reset_sequence": 22,
    "reason": "",
    "outcomes": []
  }
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`program_already_executing`](/docs/reference/error-codes#reasons-program) | A trajectory or program is still executing. Stop first. |
| [`reset_requires_inhibited`](/docs/reference/error-codes#reasons-recovery) | The machine is armed, jogging, commissioning or homing, or no fresh observation arrived within 1 s. |
| [`recovery_observation_unavailable`](/docs/reference/error-codes#reasons-recovery) | No correlated recovery publication arrived within 1 s of the receipt. |
| [`fault_persists`](/docs/reference/error-codes#reasons-recovery) | At least one selected fault condition is still present. |

Also the common [envelope](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-envelope), [authority](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-authority) and [native](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-native) reasons.

**Client libraries.** Go: `Client.ResetFault`. C++: `reset_fault()`.

### `recovery_status`

**Endpoint: `POST /v1/control`**

Read the latched faults and their recovery classes. Changes nothing. State: `implemented`. Lease: session and generation.

Requires the same inhibited, idle machine as `reset_fault`, but clears nothing and keeps the session. Each `faults[]` entry names the fault bit, the affected axes and its recovery class.

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `schema` | `string` | Yes | `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | `recovery_status`. |
| `session` | `string` | Yes | Session token from the current `Grant`. |
| `generation` | `uint64` | Yes | Current grant generation. |
| `request_id` | `string` | No | Idempotence key, 1..64 printable ASCII bytes. See [retries](https://advancedmetalresearch.com/docs/apis/rt-control-http#idempotent-retries). |

The contract's semantically required fields: `schema`, `operation`, `session`, `generation`.

#### Response

`data` is a [`RecoveryStatus`](#type-recoverystatus).

| Name | Type | Required | Description |
|---|---|---|---|
| `home_valid_mask` | `uint32` | Yes | Native per-axis home evidence remaining after observed recovery. |
| `faults` | [`FaultRecovery[]`](#type-faultrecovery) | Yes | Currently latched fault bits with axis masks and recovery policy. |
| `reset_sequence` | `uint64` | Yes | Exact native command sequence of the observed reset, zero before any reset; response sequence must match for reset_fault. |
| `reason` | `string` | Yes | Native recovery summary: empty when no fault persists, otherwise fault_persists; FaultResetRecovery refuses a persistent fault. |
| `outcomes` | [`FaultRecovery[]`](#type-faultrecovery) | Yes | Per-bit outcomes of the correlated reset; never inferred from elapsed time. |

#### Example

```http
POST /v1/control HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "schema": "rosie.rt-control.request.v1",
  "operation": "recovery_status",
  "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
  "generation": 3
}
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "recovery_status",
  "data": {
    "home_valid_mask": 511,
    "faults": [
      {
        "bit": 5,
        "name": "following_error",
        "axis_mask": 4,
        "recovery": "reset_after_condition_clears",
        "outcome": "persists",
        "rehome_axis_mask": 0
      }
    ],
    "reset_sequence": 0,
    "reason": "fault_persists",
    "outcomes": []
  }
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`program_already_executing`](/docs/reference/error-codes#reasons-program) | A trajectory or program is executing. |
| [`reset_requires_inhibited`](/docs/reference/error-codes#reasons-recovery) | The machine is armed, jogging, commissioning or homing, or no fresh observation arrived within 1 s. |
| [`recovery_observation_unavailable`](/docs/reference/error-codes#reasons-recovery) | The core published no recovery status within 1 s. |

Also the common [envelope](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-envelope) and [authority](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-authority) reasons.

**Client libraries.** Go: `Client.Command with Operation "recovery_status"`. C++: `recovery_status()`.

### `io_arm`

**Endpoint: `POST /v1/control`**

Grant fenced permission for configured, non-torch cell outputs. State: `implemented`. Lease: session and generation.

Cell I/O outputs are only driven after an explicit `io_arm` under a fresh grant, with valid OFF readback observed after the last Stop. Stop commands every output OFF. Torch-class outputs are refused everywhere: see [Process I/O and sensing](https://advancedmetalresearch.com/docs/concepts/process-io-and-sensing).

The receipt confirms native permission only. It does not establish physical feedback.

The contract describes it as: “Native cell I/O permission; torch remains unqualified.”

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `schema` | `string` | Yes | `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | `io_arm`. |
| `session` | `string` | Yes | Session token from the current `Grant`. |
| `generation` | `uint64` | Yes | Current grant generation. |
| `request_id` | `string` | No | Idempotence key, 1..64 printable ASCII bytes. See [retries](https://advancedmetalresearch.com/docs/apis/rt-control-http#idempotent-retries). |

The contract's semantically required fields: `schema`, `operation`, `session`, `generation`.

#### Response

`sequence` holds the native command sequence. There is no `data`.

#### Example

```http
POST /v1/control HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "schema": "rosie.rt-control.request.v1",
  "operation": "io_arm",
  "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
  "generation": 3
}
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "io_arm",
  "sequence": 23
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`capability_unimplemented`](/docs/reference/error-codes#reasons-envelope) | The native client has no cell I/O support. |
| [`no_grant`](/docs/reference/error-codes#reasons-authority) | Native cell I/O refusal: no current grant. |
| [`wrong_generation`](/docs/reference/error-codes#reasons-authority) | Native cell I/O refusal: generation mismatch. |
| [`inhibited`](/docs/reference/error-codes#reasons-authority) | Native cell I/O refusal: outputs are inhibited. |
| [`io_not_configured`](/docs/reference/error-codes#reasons-io) | No cell I/O is configured. |
| [`io_not_armed`](/docs/reference/error-codes#reasons-io) | An ON intent needs `io_arm` first. |
| [`io_fast_input_unsatisfied`](/docs/reference/error-codes#reasons-io) | A cyclic fast input contact is invalid or not satisfied. |
| [`io_readback_disagreement`](/docs/reference/error-codes#reasons-io) | Physical feedback disagrees with the commanded output. |
| [`io_torch_unqualified`](/docs/reference/error-codes#reasons-io) | A torch-class output was requested. Always refused. |
| [`io_marker_late`](/docs/reference/error-codes#reasons-io) | A process marker missed its one-cycle delivery bound. |
| [`io_exchange_lost`](/docs/reference/error-codes#reasons-io) | Cell I/O has no current complete exchange. |

Also the common [envelope](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-envelope), [authority](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-authority) and [native](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-native) reasons.

**Client libraries.** Go: `Client.IOArm`. C++: `io_arm()`.

### `io_disarm`

**Endpoint: `POST /v1/control`**

Withdraw cell-output permission and intent. State: `implemented`. Lease: session and generation.

Clears output permission and intent in the native cycle.

The contract describes it as: “Native cell I/O permission; torch remains unqualified.”

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `schema` | `string` | Yes | `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | `io_disarm`. |
| `session` | `string` | Yes | Session token from the current `Grant`. |
| `generation` | `uint64` | Yes | Current grant generation. |
| `request_id` | `string` | No | Idempotence key, 1..64 printable ASCII bytes. See [retries](https://advancedmetalresearch.com/docs/apis/rt-control-http#idempotent-retries). |

The contract's semantically required fields: `schema`, `operation`, `session`, `generation`.

#### Response

`sequence` holds the native command sequence. There is no `data`.

#### Example

```http
POST /v1/control HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "schema": "rosie.rt-control.request.v1",
  "operation": "io_disarm",
  "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
  "generation": 3
}
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "io_disarm",
  "sequence": 24
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`capability_unimplemented`](/docs/reference/error-codes#reasons-envelope) | The native client has no cell I/O support. |
| [`no_grant`](/docs/reference/error-codes#reasons-authority) | Native cell I/O refusal: no current grant. |
| [`wrong_generation`](/docs/reference/error-codes#reasons-authority) | Native cell I/O refusal: generation mismatch. |
| [`inhibited`](/docs/reference/error-codes#reasons-authority) | Native cell I/O refusal: outputs are inhibited. |
| [`io_not_configured`](/docs/reference/error-codes#reasons-io) | No cell I/O is configured. |
| [`io_not_armed`](/docs/reference/error-codes#reasons-io) | An ON intent needs `io_arm` first. |
| [`io_fast_input_unsatisfied`](/docs/reference/error-codes#reasons-io) | A cyclic fast input contact is invalid or not satisfied. |
| [`io_readback_disagreement`](/docs/reference/error-codes#reasons-io) | Physical feedback disagrees with the commanded output. |
| [`io_torch_unqualified`](/docs/reference/error-codes#reasons-io) | A torch-class output was requested. Always refused. |
| [`io_marker_late`](/docs/reference/error-codes#reasons-io) | A process marker missed its one-cycle delivery bound. |
| [`io_exchange_lost`](/docs/reference/error-codes#reasons-io) | Cell I/O has no current complete exchange. |

Also the common [envelope](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-envelope), [authority](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-authority) and [native](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-native) reasons.

**Client libraries.** Go: `Client.IODisarm`. C++: `io_disarm()`.

### `mark_telemetry`

**Endpoint: `POST /v1/control`**

Write a labelled marker into the telemetry stream. State: `implemented`. Lease: session and generation.

Adds a diagnostic mark. An accepted mark publishes one `telemetry_mark` event carrying the label, the native sequence and the grant generation. It does not take the motion lock, so it works during a long Home. A receipt does not mean anything was written to disk.

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `schema` | `string` | Yes | `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | `mark_telemetry`. |
| `session` | `string` | Yes | Session token from the current `Grant`. |
| `generation` | `uint64` | Yes | Current grant generation. |
| `label` | `string` | Yes | Free text, 1..128 bytes of valid UTF-8. |
| `request_id` | `string` | No | Idempotence key, 1..64 printable ASCII bytes. See [retries](https://advancedmetalresearch.com/docs/apis/rt-control-http#idempotent-retries). |

The contract's semantically required fields: `schema`, `operation`, `session`, `generation`, `label`.

#### Response

`sequence` holds the native command sequence. There is no `data`.

#### Example

```http
POST /v1/control HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "schema": "rosie.rt-control.request.v1",
  "operation": "mark_telemetry",
  "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
  "generation": 3,
  "label": "before weld 3"
}
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "mark_telemetry",
  "sequence": 25
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`telemetry_label_invalid`](/docs/reference/error-codes#reasons-observation) | `label` is empty, longer than 128 bytes or not valid UTF-8. |
| [`capability_unimplemented`](/docs/reference/error-codes#reasons-envelope) | The native client cannot mark telemetry. |

Also the common [envelope](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-envelope), [authority](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-authority) and [native](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-native) reasons.

**Client libraries.** Go: `Client.Command with Operation "mark_telemetry"`. C++: `command() with operation mark_telemetry`.

## Jog

Jogging uses its own lane with its own generation and input deadlines. The JSON calls open and close a jog session; the velocity updates themselves are binary frames.

### `begin_jog`

**Endpoint: `POST /v1/control`**

Open an independent jog session and get its jog generation. State: `implemented`. Lease: session and generation.

> [!WARNING] This call can energise the drives or move the robot. Keep the hardware E-stop within reach. See the [safety model](https://advancedmetalresearch.com/docs/get-started/safety-model).

Reserves native jog mode for `axis_mask`. Get `clock_incarnation` from `GET /v1/jog/clock`. `source_origin_host_ns` is when your input was captured and `deadline_host_ns` is when it must stop applying, both in that host clock. The deadline is never extended by admission or renewal.

The reply's `handle` is the new **jog generation**. Send velocities with [`update_jog`](#update-jog), then close with `end_jog`. Jog velocities are joint-space, in each axis's logical unit per second.

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `schema` | `string` | Yes | `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | `begin_jog`. |
| `session` | `string` | Yes | Session token from the current `Grant`. |
| `generation` | `uint64` | Yes | Current grant generation. |
| `axis_mask` | `uint32` | Yes | Axes this jog session may move. |
| `source_origin_host_ns` | `uint64` | Yes | Input capture time, host `CLOCK_MONOTONIC` ns. |
| `deadline_host_ns` | `uint64` | Yes | Absolute input deadline, host `CLOCK_MONOTONIC` ns, after the origin. |
| `clock_incarnation` | `string` | Yes | `incarnation` from `GET /v1/jog/clock`. Must match exactly. |
| `request_id` | `string` | No | Idempotence key, 1..64 printable ASCII bytes. See [retries](https://advancedmetalresearch.com/docs/apis/rt-control-http#idempotent-retries). |

The contract's semantically required fields: `schema`, `operation`, `session`, `generation`, `axis_mask`, `source_origin_host_ns`, `deadline_host_ns`, `clock_incarnation`.

#### Response

`handle` holds the result. `handle` is the jog generation.

#### Example

```http
POST /v1/control HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "schema": "rosie.rt-control.request.v1",
  "operation": "begin_jog",
  "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
  "generation": 3,
  "axis_mask": 1,
  "source_origin_host_ns": 81234567890123,
  "deadline_host_ns": 81234667890123,
  "clock_incarnation": "0f3c9a…"
}
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "begin_jog",
  "handle": 4
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`jog_clock_incarnation_mismatch`](/docs/reference/error-codes#reasons-jog) | `clock_incarnation` is empty or is not the current host clock incarnation. |
| [`independent_jog_unavailable`](/docs/reference/error-codes#reasons-jog) | The native client offers no independent jog lane. |
| [`mode_conflict`](/docs/reference/error-codes#reasons-native) | A trajectory, program or commissioning is active (native jog reason 2). |
| [`outside_limits_outward`](/docs/reference/error-codes#reasons-native) | An axis is outside its limits (native jog reason 15). |

Also the common [envelope](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-envelope) and [authority](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-authority) reasons.

Other native jog refusals return the diagnostic `RTCore rejected jog (reason N)` with `native_jog_result` set; see [native jog reasons](https://advancedmetalresearch.com/docs/reference/error-codes#native-jog-reasons).

**Client libraries.** Go: `Client.NewJogSession`, `Client.PrepareJogSession`, `Client.BeginJog`. C++: `RtJogProducer`, `begin_jog()`.

### `update_jog`

**Endpoint: `DGRAM jog.sock`**

Send the latest jog velocity: a binary frame, not an HTTP request. State: `implemented`. Lease: session and generation.

**Endpoint: `WSS /v1/jog`**

Same operation, alternative transport.

> [!WARNING] This call can energise the drives or move the robot. Keep the hardware E-stop within reach. See the [safety model](https://advancedmetalresearch.com/docs/get-started/safety-model).

Updates are 224-byte little-endian `local_jog_update` frames. Local producers send them as Unix datagrams to `jog.sock`, next to `control.sock`. Remote producers send them as binary WebSocket frames on `/v1/jog` over mutual TLS (see [Remote access](https://advancedmetalresearch.com/docs/apis/remote-access-mtls#remote-jog-over-websocket)). Build frames with the SDK (`BuildJogFrame`, `RtJogProducer`) rather than by hand.

Each frame carries the session, grant generation, jog generation, a strictly increasing non-zero `source_sequence`, its own origin and deadline, an axis mask and a finite velocity vector. `deadline_host_ns − origin_host_ns` may not exceed the cell's `max_jog_input_age_ns` (250 ms on LAN). Only the newest valid frame in a burst is applied, and a refused frame never extends the previous input's deadline. Local datagrams get no reply: read refusals from `GET /v1/jog/ingress`.

#### Request

A 224-byte little-endian `local_jog_update` frame, defined in `protocol/control.json`. Reserved bytes are zero.

| Name | Type | Offset (bytes) | Description |
|---|---|---|---|
| `session_id` | `u8[32]` | 0 | The 32 bytes of the session token (its 64 hex characters, decoded). |
| `clock_incarnation` | `u8[16]` | 32 | The 16 bytes of the jog clock incarnation (hex-decoded). On WSS the server maps source time instead. |
| `grant_generation` | `u64` | 48 | Current grant generation. |
| `jog_generation` | `u64` | 56 | Jog generation from `begin_jog`. |
| `source_sequence` | `u64` | 64 | Strictly increasing, non-zero. |
| `origin_host_ns` | `u64` | 72 | Input capture time, host ns (source clock on WSS). |
| `deadline_host_ns` | `u64` | 80 | Absolute deadline; at most the cell's input-age ceiling after the origin. |
| `axis_mask` | `u32` | 88 | Axes in this frame; a subset of the Begin mask. |
| `reserved` | `u32` | 92 | Zero. |
| `velocity` | `f64[16]` | 96 | Per-axis velocity in Describe order, rad/s or m/s. Axes outside the mask must be 0. |

#### Response

No reply on `jog.sock`. On WSS, only refusals are answered, as `{"type":"rejected","seq":N,"reason":"…"}`.

#### Reason codes

| Reason | When |
|---|---|
| [`jog_session_stale`](/docs/reference/error-codes#reasons-jog) | The frame's session, grant generation or jog generation is not the current one (WSS). |
| [`jog_session_or_sequence_stale`](/docs/reference/error-codes#reasons-jog) | No open jog session for this generation, or `source_sequence` did not increase. |
| [`jog_publisher_busy`](/docs/reference/error-codes#reasons-jog) | Another producer is publishing. Replace your unsent input with a fresh sample. |
| [`jog_invalid_frame`](/docs/reference/error-codes#reasons-jog) | The binary frame could not be decoded (WSS). |
| [`jog_stream_idle`](/docs/reference/error-codes#reasons-jog) | No complete WSS frame arrived within the cell's input-age ceiling; rt-control ends the jog and closes. |
| [`control_session_stale`](/docs/reference/error-codes#reasons-authority) | Sent on WSS just before closing, when the grant was stopped or expired. |
| [`jog_clock_unqualified`](/docs/reference/error-codes#reasons-jog) | Remote clock qualification is disabled (the `--remote-jog-*` flags are 0) or the calibration exchange is incomplete. |
| [`jog_clock_invalid_budget_or_exchange`](/docs/reference/error-codes#reasons-jog) | A malformed calibration or Begin message, or an invalid timing budget. |
| [`jog_clock_incarnation_mismatch`](/docs/reference/error-codes#reasons-jog) | The source clock incarnation changed during the WSS session. |
| [`jog_clock_mapping_generation_mismatch`](/docs/reference/error-codes#reasons-jog) | The frame was mapped with an out-of-date clock mapping. |
| [`jog_clock_moved_backwards`](/docs/reference/error-codes#reasons-jog) | A source timestamp went backwards, or input predates the Begin sample. |
| [`jog_clock_arithmetic_range`](/docs/reference/error-codes#reasons-jog) | Clock conversion would overflow. |
| [`jog_clock_uncertainty_exceeded`](/docs/reference/error-codes#reasons-jog) | The calibrated offset interval is wider than `--remote-jog-max-uncertainty-ns`. |
| [`jog_clock_calibration_expired`](/docs/reference/error-codes#reasons-jog) | The last calibration is older than `--remote-jog-calibration-max-age-ns`. Recalibrate. |
| [`jog_clock_exchange_inconsistent`](/docs/reference/error-codes#reasons-jog) | The calibration timestamps are not causally consistent. |
| [`jog_input_too_old`](/docs/reference/error-codes#reasons-jog) | The conservatively mapped input age exceeds the cell ceiling. |
| [`jog_input_entirely_future`](/docs/reference/error-codes#reasons-jog) | The whole input interval lies in the host's future. |
| [`jog_input_deadline_expired`](/docs/reference/error-codes#reasons-jog) | The input deadline has already passed. |
| [`session_principal_mismatch`](/docs/reference/error-codes#reasons-authority) | The WSS connection's TLS principal does not own the session. |
| [`independent_jog_unavailable`](/docs/reference/error-codes#reasons-jog) | No independent jog lane, or the remote listener is shutting down (HTTP 503 before upgrade). |

A frame refused by the native lane is answered on WSS as `jog_native_rejected_<n>`, where `n` is the [native jog reason](https://advancedmetalresearch.com/docs/reference/error-codes#native-jog-reasons). On `jog.sock`, every refusal is only counted in `/v1/jog/ingress`.

**Client libraries.** Go: `JogSession.Update / UpdateAt`, `RemoteJogSession.Update`. C++: `RtJogProducer::update`, `RtJogRemoteProducer::update`.

### `end_jog`

**Endpoint: `POST /v1/control`**

End a jog generation. Motion ramps to a hold. State: `implemented`. Lease: session and generation.

Revokes input for `jog_generation` before waiting for the native receipt, then the core ramps the jog to a stop. The reply's `handle` echoes the generation. `end_jog` does not release authority: call `release` separately. Ending during the expiry ramp can return a native `closed` refusal.

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `schema` | `string` | Yes | `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | `end_jog`. |
| `session` | `string` | Yes | Session token from the current `Grant`. |
| `generation` | `uint64` | Yes | Current grant generation. |
| `jog_generation` | `uint64` | Yes | The current non-zero jog generation from `begin_jog`. |
| `request_id` | `string` | No | Idempotence key, 1..64 printable ASCII bytes. See [retries](https://advancedmetalresearch.com/docs/apis/rt-control-http#idempotent-retries). |

The contract's semantically required fields: `schema`, `operation`, `session`, `generation`, `jog_generation`.

#### Response

`handle` holds the result. `handle` echoes the ended jog generation.

#### Example

```http
POST /v1/control HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "schema": "rosie.rt-control.request.v1",
  "operation": "end_jog",
  "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
  "generation": 3,
  "jog_generation": 4
}
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "end_jog",
  "handle": 4
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`jog_session_stale`](/docs/reference/error-codes#reasons-jog) | No jog session is open, or `jog_generation` is not the current one. |
| [`independent_jog_unavailable`](/docs/reference/error-codes#reasons-jog) | The native client offers no independent jog lane. |

Also the common [envelope](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-envelope) and [authority](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-authority) reasons.

Native jog refusals return `RTCore rejected jog (reason N)` with `native_jog_result`.

**Client libraries.** Go: `JogSession.End`, `Client.EndJog`. C++: `RtJogProducer::end`, `end_jog()`.

### `jog`

**Endpoint: `POST /v1/control`**

Legacy JSON velocity jog. Test only. State: `test_only`. Lease: session and generation.

Labelled `test_only`: it is kept for test fixtures. Applications use `begin_jog`, `update_jog` and `end_jog` instead.

The contract describes it as: “Legacy velocity jog is test-only for oracle ports and fixtures. Consumers use begin_jog/update_jog/end_jog; update_jog uses jog.sock or WSS /v1/jog.”

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `schema` | `string` | Yes | `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | `jog`. |
| `session` | `string` | Yes | Session token from the current `Grant`. |
| `generation` | `uint64` | Yes | Current grant generation. |
| `axis_mask` | `uint32` | Yes | Axes to jog. |
| `velocity` | `[]float64` | Yes | Finite velocities in Describe axis order, rad/s or m/s. |
| `timeout_ms` | `uint32` | Yes | Input lifetime, 1..250 ms. |
| `request_id` | `string` | No | Idempotence key, 1..64 printable ASCII bytes. See [retries](https://advancedmetalresearch.com/docs/apis/rt-control-http#idempotent-retries). |

The contract's semantically required fields: `schema`, `operation`, `session`, `generation`, `axis_mask`, `velocity`, `timeout_ms`.

#### Response

`sequence` holds the native command sequence. There is no `data`.

#### Example

```http
POST /v1/control HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "schema": "rosie.rt-control.request.v1",
  "operation": "jog",
  "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
  "generation": 3,
  "axis_mask": 1,
  "velocity": [
    0.01,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0
  ],
  "timeout_ms": 100
}
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "jog",
  "sequence": 26
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`mode_conflict`](/docs/reference/error-codes#reasons-native) | A program is executing. |

Also the common [envelope](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-envelope), [authority](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-authority) and [native](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-native) reasons.

**Client libraries.** Go: `Client.Jog`. C++: `jog()`.

## Trajectories

A low-level point-list path, used by tests and short moves. Programs from the planner use [Programs](https://advancedmetalresearch.com/docs/apis/rt-control-http#programs).

### `prepare_trajectory`

**Endpoint: `POST /v1/control`**

Upload a point list and get an inert handle. State: `implemented`. Lease: session and generation.

Stages 2..250000 `Point` records for `axis_mask` and returns a `handle` in the `prepared` state. Nothing moves until `start_trajectory`. The first point's `time_ns` is 0 and times strictly increase; positions and optional velocities are in Describe axis order and logical rad or m units. Native limits and continuity are enforced at Prepare, and again at Start.

For a large body, put `schema`, `operation`, `session` and a non-zero `generation` **before** `points`, within the first 16384 bytes, so rt-control can check the fence before reading the rest. Do not use a key-sorting JSON encoder. One JSON upload is admitted at a time across all listeners, and the whole body is capped at 222516384 bytes. This is the low-level path for tests and short moves; production programs use [`prepare_program`](#prepare-program).

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `schema` | `string` | Yes | `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | `prepare_trajectory`. |
| `session` | `string` | Yes | Session token from the current `Grant`. |
| `generation` | `uint64` | Yes | Current grant generation. |
| `axis_mask` | `uint32` | Yes | Axes the trajectory commands. |
| `points` | [`Point`](#type-point)`[]` | Yes | 2..250000 points. See `Point` for units. |
| `identity` | [`Identity`](#type-identity) | No | Immutable identity checked again at Start. Default: all zero. |
| `request_id` | `string` | No | Idempotence key, 1..64 printable ASCII bytes. See [retries](https://advancedmetalresearch.com/docs/apis/rt-control-http#idempotent-retries). |

The contract's semantically required fields: `schema`, `operation`, `session`, `generation`, `axis_mask`, `points`.

#### Response

`handle` is the new handle and `data` is its [`HandleRecord`](#type-handlerecord).

| Name | Type | Required | Description |
|---|---|---|---|
| `handle` | `uint64` | Yes | — |
| `state` | `string` | Yes | One of the handle_states labels; terminal states never regain permission. |
| `generation` | `uint64` | Yes | Application grant generation that prepared this handle. |
| `execution_generation` | `uint64` | Yes | Count of acknowledged native Starts when this handle started; zero before Start. |
| `native_sequence` | `uint64` | Yes | Correlated native Start sequence; zero before Start. |
| `identity` | [`Identity`](#type-identity) | No | Immutable raw trajectory identity supplied at Prepare and checked at Start; echoed unchanged through terminal observation. |

#### Example

```http
POST /v1/control HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "schema": "rosie.rt-control.request.v1",
  "operation": "prepare_trajectory",
  "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
  "generation": 3,
  "axis_mask": 1,
  "points": [
    {
      "time_ns": 0,
      "position": [
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0
      ]
    },
    {
      "time_ns": 1000000000,
      "position": [
        0.05,
        0,
        0,
        0,
        0,
        0,
        0,
        0,
        0
      ]
    }
  ]
}
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "prepare_trajectory",
  "handle": 7,
  "data": {
    "handle": 7,
    "state": "prepared",
    "generation": 3,
    "execution_generation": 0,
    "native_sequence": 0,
    "identity": {}
  }
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`busy`](/docs/reference/error-codes#reasons-program) | Another JSON upload holds the preparation slot, or the lifecycle lock is busy. |

Also the common [envelope](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-envelope), [authority](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-authority), [native](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-native) and [cell I/O](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-io) reasons.

A native `invalid_trajectory` refusal (reason 6) retires every prepared handle and the prepared program.

**Client libraries.** Go: `Client.PrepareTrajectory`. C++: `prepare_trajectory()`.

### `start_trajectory`

**Endpoint: `POST /v1/control`**

Start a prepared handle. State: `implemented`. Lease: session and generation.

> [!WARNING] This call can energise the drives or move the robot. Keep the hardware E-stop within reach. See the [safety model](https://advancedmetalresearch.com/docs/get-started/safety-model).

Starts `handle` if it is still `prepared` and belongs to this grant. The full [motion start gate](https://advancedmetalresearch.com/docs/concepts/real-time-core#the-motion-start-gate) is evaluated in the core: lease, Arm, bus, faults, configuration, Home, limits, CSP mode, enable and a first point continuous with the held position.

A definite `not_ready` or `mode_conflict` refusal restores the prepared plan so you can retry. Any uncertain outcome retires the handle: prepare again rather than replay. Completion is observed in Status and events, never inferred from the receipt.

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `schema` | `string` | Yes | `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | `start_trajectory`. |
| `session` | `string` | Yes | Session token from the current `Grant`. |
| `generation` | `uint64` | Yes | Current grant generation. |
| `handle` | `uint64` | Yes | Non-zero handle from `prepare_trajectory`. |
| `identity` | [`Identity`](#type-identity) | No | Must equal the identity supplied at Prepare (all zero if none was). |
| `request_id` | `string` | No | Idempotence key, 1..64 printable ASCII bytes. See [retries](https://advancedmetalresearch.com/docs/apis/rt-control-http#idempotent-retries). |

The contract's semantically required fields: `schema`, `operation`, `session`, `generation`, `handle`.

#### Response

`sequence` holds the native command sequence and `data` is a [`HandleRecord`](#type-handlerecord).

| Name | Type | Required | Description |
|---|---|---|---|
| `handle` | `uint64` | Yes | — |
| `state` | `string` | Yes | One of the handle_states labels; terminal states never regain permission. |
| `generation` | `uint64` | Yes | Application grant generation that prepared this handle. |
| `execution_generation` | `uint64` | Yes | Count of acknowledged native Starts when this handle started; zero before Start. |
| `native_sequence` | `uint64` | Yes | Correlated native Start sequence; zero before Start. |
| `identity` | [`Identity`](#type-identity) | No | Immutable raw trajectory identity supplied at Prepare and checked at Start; echoed unchanged through terminal observation. |

#### Example

```http
POST /v1/control HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "schema": "rosie.rt-control.request.v1",
  "operation": "start_trajectory",
  "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
  "generation": 3,
  "handle": 7
}
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "start_trajectory",
  "sequence": 27,
  "data": {
    "handle": 7,
    "state": "started",
    "generation": 3,
    "execution_generation": 1,
    "native_sequence": 27,
    "identity": {}
  }
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`unknown_handle`](/docs/reference/error-codes#reasons-handles) | No such handle in this adapter incarnation. |
| [`trajectory_identity_mismatch`](/docs/reference/error-codes#reasons-handles) | `identity` differs from the one given at Prepare. |
| [`handle_started`](/docs/reference/error-codes#reasons-handles) | The handle has already started. |
| [`handle_consumed`](/docs/reference/error-codes#reasons-handles) | The handle completed, or a later execution replaced it. |
| [`handle_discarded`](/docs/reference/error-codes#reasons-handles) | The handle was discarded. |
| [`handle_superseded`](/docs/reference/error-codes#reasons-handles) | A newer preparation replaced it. |
| [`handle_retired`](/docs/reference/error-codes#reasons-handles) | Stop, grant loss or an uncertain outcome retired it. Prepare again. |
| [`mode_conflict`](/docs/reference/error-codes#reasons-native) | A program is executing, or the core reports another active mode. The handle is kept. |
| [`not_ready`](/docs/reference/error-codes#reasons-native) | The start gate failed. The handle is kept; fix readiness and retry. |
| [`fence`](/docs/reference/error-codes#reasons-authority) | Authority was revoked or the request cancelled while starting. The handle is retired. |

Also the common [envelope](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-envelope), [authority](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-authority) and [native](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-native) reasons.

**Client libraries.** Go: `Client.StartTrajectory`. C++: `start_trajectory()`.

### `discard_trajectory`

**Endpoint: `POST /v1/control`**

Discard a prepared handle and free its native storage. State: `implemented`. Lease: session and generation.

Discards a `prepared` handle. Discarding an already discarded handle is a no-op. A started handle cannot be discarded: you get `handle_active`; use Halt or Stop instead.

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `schema` | `string` | Yes | `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | `discard_trajectory`. |
| `session` | `string` | Yes | Session token from the current `Grant`. |
| `generation` | `uint64` | Yes | Current grant generation. |
| `handle` | `uint64` | Yes | Non-zero handle to discard. |
| `request_id` | `string` | No | Idempotence key, 1..64 printable ASCII bytes. See [retries](https://advancedmetalresearch.com/docs/apis/rt-control-http#idempotent-retries). |

The contract's semantically required fields: `schema`, `operation`, `session`, `generation`, `handle`.

#### Response

`sequence` holds the native command sequence. There is no `data`.

#### Example

```http
POST /v1/control HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "schema": "rosie.rt-control.request.v1",
  "operation": "discard_trajectory",
  "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
  "generation": 3,
  "handle": 7
}
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "discard_trajectory",
  "sequence": 28
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`unknown_handle`](/docs/reference/error-codes#reasons-handles) | No such handle in this adapter incarnation. |
| [`handle_active`](/docs/reference/error-codes#reasons-handles) | The handle has started. Discard is refused; end it with Halt or Stop. |

Also the common [envelope](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-envelope), [authority](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-authority) and [native](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-native) reasons.

When native cleanup fails, the error reads `discard preparation <handle>: <cause>`.

**Client libraries.** Go: `Client.DiscardTrajectory`. C++: `discard_trajectory()`.

## Programs

The production path for planned weld programs: upload a verified `.rdt`, then start it by identity.

### `prepare_program`

**Endpoint: `POST /v1/program`**

Upload a dense `.rdt` program. It stays inert until `start_program`. State: `implemented`. Lease: session and generation headers.

The body is the raw `.rdt` bytes (see [the .rdt format](https://advancedmetalresearch.com/docs/reference/rdt-format)); authority travels in headers. rt-control decodes and validates the whole file (format, digests, time grid, continuity, native position, velocity and declared acceleration limits, axis mapping and process markers) before staging it. It replaces any previously prepared program.

The reply's `Program` carries the full `Identity`. Echo it unchanged to `start_program`. `prepare_program` has no `request_id` deduplication: never replay an uncertain upload; inspect Describe or Status, or Stop, first. One binary upload is admitted at a time, and the body is capped at 39298580 bytes.

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `X-Control-Session` (header) | `string` | Yes | Current session token. |
| `X-Control-Generation` (header) | `uint64` | Yes | Current grant generation, as decimal text. |
| `Content-Type` (header) | `string` | No | `application/octet-stream` (sent by the SDKs). |

Body: Binary `.rdt` blob.

Required: `X-Control-Session`, `X-Control-Generation`.

#### Response

`data` is a [`Program`](#type-program).

| Name | Type | Required | Description |
|---|---|---|---|
| `process_markers` | [`ProcessMarker[]`](#type-processmarker) | Yes | Process-I/O markers in the program. |
| `identity` | [`Identity`](#type-identity) | Yes | Echo this unchanged to `start_program`. |
| `requires_process_io` | `bool` | Yes | True if the program has output markers. |
| `segments` | `int` | Yes | Segment count. |
| `samples` | `int` | Yes | Source sample count. |
| `normalised_samples` | `int` | Yes | Exact execution sample count after coincident segment endpoints are shared. |
| `axis_mask` | `uint32` | Yes | Axes the program commands. |

#### Example

```http
POST /v1/program HTTP/1.1
Host: localhost
Content-Type: application/octet-stream
X-Control-Session: 8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e
X-Control-Generation: 3
Content-Length: 1530412

<.rdt bytes>
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "prepare_program",
  "data": {
    "process_markers": [],
    "identity": {
      "plan_id": "weld-demo:1a2b3c4d5e6f",
      "program_id": "weld-demo",
      "program_digest": "sha256:…",
      "trajectory_digest": "sha256:…",
      "source_digest": "sha256:…",
      "normalised_digest": "sha256:…",
      "manifest_revision": 1,
      "plan_revision": 1
    },
    "requires_process_io": false,
    "segments": 3,
    "samples": 10002,
    "normalised_samples": 10000,
    "axis_mask": 511
  }
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`invalid_control_generation`](/docs/reference/error-codes#reasons-envelope) | `X-Control-Generation` is missing or not a decimal uint64. |
| [`body_too_large`](/docs/reference/error-codes#reasons-envelope) | The body exceeds 39298580 bytes. HTTP 413. |
| [`session_principal_mismatch`](/docs/reference/error-codes#reasons-authority) | The session belongs to another principal. |
| [`busy`](/docs/reference/error-codes#reasons-program) | Another `.rdt` upload is in progress, the lifecycle lock is busy, or the core has no free plan slot (then `native_result.Reason` is 5, capacity). |
| [`program_already_executing`](/docs/reference/error-codes#reasons-program) | A program is executing. Stop it or wait. |
| [`manifest_revision_mismatch`](/docs/reference/error-codes#reasons-program) | The program's `manifest_revision` is not the adapter's pair revision. |
| [`process_io_executor_not_qualified`](/docs/reference/error-codes#reasons-program) | The program sets the torch flag. Always refused. |
| [`program_identity_missing`](/docs/reference/error-codes#reasons-program) | The `.rdt` header lacks a required identity field. |
| [`dense_axis_map_requires_nine_ids`](/docs/reference/error-codes#reasons-program) | The cell describes more rotary axes than the nine-column format carries. |
| [`invalid_dense_axis_map`](/docs/reference/error-codes#reasons-program) | The cell's rotary axes cannot be mapped onto the dense columns. |
| [`unmapped_dense_axis`](/docs/reference/error-codes#reasons-program) | A commanded dense column has no native axis. The error reads `unmapped_dense_axis: <axis>`. |
| [`native_limit_exceeded`](/docs/reference/error-codes#reasons-program) | A sample exceeds a native position, velocity or declared acceleration limit. `data.limit_violation` names it. |
| [`native_segment_rate_exceeded`](/docs/reference/error-codes#reasons-program) | An interpolated segment exceeds a velocity limit. `data.limit_violation` names it. |
| [`outside_limits_outward`](/docs/reference/error-codes#reasons-native) | A recovery segment would bow further outside the limits. |
| [`segment_boundary_discontinuous`](/docs/reference/error-codes#reasons-program) | Adjacent moving segments do not meet. |
| [`io_not_configured`](/docs/reference/error-codes#reasons-io) | The program has process markers but no cell I/O is configured. |
| [`io_torch_unqualified`](/docs/reference/error-codes#reasons-io) | A marker names a torch-class output. |
| [`io_marker_invalid`](/docs/reference/error-codes#reasons-io) | A marker names an unknown output or does not land on a sample. |
| [`axis_count_mismatch`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`blob_length_mismatch`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`block_layout_invalid`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`block_sha256_mismatch`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`dense_schema_mismatch`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`duration_mismatch`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`header_json_invalid`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`header_truncated`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`kind_invalid`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`limits_invalid`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`nonfinite_sample`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`q_step_exceeded`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`qd_limit_exceeded`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`reserved_flags_set`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`sample_count_overflow`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`sample_encoding_mismatch`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`segment_index_out_of_order`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`segment_too_short`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`segments_empty`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`time_grid_invalid`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`torch_outside_weld`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`total_sample_count_mismatch`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`trajectory_digest_mismatch`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`boundary_q_discontinuity`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |
| [`boundary_qd_nonzero`](/docs/reference/error-codes#reasons-dense) | The `.rdt` file failed format validation. |

Also the common [authority](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-authority) and [native](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-native) reasons.

The dense format reasons are defined with the [.rdt format](https://advancedmetalresearch.com/docs/reference/rdt-format). Every one of them is a catalogue label.

**Client libraries.** Go: `Client.PrepareProgram`. C++: `prepare_program()`.

### `start_program`

**Endpoint: `POST /v1/control`**

Start the prepared program by its exact identity. State: `implemented`. Lease: session and generation.

> [!WARNING] This call can energise the drives or move the robot. Keep the hardware E-stop within reach. See the [safety model](https://advancedmetalresearch.com/docs/get-started/safety-model).

`identity` must equal the prepared program's `Identity` field for field. The same start gate as `start_trajectory` applies. Record `sequence`; completion appears in Status as `execution.state: completed` for the next `execution.generation`, or `faulted` with `native_execution_failed`.

To retry a lost receipt, resend the same request with the same `request_id`. A new ID is a new command.

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `schema` | `string` | Yes | `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | `start_program`. |
| `session` | `string` | Yes | Session token from the current `Grant`. |
| `generation` | `uint64` | Yes | Current grant generation. |
| `identity` | [`Identity`](#type-identity) | Yes | The complete `Identity` returned by `prepare_program`. |
| `request_id` | `string` | No | Idempotence key, 1..64 printable ASCII bytes. See [retries](https://advancedmetalresearch.com/docs/apis/rt-control-http#idempotent-retries). |

The contract's semantically required fields: `schema`, `operation`, `session`, `generation`, `identity`.

#### Response

`sequence` holds the native command sequence. There is no `data`.

#### Example

```http
POST /v1/control HTTP/1.1
Host: localhost
Content-Type: application/json

{
  "schema": "rosie.rt-control.request.v1",
  "operation": "start_program",
  "session": "8c2f4e1a9b7d3c5e0f6a2b4c8d1e3f5a7b9c0d2e4f6a8b1c3d5e7f9a0b2c4d6e",
  "generation": 3,
  "request_id": "9b2e41c07d5f4a3e8c6b1d0f2a4e6c8b",
  "identity": {
    "plan_id": "weld-demo:1a2b3c4d5e6f",
    "program_id": "weld-demo",
    "program_digest": "sha256:…",
    "trajectory_digest": "sha256:…",
    "source_digest": "sha256:…",
    "normalised_digest": "sha256:…",
    "manifest_revision": 1,
    "plan_revision": 1
  }
}
```

200 OK:

```json
{
  "schema": "rosie.rt-control.response.v1",
  "operation": "start_program",
  "sequence": 29
}
```

#### Reason codes

| Reason | When |
|---|---|
| [`program_identity_mismatch`](/docs/reference/error-codes#reasons-program) | No program is prepared, or `identity` differs from it. |
| [`program_already_executing`](/docs/reference/error-codes#reasons-program) | A program is already executing. |
| [`process_io_executor_not_qualified`](/docs/reference/error-codes#reasons-program) | The program requires torch output. Always refused. |
| [`unknown_handle`](/docs/reference/error-codes#reasons-handles) | No such handle in this adapter incarnation. |
| [`handle_started`](/docs/reference/error-codes#reasons-handles) | The handle has already started. |
| [`handle_consumed`](/docs/reference/error-codes#reasons-handles) | The handle completed, or a later execution replaced it. |
| [`handle_discarded`](/docs/reference/error-codes#reasons-handles) | The handle was discarded. |
| [`handle_superseded`](/docs/reference/error-codes#reasons-handles) | A newer preparation replaced it. |
| [`handle_retired`](/docs/reference/error-codes#reasons-handles) | Stop, grant loss or an uncertain outcome retired it. Prepare again. |
| [`mode_conflict`](/docs/reference/error-codes#reasons-native) | A program is executing, or the core reports another active mode. The handle is kept. |
| [`not_ready`](/docs/reference/error-codes#reasons-native) | The start gate failed. The handle is kept; fix readiness and retry. |
| [`fence`](/docs/reference/error-codes#reasons-authority) | Authority was revoked or the request cancelled while starting. The handle is retired. |

Also the common [envelope](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-envelope), [authority](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-authority), [native](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-native) and [cell I/O](https://advancedmetalresearch.com/docs/apis/rt-control-http#common-io) reasons.

**Client libraries.** Go: `Client.StartProgram`. C++: `start_program()`.

## Reserved operations

Named in the contract so clients can detect them, but they perform no operation.

### `abort`

**Endpoint: `POST /v1/control`**

Reserved. Not implemented. State: `unimplemented`. Lease: none.

Advertised as `unimplemented`, with transport `none`. Sending `operation: "abort"` performs nothing and returns `capability_unimplemented` with the `CapabilityInfo` in `data`. Use `stop` or `halt`.

#### Request

The standard envelope with `operation` set to the operation name. No other fields.

#### Response

Always refused; see below.

#### Reason codes

| Reason | When |
|---|---|
| [`capability_unimplemented`](/docs/reference/error-codes#reasons-envelope) | Always. |

### `readiness`

**Endpoint: `POST /v1/control`**

Reserved. Not implemented. State: `unimplemented`. Lease: none.

Advertised as `unimplemented`. Returns `capability_unimplemented`. Read per-axis `readiness` from `GET /v1/status` instead.

#### Request

The standard envelope with `operation` set to the operation name. No other fields.

#### Response

Always refused; see below.

#### Reason codes

| Reason | When |
|---|---|
| [`capability_unimplemented`](/docs/reference/error-codes#reasons-envelope) | Always. |

## Types

Every type in the contract, generated from `types` in the schema. `Required` is the codec designation; which fields an operation needs is in its request table. JSON integers are exact uint64 values: parse them without converting to floating point. Fields of native receipts (`CommandResult`, `JogObservation`, `GrantObservation`) keep their case-sensitive native names.

### Authority

#### Fence

| Name | Type | Required | Description |
|---|---|---|---|
| `session` | `string` | Yes | Current unguessable session token; acquire omits the fence. Tokens issued by the adapter are 64 lowercase hexadecimal characters. |
| `generation` | `uint64` | Yes | Nonzero uint64 grant generation. Motion requires exact equality; renew and stop accept an older nonzero generation not above the current one for the same session. |

#### Binding

| Name | Type | Required | Description |
|---|---|---|---|
| `pair_id` | `string` | Yes | Exact configured nonempty pair ID. |
| `revision` | `uint64` | Yes | Nonzero uint64 equal to configured pair revision. |
| `configuration_sha256` | `string` | Yes | Exact configured native SHA-256 label. |
| `machine_sha256` | `string` | No | Machine-semantics digest; omitted by clients that only know the whole-artifact digest. |

#### Grant

| Name | Type | Required | Description |
|---|---|---|---|
| `stopping` | `bool` | Yes | True means renewal keeps the lease alive while Stop is in flight; it grants no movement permission. |
| `deadline_host_ns` | `uint64` | Yes | Uint64 absolute lease deadline in host monotonic nanoseconds. |
| `session` | `string` | Yes | 64-character unpredictable lowercase hexadecimal token while owned; empty after release/expiry. |
| `generation` | `uint64` | Yes | Monotonically increasing uint64 application generation, fenced by Stop and acquisition. |
| `controller` | `string` | Yes | The `controller` label sent to `acquire`. |
| `binding` | [`Binding`](#type-binding) | Yes | The binding, completed with the adapter's digests. |
| `lease_ms` | `int` | Yes | Effective negotiated lease in whole milliseconds: min(client requested lease, compiled cell ceiling); zero/omitted request retains 500 ms capped by the cell. Renew preserves this value. Longer leases increase worst-case unattended-stop delay after link loss. Unverified absolute bound: 10000 ms. |

#### GrantObservation

| Name | Type | Required | Description |
|---|---|---|---|
| `Ticket` | `uint64` | Yes | — |
| `ConnectionId` | `uint64` | Yes | — |
| `NativeGeneration` | `uint64` | Yes | — |
| `AppGrantGeneration` | `uint64` | Yes | — |
| `CapabilityId` | `uint8[16]` | Yes | — |
| `DeadlineHostNs` | `uint64` | Yes | — |
| `NowHostNs` | `uint64` | Yes | — |
| `ConfigurationEpoch` | `uint64` | Yes | — |
| `HomeEpoch` | `uint64` | Yes | — |
| `StopAckId` | `uint64` | Yes | — |
| `GrantReason` | `uint32` | Yes | — |
| `GrantActive` | `uint32` | Yes | — |
| `AxisMask` | `uint32` | Yes | — |
| `Reserved` | `uint32` | Yes | — |
| `Reserved1` | `uint64` | Yes | — |
| `Reserved2` | `uint64` | Yes | — |
| `Reserved3` | `uint64` | Yes | — |

### Requests and responses

#### Request

| Name | Type | Required | Description |
|---|---|---|---|
| `request_id` | `string` | No | Optional, non-null string of 1..64 printable ASCII bytes (0x20..0x7e). |
| `schema` | `string` | Yes | Exactly `rosie.rt-control.request.v1`. |
| `operation` | `string` | Yes | Exact capability name; only POST /v1/control operations dispatch here. Unknown names are rejected. |
| *(embedded)* | [`Fence`](#type-fence) | Yes | All fields of `Fence` appear at this level of the object. |
| `label` | `string` | No | mark_telemetry requires 1..128 UTF-8 bytes; free-text dump label. |
| `controller` | `string` | No | Acquire requires 1..63 bytes; opaque controller name. |
| `binding` | [`Binding`](#type-binding) | No | Acquire requires exact equality with the configured Binding. |
| `axis_mask` | `uint32` | No | Uint32 configured axis group mask; interpretation is native operation-specific; reset_fault defaults zero to all axes. |
| `velocity` | `float64[]` | No | Finite logical velocities in described axis order for legacy jog; native vector and velocity-limit validation applies. |
| `timeout_ms` | `uint32` | No | Legacy jog requires an integer 1..250 milliseconds. |
| `handle` | `uint64` | No | Nonzero opaque uint64 handle for start/discard, owned by the current daemon incarnation and grant. |
| `points` | [`Point[]`](#type-point) | No | 2..250000 Point records; declared axis order, native limits and continuity apply. Large bodies require the envelope before this array. |
| `identity` | [`Identity`](#type-identity) | No | Exact prepared program Identity for start_program. |
| `jog_generation` | `uint64` | No | EndJog requires the exact current nonzero independent jog generation. |
| `source_sequence` | `uint64` | No | Accepted envelope field retained for compatibility; current /v1/control dispatch does not consume it. Independent updates use the binary lane. |
| `source_origin_host_ns` | `uint64` | No | BeginJog origin in the negotiated host monotonic clock, uint64 nanoseconds; native freshness validation applies. |
| `deadline_host_ns` | `uint64` | No | BeginJog producer deadline in host monotonic nanoseconds; after origin and never extended by admission or renewal. |
| `clock_incarnation` | `string` | No | BeginJog requires exact equality with GET /v1/jog/clock incarnation. |
| `requested_lease_ms` | `int` | No | Acquire requested lease in whole ms, 1..10000; omitted or zero retains the LAN default capped by the cell. Adapter returns min(request, cell ceiling) in Grant.lease_ms. Clients require at least three measured round trips plus renewal cadence. |

#### Response

| Name | Type | Required | Description |
|---|---|---|---|
| `native_jog_result` | [`JogObservation`](#type-jogobservation) | No | Native jog observation on a jog refusal. |
| `native_result` | [`CommandResult`](#type-commandresult) | No | Native receipt on a native refusal. |
| `schema` | `string` | Yes | `rosie.rt-control.response.v1`. |
| `operation` | `string` | Yes | The operation this reply answers. |
| `sequence` | `uint64` | No | Native command sequence, when returned. |
| `handle` | `uint64` | No | Handle or jog generation, when returned. |
| `data` | `any` | No | The operation's result type. |
| `error` | `string` | No | Reason code or diagnostic, on failure only. |

#### RawResponse

| Name | Type | Required | Description |
|---|---|---|---|
| `native_jog_result` | [`JogObservation`](#type-jogobservation) | No | Native jog observation on a jog refusal. |
| `native_result` | [`CommandResult`](#type-commandresult) | No | Native receipt on a native refusal. |
| `schema` | `string` | Yes | `rosie.rt-control.response.v1`. |
| `operation` | `string` | Yes | The operation this reply answers. |
| `sequence` | `uint64` | No | Native command sequence, when returned. |
| `handle` | `uint64` | No | Handle or jog generation, when returned. |
| `data` | `object` | No | Undecoded result JSON. |
| `error` | `string` | No | Reason code or diagnostic, on failure only. |

#### CommandResult

| Name | Type | Required | Description |
|---|---|---|---|
| `Sequence` | `uint64` | Yes | Native command sequence. |
| `Handle` | `uint64` | Yes | Native handle, when relevant. |
| `Generation` | `uint64` | Yes | Native control generation. |
| `Operation` | `uint32` | Yes | Native message code (`MSG_CMD_*`). |
| `Result` | `uint32` | Yes | 0 accepted, 1 rejected, 2 prepared. |
| `Reason` | `uint32` | Yes | Native command reason; see [native command reasons](https://advancedmetalresearch.com/docs/reference/error-codes#native-command-reasons). |
| `AxisMask` | `uint32` | Yes | Axes the result applies to. |

#### CapabilityInfo

| Name | Type | Required | Description |
|---|---|---|---|
| `name` | `string` | Yes | Canonical capability name. |
| `state` | `string` | Yes | implemented, interim, test_only or unimplemented. test_only operations are retained for oracle ports and fixtures; consumers use implemented operations. |
| `transport` | `string` | Yes | Actual ingress; unimplemented has no transport. |

### Motion

#### Point

| Name | Type | Required | Description |
|---|---|---|---|
| `time_ns` | `int64` | Yes | Signed int64 nanoseconds from plan start; first point zero, later timestamps strictly increasing. |
| `position` | `float64[]` | Yes | Finite logical rad/m positions in described axis order, within native configured bounds. |
| `velocity` | `float64[]` | No | Optional finite logical rad/s or m/s velocities in described axis order; nil/empty omits feedforward. |
| `io_mask` | `uint32` | No | Eight-bit mask in configured output order; set bits require configured non-torch outputs. |
| `io_values` | `uint32` | No | Eight-bit output intents; every set bit must also be set in io_mask. Physical readback is independent. |

#### Identity

| Name | Type | Required | Description |
|---|---|---|---|
| `plan_id` | `string` | Yes | Plan identifier from the `.rdt` header. |
| `program_id` | `string` | Yes | Program identifier from the `.rdt` header. |
| `program_digest` | `string` | Yes | Program digest from the `.rdt` header. |
| `trajectory_digest` | `string` | Yes | Content digest of the dense trajectory. |
| `source_digest` | `string` | Yes | Verified source trajectory digest; start_program must echo the prepared value exactly. |
| `normalised_digest` | `string` | Yes | Canonical segment-record digest including local clocks and process bits; start_program must echo the prepared value exactly. |
| `manifest_revision` | `uint64` | Yes | Must equal the pair revision rt-control was started with. |
| `plan_revision` | `uint64` | Yes | Plan revision from the `.rdt` header. |

#### HandleRecord

| Name | Type | Required | Description |
|---|---|---|---|
| `handle` | `uint64` | Yes | — |
| `state` | `string` | Yes | One of the handle_states labels; terminal states never regain permission. |
| `generation` | `uint64` | Yes | Application grant generation that prepared this handle. |
| `execution_generation` | `uint64` | Yes | Count of acknowledged native Starts when this handle started; zero before Start. |
| `native_sequence` | `uint64` | Yes | Correlated native Start sequence; zero before Start. |
| `identity` | [`Identity`](#type-identity) | No | Immutable raw trajectory identity supplied at Prepare and checked at Start; echoed unchanged through terminal observation. |

#### Execution

| Name | Type | Required | Description |
|---|---|---|---|
| `handles` | [`HandleRecord[]`](#type-handlerecord) | Yes | Every handle this adapter knows, with its state. |
| `generation` | `uint64` | Yes | Count of acknowledged native Starts; distinct from native control generation. |
| `state` | `string` | Yes | Application execution state: empty before observation, prepared, executing, completed, faulted, cancelled, discarded or released. |
| `identity` | [`Identity`](#type-identity) | Yes | Identity of the prepared or running program. |
| `segment` | `int` | Yes | Current zero-based program segment when a program is observed. |
| `native_sequence` | `uint64` | Yes | Native sequence of the program Start. |
| `error` | `string` | No | Failure detail, for example `native_execution_failed`. |

#### MotionState

| Name | Type | Required | Description |
|---|---|---|---|
| `mode` | `uint32` | Yes | 0 idle, 2 trajectory, 3 jog. |
| `state` | `uint32` | Yes | Native execution state: 0 idle, 1 accepted, 2 queued, 3 executing, 4 completed, 5 aborted, 6 faulted, 7 underrun. |
| `trajectory_id` | `uint64` | Yes | Active trajectory handle. |
| `point_index` | `uint32` | Yes | Current point index. |
| `queue_depth` | `uint32` | Yes | — |
| `last_event` | `uint32` | Yes | — |
| `done` | `bool` | Yes | True when the active execution has finished. |
| `command_sequence` | `uint64` | Yes | Native command sequence of the active or last motion. |
| `time_ns` | `uint64` | Yes | Native observation time, ns. |

#### Program

| Name | Type | Required | Description |
|---|---|---|---|
| `process_markers` | [`ProcessMarker[]`](#type-processmarker) | Yes | Process-I/O markers in the program. |
| `identity` | [`Identity`](#type-identity) | Yes | Echo this unchanged to `start_program`. |
| `requires_process_io` | `bool` | Yes | True if the program has output markers. |
| `segments` | `int` | Yes | Segment count. |
| `samples` | `int` | Yes | Source sample count. |
| `normalised_samples` | `int` | Yes | Exact execution sample count after coincident segment endpoints are shared. |
| `axis_mask` | `uint32` | Yes | Axes the program commands. |

#### ProcessMarker

| Name | Type | Required | Description |
|---|---|---|---|
| `time_ns` | `int64` | Yes | Marker time from program start, ns. |
| `segment` | `int` | Yes | Segment index. |
| `sample` | `int` | Yes | Sample index. |
| `action` | `string` | Yes | Output name. |
| `value` | `bool` | Yes | Requested output value. |

#### ProgramLimitData

| Name | Type | Required | Description |
|---|---|---|---|
| `limit_violation` | [`ProgramLimitViolation`](#type-programlimitviolation) | Yes | Logical joint-limit diagnostic for native_limit_exceeded or native_segment_rate_exceeded; supplements the unchanged refusal reason and is not a native command receipt. |

#### ProgramLimitViolation

| Name | Type | Required | Description |
|---|---|---|---|
| `kind` | `string` | Yes | Diagnostic quantity: position or velocity. |
| `segment` | `int` | Yes | Zero-based source segment index, matching the refusal detail. |
| `sample` | `int` | Yes | Zero-based source sample index, matching the refusal detail. |
| `axis` | `string` | Yes | Canonical joint identifier reported by admission, for example J6. |
| `value` | `float64` | Yes | Finite logical joint position or peak velocity in unit; command-count direction and Home offset have been removed. |
| `limit` | `float64` | Yes | Finite logical admission bound in unit; invalid numeric details are omitted as a whole without discarding the refusal. |
| `unit` | `string` | Yes | rad for position; rad/s for velocity. |

#### PlanCursor

| Name | Type | Required | Description |
|---|---|---|---|
| `time_ns` | `uint64` | Yes | — |
| `active_handle` | `uint64` | Yes | — |
| `sample_index` | `uint32` | Yes | — |
| `native_clock_ns` | `uint64` | Yes | — |

#### BufferHealth

| Name | Type | Required | Description |
|---|---|---|---|
| `time_ns` | `uint64` | Yes | — |
| `slots_free` | `uint32` | Yes | — |
| `staging_in_progress` | `bool` | Yes | — |
| `ring_drops` | `uint64` | Yes | — |
| `native_slot_occupancy` | `object` | Yes | — |

### Jog

#### LocalJogClock

| Name | Type | Required | Description |
|---|---|---|---|
| `domain` | `string` | Yes | `CLOCK_MONOTONIC`. |
| `incarnation` | `string` | Yes | Clock incarnation; pass it to `begin_jog`. |
| `mapping_generation` | `uint64` | Yes | Clock mapping generation (1 for the local lane). |
| `now_host_ns` | `uint64` | Yes | Current host monotonic time, ns. |

#### JogObservation

| Name | Type | Required | Description |
|---|---|---|---|
| `Ticket` | `uint64` | Yes | — |
| `ConnectionId` | `uint64` | Yes | — |
| `NativeGeneration` | `uint64` | Yes | — |
| `CapabilityId` | `uint8[16]` | Yes | — |
| `AppGrantGeneration` | `uint64` | Yes | — |
| `ConfigurationEpoch` | `uint64` | Yes | — |
| `HomeEpoch` | `uint64` | Yes | — |
| `ClockMappingGeneration` | `uint64` | Yes | — |
| `JogGeneration` | `uint64` | Yes | Current jog generation. |
| `SourceSequence` | `uint64` | Yes | Sequence of the latest applied input. |
| `InputDeadlineHostNs` | `uint64` | Yes | Deadline of the latest applied input, host ns. |
| `NowHostNs` | `uint64` | Yes | Publication time, host ns. |
| `ObservedSourceSequence` | `uint64` | Yes | — |
| `ObservedOriginHostNs` | `uint64` | Yes | — |
| `FirstObservedHostNs` | `uint64` | Yes | — |
| `ControlReason` | `uint32` | Yes | — |
| `ControlAccepted` | `uint32` | Yes | — |
| `UpdateReason` | `uint32` | Yes | — |
| `StateReason` | `uint32` | Yes | Native jog reason for the current state. |
| `AxisMask` | `uint32` | Yes | Axes of the jog session. |
| `Open` | `uint32` | Yes | 1 while the jog session accepts input. |
| `HasInput` | `uint32` | Yes | 1 once an input has been applied. |
| `VelocityScalePpm` | `uint32` | Yes | — |

#### JogIngressObservation

| Name | Type | Required | Description |
|---|---|---|---|
| `source_sequence` | `uint64` | Yes | — |
| `reason` | `uint32` | Yes | — |
| `now_host_ns` | `uint64` | Yes | — |
| `refused` | `uint64` | Yes | Cumulative refused ingress count for this adapter process; accepted frames do not increment it. |
| `refused_by_reason` | `uint64[15]` | Yes | Cumulative refusal counts indexed by protocol/control.json jog reason 0..14; index 0 remains zero. Unverified fixed capacity: 15 reason slots. |
| `latest_refusal` | [`JogIngressRefusal`](#type-jogingressrefusal) | Yes | Most recent refusal across all ingress transports; zero before any refusal and preserved through acceptance and grant changes. |

#### JogIngressRefusal

| Name | Type | Required | Description |
|---|---|---|---|
| `source_sequence` | `uint64` | Yes | Refused source sequence; zero when no complete input frame is available. |
| `reason` | `uint32` | Yes | Typed native jog reason; zero only before the first refusal. |
| `now_host_ns` | `uint64` | Yes | Host monotonic ns when the adapter recorded the refusal; not an RT application timestamp. |

### Description

#### Description

| Name | Type | Required | Description |
|---|---|---|---|
| *(embedded)* | [`NativeDescription`](#type-nativedescription) | Yes | All fields of `NativeDescription` appear at this level of the object. |
| `contract_version` | `uint32` | Yes | Exactly 1. |
| `capabilities_digest` | `string` | Yes | Lowercase SHA-256 of the exact committed UTF-8 LF schema file bytes, including its final newline; no self-referential digest field. |
| `capabilities` | [`CapabilityInfo[]`](#type-capabilityinfo) | Yes | Every target capability with its implementation state and transport. |
| `control_idle_timeout_ns` | `uint64` | No | Compiled link idle timeout in ns; clients keep reserved connections alive at one third of this bound, independently of grants. Unverified LAN and internet policy: 90000000000 ns. An omitted legacy field uses 30000000000 ns. |
| `program` | [`Program`](#type-program) | No | Detached prepared program metadata including both identity digests; absent when no program is prepared. |
| `robot` | [`RobotDescription`](#type-robotdescription) | No | Null when no robot definition is bound or no compiled artefact is loaded; never guessed from axis count. |
| `drives` | [`DriveDescription[]`](#type-drivedescription) | Yes | Per-axis compiled drive configuration and current verified bring-up identity; empty without a compiled artefact. |

#### NativeDescription

| Name | Type | Required | Description |
|---|---|---|---|
| `backend` | `string` | Yes | Core backend, for example `simulation`. |
| `schema` | `string` | Yes | — |
| `protocol_major` | `uint32` | Yes | Native protocol major version. |
| `protocol_minor` | `uint32` | Yes | Native protocol minor version. |
| `configuration_sha256` | `string` | Yes | Digest of the compiled configuration. |
| `machine_sha256` | `string` | Yes | Canonical machine-semantics digest recomputed by the daemon at startup. |
| `deployment_sha256` | `string` | Yes | Deployment identity digest (host, NIC, CPUs, sockets, pair, users). |
| `max_trajectory_points` | `uint32` | Yes | Maximum points per trajectory. |
| `resident_plans` | `uint32` | Yes | Native plan slots. |
| `cycle_ns` | `uint64` | Yes | Cycle period, ns. |
| `interpolation` | `string` | Yes | — |
| `stop` | `string` | Yes | — |
| `axes` | [`AxisDescription[]`](#type-axisdescription) | Yes | Axes in native index order. |
| `bus` | `object` | Yes | Current native bus policy and held_slaves inventory; the machine digest binds configured policy and declarations, not discovered devices. A declared cell I/O terminal has disposition io_terminal and contributes one required responding slave. Its declared identity and presence remain mandatory under both hold and refuse; after activation it must be OP or admission reports io_terminal_not_operational. Held devices alone receive no PDOs or output capability. |
| `max_grant_lease_ns` | `uint64` | Yes | Compiled cell lease ceiling in ns; a longer effective lease increases unattended-stop delay after link loss. |
| `max_jog_input_age_ns` | `uint64` | Yes | Compiled cell capture-age and lifetime ceiling in ns; longer ages prolong stale velocity application before the existing expiry ramp. |
| `io` | `object` | No | Configured cell I/O terminal identity, torch_qualified=false, named input classes and polarities, and output classes, OFF safe states, expiry_ns budgets and independent readback wiring. Omitted when no terminal is configured; this policy is separate from NativeStatus.io observations. |

#### AxisDescription

| Name | Type | Required | Description |
|---|---|---|---|
| `completion_tolerance` | `float64` | Yes | Settle tolerance at the final sample, in `position_unit`. |
| `feedback_fields` | `string[]` | Yes | — |
| `index` | `uint32` | Yes | Native index; the bit position in every axis mask. |
| `id` | `string` | Yes | Axis identifier, for example `J1`. |
| `position_unit` | `string` | Yes | `rad` or `m`. |
| `counts_per_unit` | `float64` | Yes | Drive counts per `position_unit`, compiled from the robot definition. |
| `sign` | `int` | Yes | Command direction, +1 or −1. |
| `feedback_wrap` | `bool` | Yes | — |
| `command_wrap` | `bool` | Yes | — |
| `velocity_command_mapped` | `bool` | Yes | — |
| `native_home` | `bool` | Yes | True if the axis supports native Home. |
| `require_home` | `bool` | Yes | True if motion requires a valid Home. |
| `min_position` | `float64` | Yes | Lower position limit, in `position_unit`. |
| `max_position` | `float64` | Yes | Upper position limit, in `position_unit`. |
| `max_velocity` | `float64` | Yes | Velocity limit, `position_unit`/s. |
| `jog_acceleration` | `float64` | Yes | Jog acceleration, `position_unit`/s². |
| `max_target_lead` | `float64` | Yes | Largest allowed command lead over feedback, in `position_unit`. |
| `following_error` | `float64` | Yes | Following-error bound, in `position_unit`. |
| `following_error_timeout_ns` | `uint64` | Yes | How long a following error may persist, ns. |
| `completion_timeout_ns` | `uint64` | Yes | Settle deadline after the final sample, ns. |
| `interpolation` | `string` | Yes | hermite_position_with_velocity, linear_without: cubic Hermite q when both knots supply qd, otherwise linear q; configured shortest-step command wrapping applies. |
| `feedforward` | `string` | Yes | qd_optional: both supplied knot velocities shape Hermite position; feedforward is its derivative. If either knot lacks qd, feedforward is the linear segment slope. Endpoint hold velocity is zero. |
| `checks` | `object` | Yes | Object with exactly position: declared, velocity: declared, acceleration: declared or not_declared, jerk: unsupported. Prepare checks position range and supplied qd/segment velocity. With max_acceleration, check consecutive qd differences per segment or second q differences across segment midpoints when qd is absent; mixed qd presence rejects. Without the profile field acceleration is not_declared. Sampled derivatives give no continuous acceleration guarantee at linear corners or endpoint hold. Jerk is never checked. |
| `endpoint` | `string` | Yes | hold_last_sample_then_settle: hold final q with zero velocity; fresh ready feedback within completion_tolerance by completion_timeout_ns, as evaluated by completion.hpp. |
| `max_acceleration` | `float64` | No | Positive finite profile bound in position units per s^2; absent only when acceleration is not_declared. |
| `brake_override_reason` | `string` | No | Nonempty reason for an explicit bench brake override (effective present=false); absent without an override. Does not claim the physical brake is absent or qualified. |

#### RobotDescription

| Name | Type | Required | Description |
|---|---|---|---|
| `model_id` | `string` | Yes | The robot description's model id: the directory name under robot_description/robots/ the machine was compiled with. |
| `robot_description_sha256` | `string` | Yes | sha256:<64 lowercase hex>, the description's manifest identity over the resources it registers (docs/ROBOT-DESCRIPTION-CONTRACT.md); the identity a plan's header must carry. |
| `machine_planning_calibration_sha256` | `string` | Yes | sha256:<64 lowercase hex> of the served machine_planning_calibration.json bytes, this machine's deviation from the description; the identity a plan's header must carry. |
| `resources` | [`ResourceInfo[]`](#type-resourceinfo) | Yes | The description's manifest and every file it registers, by description-relative path, plus machine_planning_calibration.json; unverified maximum 1024 entries and 134217728 distinct bytes. |

#### ResourceInfo

| Name | Type | Required | Description |
|---|---|---|---|
| `path` | `string` | Yes | Description-relative path (robot_description_manifest.json, robot.urdf, meshes/<file>, rtcore_definition.json, ...) or machine_planning_calibration.json. Never a server filesystem path. |
| `sha256` | `string` | Yes | Lowercase SHA-256 of the exact resource bytes. |
| `bytes` | `uint64` | Yes | Exact resource byte count; unverified maximum 33554432 bytes. |
| `media_type` | `string` | Yes | application/json, application/xml, model/vnd.collada+xml, model/stl or application/octet-stream. |

#### DriveDescription

| Name | Type | Required | Description |
|---|---|---|---|
| `axis` | `string` | Yes | Configured axis ID in native axis order. |
| `config_name` | `string` | Yes | Compiled drive configuration name. |
| `config_sha256` | `string` | Yes | SHA-256 of canonical drive configuration content used in the compiled identity. |
| `slave_position` | `uint16` | Yes | Configured EtherCAT slave position. |
| `verified_identity` | [`DriveIdentity`](#type-driveidentity) | No | Null unless current native configuration verification and CoE revision readback are valid. Vendor/product are configured expectations, revision is observed. Simulation synthesizes vendor/product from the profile and cannot independently inject their mismatch. |

#### DriveIdentity

| Name | Type | Required | Description |
|---|---|---|---|
| `expected_vendor_id` | `uint32` | Yes | Configured vendor expectation used by IgH slave matching; not an independent vendor readback. |
| `expected_product_code` | `uint32` | Yes | Configured product expectation used by IgH slave matching; not an independent product readback. |
| `observed_coe_revision` | `uint32` | Yes | Observed CoE 0x1018:3 firmware revision; distinct from the configured SII revision and never substituted from configuration. |

### Status

#### ProcessStatus

| Name | Type | Required | Description |
|---|---|---|---|
| `daemon_incarnation` | `string` | Yes | Core process identity for this snapshot. |
| `adapter_incarnation` | `string` | Yes | rt-control process identity. |
| `grant` | [`GrantObservation`](#type-grantobservation) | Yes | Native grant observation. |
| `jog` | [`JogObservation`](#type-jogobservation) | Yes | Native jog observation. |
| `jog_ingress` | [`JogIngressObservation`](#type-jogingressobservation) | Yes | Adapter-lifetime ingress outcomes across datagram, WSS and UpdateJog; counters and latest refusal match GET /v1/jog/ingress. |
| `core` | [`NativeStatus`](#type-nativestatus) | Yes | Native core status. |
| `motion` | [`MotionState`](#type-motionstate) | Yes | Native motion state. |
| `execution` | [`Execution`](#type-execution) | Yes | Program and handle lifecycle. |
| `time_ns` | `uint64` | Yes | Native publication time, ns. |
| `axes` | [`LogicalAxisStatus[]`](#type-logicalaxisstatus) | Yes | Per-axis logical status, in Describe order. |
| `generations` | [`StatusGenerations`](#type-statusgenerations) | Yes | Current generations and epochs. |
| `plan_cursor` | [`PlanCursor`](#type-plancursor) | Yes | Native plan cursor. |
| `buffer_health` | [`BufferHealth`](#type-bufferhealth) | Yes | Native buffer health. |
| `adapter` | [`AdapterStatus`](#type-adapterstatus) | Yes | Adapter runtime observations; not native motion state. |

#### NativeStatus

| Name | Type | Required | Description |
|---|---|---|---|
| `daemon_incarnation` | `string` | Yes | Identity of this core process. |
| `rt_cpu` | `uint32` | Yes | Configured RT CPU index as an exact uint32 integer. |
| `housekeeping_cpus` | `string` | Yes | Effective Linux CPU-list mask for housekeeping workers, excluding the RT CPU. |
| `affinity_applied` | `object` | Yes | Worker name to affinity-application success; false is not qualified placement and null means no observations. |
| `recovery` | [`RecoveryStatus`](#type-recoverystatus) | Yes | Latched faults and recovery classes. |
| `time_ns` | `uint64` | Yes | Native publication time, ns. |
| `backend` | `string` | Yes | Core backend. |
| `configuration_sha256` | `string` | Yes | Digest of the compiled configuration. |
| `control_generation` | `uint64` | Yes | Native control generation (the core's fence). |
| `lease_valid` | `uint32` | Yes | 1 while the core holds a valid lease. |
| `config_verified_mask` | `uint32` | Yes | Axes whose drive configuration is verified. |
| `home_valid_mask` | `uint32` | Yes | Axes with valid Home evidence. |
| `home_epoch` | `uint64` | Yes | Increments when Home evidence changes. |
| `commissioning_phase` | `uint32` | Yes | 0 when no Home or commissioning is running. |
| `safety_fault_mask` | `uint32` | Yes | Non-zero blocks all motion. |
| `execution_fault_reasons` | `uint32` | Yes | Latched execution fault bits; see error codes. |
| `last_bus_failure_operation` | `uint32` | Yes | — |
| `last_bus_failure_code` | `int64` | Yes | — |
| `armed` | `uint32` | Yes | 1 when armed. |
| `axis_enable_mask` | `uint32` | Yes | Axes that are enabled. |
| `native_home_active_axis_mask` | `uint32` | Yes | Axes running native Home. |
| `axes` | [`AxisStatus[]`](#type-axisstatus) | Yes | Per-axis native status. |
| `configuration_epoch` | `uint64` | Yes | — |
| `buffer_health` | [`BufferHealth`](#type-bufferhealth) | Yes | — |
| `plan_cursor` | [`PlanCursor`](#type-plancursor) | Yes | — |
| `bus` | `object` | Yes | Native bus policy and held_slaves with retained, observed and expected declared identities, presence, AL state and disposition; unreadable identity fields are null. Dispositions: held for an undeclared device allowed by hold; declared_unused for a matching declaration; refused_unknown for an undeclared device under refuse; refused_identity for a declared identity mismatch; identity_unreadable for an unavailable SII identity; refused_missing for a missing required declaration; identity_changed for a post-admission identity change under hold; disappeared for a missing unused device under hold; refused_state for a held device outside PREOP or INIT, or a configured terminal outside OP after activation. Refuse continuously requires declared identities and presence; post-admission refusal latches a readiness fault and inhibits outputs before submission, requiring public reset_fault (FaultReset), reacquisition of the application session, then explicit Enable and Arm after restoring the device and verified readiness. Enable before reset is rejected with native not_ready (reason 2); reset clears the readiness fault (reason 128) and safety mask while keeping outputs inhibited and retiring the session. Lost Home evidence requires separate qualified recovery before motion. Hold reports post-admission unused-device identity or presence changes and continues axes while axis readiness remains valid. Admission reason and reason_position identify identity_unreadable, unknown_slave, declared_identity_mismatch, declared_slave_missing, unsafe_held_state or capacity_exceeded; none means no observed refusal. No output capability is granted to held devices. A declared cell I/O terminal has disposition io_terminal and contributes one required responding slave. Its declared identity and presence remain mandatory under both hold and refuse; after activation it must be OP or admission reports io_terminal_not_operational. Held devices alone receive no PDOs or output capability. |
| `io` | [`IoState`](#type-iostate) | Yes | — |

#### AxisStatus

| Name | Type | Required | Description |
|---|---|---|---|
| `drive_alarm` | `string` | Yes | Drive display alarm code decoded from the drive's error-code objects; unresolved bus candidates joined by \|; empty when clear, unmapped, or another drive family. |
| `drive_alarm_text` | `string` | Yes | Text meaning of drive_alarm; multiple candidates joined by \| in matching order. |
| `drive_alarm_aux_code` | `uint32` | Yes | Raw auxiliary alarm word read from the drive. Retained for the current nonzero drive error event and configuration epoch, zero when invalid or cleared. Separate from manufacturer_error_code, which carries the cyclic manufacturer_err PDO. |
| `drive_alarm_aux_valid` | `bool` | Yes | True only for an admitted auxiliary reply matching the current nonzero drive error event and configuration epoch; false when missing, stale, or cleared, preventing zero from being mistaken for a valid reply. |
| `logical_position` | `float64` | Yes | — |
| `logical_valid` | `bool` | Yes | — |
| `coordinate_counts` | `int64` | Yes | — |
| `absolute_source_counts` | `int32` | Yes | — |
| `independent_anchor_source` | [`IndependentAnchorSource`](#type-independentanchorsource) | No | Fresh independent raw acquisition; does not grant a command-frame binding or Home. |
| `coordinate_valid` | `uint32` | Yes | — |
| `coordinate_source_valid` | `uint32` | Yes | — |
| `pdo_fresh` | `uint32` | Yes | — |
| `native_home_position_offset` | `int32` | Yes | — |
| `observed_revision_no` | `uint32` | Yes | — |
| `revision_readback_valid` | `uint32` | Yes | — |
| `serial_no` | `uint32` | Yes | — |
| `serial_readback_valid` | `uint32` | Yes | — |
| `anchor_period_counts` | `uint32` | Yes | — |
| `anchor_tolerance_counts` | `uint64` | Yes | — |
| `coordinate_reason` | `uint32` | Yes | — |
| `coordinate_identity_sha256` | `string` | Yes | Canonical coordinate digest; empty means persisted anchors are unavailable. |
| `coordinate_identity` | `string` | Yes | Canonical per-axis fields; empty means persisted anchors are unavailable. |
| `anchor_identity_changed_field` | `string` | Yes | Differing identity component for anchor_identity_mismatch, or empty. |
| `anchor_reason` | `string` | Yes | — |
| `restored_anchor_home_epoch` | `uint64` | Yes | — |
| `pos_counts` | `int32` | Yes | — |
| `statusword` | `uint16` | Yes | — |
| `error_code` | `uint16` | Yes | — |
| `vendor_id` | `uint32` | Yes | — |
| `product_code` | `uint32` | Yes | — |
| `slave_position` | `uint16` | Yes | — |
| `time_ns` | `uint64` | Yes | — |
| `logical_target` | `float64` | Yes | — |
| `readiness` | `string` | Yes | — |
| `brake_state` | `uint32` | Yes | — |
| `home_valid` | `bool` | Yes | — |
| `logical_velocity` | `float64` | No | — |
| `velocity_actual_counts_per_s` | `int32` | Yes | Drive 0x606C signed reference counts/s in the position frame, sampled each cycle. Diagnostics only; never a speed-safety certification or an execution guard. |
| `following_error_counts` | `int32` | Yes | Drive 0x60F4 signed reference counts in the position frame; preferred input to the configured collision watchdog. |
| `velocity_actual_valid` | `bool` | Yes | True only when velocity_actual is mapped and this cycle has fresh PDO feedback. An absent signal is zero with false validity. |
| `following_error_valid` | `bool` | Yes | True only when following_error is mapped and this cycle has fresh PDO feedback. An absent signal is zero with false validity. |
| `max_abs_velocity_actual_counts_per_s` | `uint32` | Yes | Maximum absolute valid drive velocity in counts/s since the last accepted Arm(true); zero before Arm. Retained through Stop/disarm; INT32_MIN has magnitude 2147483648. |
| `max_abs_following_error_counts` | `uint32` | Yes | Maximum absolute valid drive following error in counts since the last accepted Arm(true); zero before Arm. Retained through Stop/disarm; INT32_MIN has magnitude 2147483648. |
| `di_bits` | `uint32` | Yes | Current 0x60FD DI logic bits; interpret only when di_valid. |
| `di_valid` | `bool` | Yes | True only for a fresh PDO and verified digital-input mapping. |
| `external_enable_active` | `bool` | Yes | Drive servo-on (S-ON) input active; interpret only when external_enable_valid. Diagnostics only; does not prove STO or torque state. |
| `external_enable_valid` | `bool` | Yes | True only with valid DI feedback and exactly one explicitly configured, verified S-ON digital-input function. |
| `torque_raw` | `int32` | Yes | Drive 0x6077 raw per-mille of rated torque; use only with fresh PDO feedback. |
| `collision_watchdog` | [`CollisionWatchdogStatus`](#type-collisionwatchdogstatus) | Yes | Native cycle evaluation and latched trip evidence for this axis. |
| `calibration_valid` | `bool` | No | Persistent calibration applicability; null/absent means legacy unknown. |
| `position_state` | `string` | No | Native state: unverified, recovering, trusted or lost; empty means legacy unknown. |
| `position_reason` | `string` | No | Native CoordinateReason name; none means no coordinate refusal; absent means legacy unknown. |
| `audit_status` | `string` | No | Last stationary audit: pending, verified or unavailable; never motion authority. |
| `audit_reason` | `string` | No | Native SampleInvalidity name; none means no reported audit failure; absent means legacy unknown. |
| `audit_last_verified_ns` | `uint64` | No | Monotonic acquisition completion of last accepted independent evidence; zero means never. |

#### LogicalAxisStatus

| Name | Type | Required | Description |
|---|---|---|---|
| `drive_alarm` | `string` | Yes | Drive display alarm code decoded from the drive's error-code objects; unresolved bus candidates joined by \|; empty when clear, unmapped, or another drive family. |
| `drive_alarm_text` | `string` | Yes | Text meaning of drive_alarm; multiple candidates joined by \| in matching order. |
| `drive_alarm_aux_code` | `uint32` | Yes | Raw auxiliary alarm word read from the drive. Retained for the current nonzero drive error event and configuration epoch, zero when invalid or cleared. Separate from manufacturer_error_code, which carries the cyclic manufacturer_err PDO. |
| `drive_alarm_aux_valid` | `bool` | Yes | True only for an admitted auxiliary reply matching the current nonzero drive error event and configuration epoch; false when missing, stale, or cleared, preventing zero from being mistaken for a valid reply. |
| `time_ns` | `uint64` | Yes | — |
| `logical_position` | `float64` | Yes | — |
| `logical_valid` | `bool` | Yes | — |
| `logical_target` | `float64` | Yes | — |
| `logical_velocity` | `float64` | No | — |
| `readiness` | `string` | Yes | ready or first failing axis gate in this order: faulted, mode_mismatch, home_required, coordinate_invalid, not_enabled, not_operation_enabled, brake_wait; group authority still applies. |
| `position_counts` | `int32` | Yes | — |
| `statusword` | `uint16` | Yes | — |
| `brake_state` | `uint32` | Yes | — |
| `home_valid` | `bool` | Yes | — |
| `velocity_actual_counts_per_s` | `int32` | Yes | Drive 0x606C signed reference counts/s in the position frame, sampled each cycle. Diagnostics only; never a speed-safety certification or an execution guard. |
| `following_error_counts` | `int32` | Yes | Drive 0x60F4 signed reference counts in the position frame; preferred input to the configured collision watchdog. |
| `velocity_actual_valid` | `bool` | Yes | True only when velocity_actual is mapped and this cycle has fresh PDO feedback. An absent signal is zero with false validity. |
| `following_error_valid` | `bool` | Yes | True only when following_error is mapped and this cycle has fresh PDO feedback. An absent signal is zero with false validity. |
| `max_abs_velocity_actual_counts_per_s` | `uint32` | Yes | Maximum absolute valid drive velocity in counts/s since the last accepted Arm(true); zero before Arm. Retained through Stop/disarm; INT32_MIN has magnitude 2147483648. |
| `max_abs_following_error_counts` | `uint32` | Yes | Maximum absolute valid drive following error in counts since the last accepted Arm(true); zero before Arm. Retained through Stop/disarm; INT32_MIN has magnitude 2147483648. |
| `di_bits` | `uint32` | Yes | Current 0x60FD DI logic bits; interpret only when di_valid. |
| `di_valid` | `bool` | Yes | True only for a fresh PDO and verified digital-input mapping. |
| `external_enable_active` | `bool` | Yes | Drive servo-on (S-ON) input active; interpret only when external_enable_valid. Diagnostics only; does not prove STO or torque state. |
| `external_enable_valid` | `bool` | Yes | True only with valid DI feedback and exactly one explicitly configured, verified S-ON digital-input function. |
| `torque_raw` | `int32` | Yes | Drive 0x6077 raw per-mille of rated torque; use only with fresh PDO feedback. |
| `collision_watchdog` | [`CollisionWatchdogStatus`](#type-collisionwatchdogstatus) | Yes | Native cycle evaluation and latched trip evidence for this axis. |
| `calibration_valid` | `bool` | No | Persistent calibration applicability; null/absent means legacy unknown. |
| `position_state` | `string` | No | Native state: unverified, recovering, trusted or lost; empty means legacy unknown. |
| `position_reason` | `string` | No | Native CoordinateReason name; none means no coordinate refusal; absent means legacy unknown. |
| `audit_status` | `string` | No | Last stationary audit: pending, verified or unavailable; never motion authority. |
| `audit_reason` | `string` | No | Native SampleInvalidity name; none means no reported audit failure; absent means legacy unknown. |
| `audit_last_verified_ns` | `uint64` | No | Monotonic acquisition completion of last accepted independent evidence; zero means never. |

#### StatusGenerations

| Name | Type | Required | Description |
|---|---|---|---|
| `time_ns` | `uint64` | Yes | — |
| `native` | `uint64` | Yes | — |
| `configuration_epoch` | `uint64` | Yes | — |
| `home_epoch` | `uint64` | Yes | — |
| `execution` | `uint64` | Yes | Adapter handle execution generation; correlated native Start acknowledgement. |
| `jog` | `uint64` | Yes | — |
| `grant` | `uint64` | Yes | — |
| `grant_time_ns` | `uint64` | Yes | Daemon fast-grant publication timestamp; zero if grant observation is unavailable. Independent of metrics time_ns. |
| `jog_time_ns` | `uint64` | Yes | Daemon jog publication timestamp; zero if jog observation is unavailable. Independent of metrics time_ns. |

#### AdapterStatus

| Name | Type | Required | Description |
|---|---|---|---|
| `heap_inuse` | `uint64` | Yes | Go runtime bytes at request observation. |
| `sys` | `uint64` | Yes | Go runtime bytes at request observation. |
| `goroutines` | `uint32` | Yes | Go runtime goroutine count at request observation. |
| `open_preparation_slots` | `uint32` | Yes | Available adapter preparation admissions, from the two atomic reservations. |

#### CollisionWatchdogStatus

| Name | Type | Required | Description |
|---|---|---|---|
| `armed` | `bool` | Yes | True only while enabled, armed and receiving valid feedback; false after a trip. |
| `torque_cycles` | `uint32` | Yes | Consecutive torque breaches; equality resets. Frozen at trip. |
| `following_error_cycles` | `uint32` | Yes | Consecutive following-error breaches independent of torque. Frozen at trip. |
| `peak_torque_raw` | `uint32` | Yes | Peak absolute 0x6077 raw per-mille since arm; frozen at trip. |
| `peak_following_error_counts` | `uint64` | Yes | Peak absolute counts since arm; drive 0x60F4 when valid, otherwise prior wire target minus feedback with configured wrap handling. |
| `last_trip_quantity` | `string` | Yes | none, torque, following_error or torque_and_following_error; retained across explicit reset. |
| `trip_sustained_cycles` | `uint32` | Yes | Configured consecutive count reached at last trip, 2..1000. |
| `trip_count` | `uint64` | Yes | Monotonic per-axis trip sequence for the daemon incarnation. |
| `samples` | `uint64` | Yes | Fresh normal armed/enabled cycles accumulated since arm, even if the watchdog is disabled; peaks include pre-motion hold. Frozen after trip or disarm. |
| `last_trip_peak_torque_raw` | `uint32` | Yes | Peak absolute torque at last trip, retained across reset and rearm. |
| `last_trip_peak_following_error_counts` | `uint64` | Yes | Peak absolute following error at last trip, retained across reset and rearm. |

#### IndependentAnchorSource

| Name | Type | Required | Description |
|---|---|---|---|
| `valid` | `bool` | Yes | Native acquisition validity; never motion permission. |
| `encoder_counts` | `int64` | Yes | Independent signed raw encoder count. |
| `completed_ns` | `uint64` | Yes | Host-monotonic acquisition completion timestamp in nanoseconds. |
| `maximum_age_ns` | `uint64` | Yes | Configured acquisition freshness bound in nanoseconds. |

### Recovery

#### RecoveryStatus

| Name | Type | Required | Description |
|---|---|---|---|
| `home_valid_mask` | `uint32` | Yes | Native per-axis home evidence remaining after observed recovery. |
| `faults` | [`FaultRecovery[]`](#type-faultrecovery) | Yes | Currently latched fault bits with axis masks and recovery policy. |
| `reset_sequence` | `uint64` | Yes | Exact native command sequence of the observed reset, zero before any reset; response sequence must match for reset_fault. |
| `reason` | `string` | Yes | Native recovery summary: empty when no fault persists, otherwise fault_persists; FaultResetRecovery refuses a persistent fault. |
| `outcomes` | [`FaultRecovery[]`](#type-faultrecovery) | Yes | Per-bit outcomes of the correlated reset; never inferred from elapsed time. |

#### FaultRecovery

| Name | Type | Required | Description |
|---|---|---|---|
| `bit` | `uint32` | Yes | Native execution-fault bit index. |
| `name` | `string` | Yes | Native fault name for that bit. |
| `axis_mask` | `uint32` | Yes | Affected axis evidence mask. |
| `recovery` | `string` | Yes | Native recovery-policy label for the fault bit. |
| `outcome` | `string` | Yes | Native per-bit outcome: persists, cleared or rehome_required. |
| `rehome_axis_mask` | `uint32` | Yes | Axes requiring qualified re-home after this outcome. |

### Events

#### Event

| Name | Type | Required | Description |
|---|---|---|---|
| `sequence` | `uint64` | Yes | Strictly increasing within adapter_incarnation; drop records use last_lost_sequence; no renumbering. |
| `time_ns` | `uint64` | Yes | Native observation host time, except telemetry_mark uses adapter host monotonic time after native acceptance; never proof of motion or disk output. |
| `type` | `string` | Yes | grant_acquired, grant_renewed, grant_released, grant_expired, grant_revoked, enable_changed, arm_changed, handle_transition, execution_started, execution_completed, execution_faulted, execution_aborted, jog_begin, jog_end, jog_expired, jog_ramping, jog_limited, fault_latched, fault_cleared, home_epoch_changed, daemon_incarnation_changed, adapter_incarnation_changed, publisher_overflow, events_dropped, telemetry_mark |
| `daemon_incarnation` | `string` | Yes | — |
| `adapter_incarnation` | `string` | Yes | — |
| `grant` | [`GrantEvent`](#type-grantevent) | No | — |
| `handle` | [`HandleTransitionEvent`](#type-handletransitionevent) | No | — |
| `execution` | [`ExecutionEvent`](#type-executionevent) | No | — |
| `jog` | [`JogEvent`](#type-jogevent) | No | — |
| `fault` | [`FaultEvent`](#type-faultevent) | No | — |
| `enable` | [`EnableEvent`](#type-enableevent) | No | — |
| `epoch` | [`EpochEvent`](#type-epochevent) | No | — |
| `incarnation` | [`IncarnationEvent`](#type-incarnationevent) | No | — |
| `overflow` | [`PublisherOverflowEvent`](#type-publisheroverflowevent) | No | — |
| `events_dropped` | [`EventsDropped`](#type-eventsdropped) | No | — |
| `mark` | [`TelemetryMarkEvent`](#type-telemetrymarkevent) | No | Present for telemetry_mark only, once per accepted mark command; rejected commands produce no mark event. |

#### EventBatch

| Name | Type | Required | Description |
|---|---|---|---|
| `events` | [`Event[]`](#type-event) | Yes | At most 64 records, including at most one leading events_dropped record; 256 retained events. |
| `next_sequence` | `uint64` | Yes | Resume cursor after the last returned event; unchanged when empty. |
| `latest_sequence` | `uint64` | Yes | Newest retained sequence at batch capture. |
| `adapter_incarnation` | `string` | Yes | — |

#### GrantEvent

| Name | Type | Required | Description |
|---|---|---|---|
| `generation` | `uint64` | Yes | — |
| `stopping` | `bool` | Yes | True for a local keepalive while Stop drains; does not confer native authority. |

#### HandleTransitionEvent

| Name | Type | Required | Description |
|---|---|---|---|
| `handle` | `uint64` | Yes | — |
| `from` | `string` | Yes | — |
| `to` | `string` | Yes | — |
| `execution_generation` | `uint64` | Yes | — |
| `native_sequence` | `uint64` | Yes | — |
| `identity` | [`Identity`](#type-identity) | No | Immutable raw trajectory identity supplied at Prepare and checked at Start; echoed unchanged through terminal observation. |
| `generation` | `uint64` | No | Application grant generation that owns this immutable preparation. |

#### ExecutionEvent

| Name | Type | Required | Description |
|---|---|---|---|
| `handle` | `uint64` | Yes | — |
| `generation` | `uint64` | Yes | — |
| `native_sequence` | `uint64` | Yes | — |
| `fault_bits` | `uint32` | Yes | — |
| `recovery` | [`FaultRecovery[]`](#type-faultrecovery) | Yes | — |
| `identity` | [`Identity`](#type-identity) | No | Immutable raw trajectory identity supplied at Prepare and checked at Start; echoed unchanged through terminal observation. |

#### JogEvent

| Name | Type | Required | Description |
|---|---|---|---|
| `generation` | `uint64` | Yes | — |
| `reason` | `uint32` | Yes | — |
| `axis_mask` | `uint32` | Yes | — |
| `velocity_scale_ppm` | `uint32` | Yes | — |

#### FaultEvent

| Name | Type | Required | Description |
|---|---|---|---|
| `bit` | `uint32` | Yes | — |
| `axis_mask` | `uint32` | Yes | — |
| `recovery_class` | `string` | Yes | — |

#### EnableEvent

| Name | Type | Required | Description |
|---|---|---|---|
| `enabled_mask` | `uint32` | Yes | — |
| `armed` | `bool` | Yes | — |

#### EpochEvent

| Name | Type | Required | Description |
|---|---|---|---|
| `previous` | `uint64` | Yes | — |
| `current` | `uint64` | Yes | — |

#### IncarnationEvent

| Name | Type | Required | Description |
|---|---|---|---|
| `previous` | `string` | Yes | — |
| `current` | `string` | Yes | — |

#### PublisherOverflowEvent

| Name | Type | Required | Description |
|---|---|---|---|
| `previous_drops` | `uint64` | Yes | — |
| `total_drops` | `uint64` | Yes | — |

#### EventsDropped

| Name | Type | Required | Description |
|---|---|---|---|
| `first_lost_sequence` | `uint64` | Yes | Inclusive first missing event. |
| `last_lost_sequence` | `uint64` | Yes | Inclusive last missing event; synthetic drop event sequence equals this cursor, so retained sequences are never renumbered. |

#### TelemetryMarkEvent

| Name | Type | Required | Description |
|---|---|---|---|
| `label` | `string` | Yes | Accepted mark label, 1..128 UTF-8 bytes; receipt of queued dump work, not proof of disk output. |
| `native_sequence` | `uint64` | Yes | Accepted mark command sequence returned by POST /v1/control; distinct from event and telemetry sample cursors. |
| `generation` | `uint64` | Yes | Authorizing application grant generation; session capability is never published. |

### Telemetry

#### TelemetryBatch

A binary transport type: it is never encoded as JSON. The fields below are the decoded header plus the record body.

| Name | Type | Required | Description |
|---|---|---|---|
| `daemon_incarnation` | `string` | Yes | 32 lowercase hex bytes identifying the daemon owning these sequences; reconnect must reconcile changes. |
| `adapter_incarnation` | `string` | Yes | 32 lowercase hex bytes identifying the adapter incarnation. |
| `machine_sha256` | `string` | Yes | 64 lowercase hex machine digest bytes. |
| `deployment_sha256` | `string` | Yes | 64 lowercase hex deployment digest bytes. |
| `cycle_period_ns` | `uint64` | Yes | Cycle period in ns. |
| `axis_count` | `uint32` | Yes | Active axes, 1 through 16. |
| `record_layout_digest` | `string` | Yes | 64 ASCII hex bytes from the generated transitive CycleCaptureRecordV2 layout digest. |
| `first_sequence` | `uint64` | Yes | First included sequence; zero when empty. |
| `last_sequence` | `uint64` | Yes | Last included sequence or after+dropped when empty; resume cursor. |
| `dropped` | `uint64` | Yes | Exact number of records lost after the requested cursor and before this batch. |
| `record_count` | `uint32` | Yes | Number of complete records, at most min(ring capacity,4096). |
| `records` | `bytes` | Yes | Opaque binary body; never encoded as JSON or base64. Each CycleCaptureAxisV2 appends velocity_actual_counts_per_s (i32), following_error_counts (i32), velocity_actual_valid (u32 0/1), following_error_valid (u32 0/1). Drive position-frame counts/s and counts; following error also feeds the configured collision watchdog. No speed-safety certification. |

### Cell I/O

#### IoState

| Name | Type | Required | Description |
|---|---|---|---|
| `time_ns` | `uint64` | Yes | — |
| `cycle` | `uint64` | Yes | — |
| `armed` | `bool` | Yes | — |
| `reason` | `uint32` | Yes | — |
| `inputs` | [`IoInput[]`](#type-ioinput) | Yes | — |
| `outputs` | [`IoOutput[]`](#type-iooutput) | Yes | — |

#### IoInput

| Name | Type | Required | Description |
|---|---|---|---|
| `name` | `string` | Yes | — |
| `value` | `bool` | Yes | — |
| `valid` | `bool` | Yes | — |
| `observed_ns` | `uint64` | Yes | — |
| `bit` | `uint32` | Yes | — |
| `fast` | `bool` | Yes | — |

#### IoOutput

| Name | Type | Required | Description |
|---|---|---|---|
| `name` | `string` | Yes | — |
| `intent` | `bool` | Yes | — |
| `commanded` | `bool` | Yes | — |
| `readback` | `bool` | Yes | — |
| `valid` | `bool` | Yes | — |
| `expiry_ns` | `uint64` | Yes | — |
| `observed_ns` | `uint64` | Yes | — |
| `changed_ns` | `uint64` | Yes | — |
| `marker_sample_ns` | `uint64` | Yes | — |
| `marker_cycle` | `uint64` | Yes | — |
| `bit` | `uint32` | Yes | — |
| `torch` | `bool` | Yes | — |

## Related pages

- [Control authority](https://advancedmetalresearch.com/docs/concepts/control-authority): leases, fences and the jog lane.
- [Events and telemetry](https://advancedmetalresearch.com/docs/apis/rt-control-events-telemetry): SSE events, cursors and the binary telemetry layout.
- [Remote access (mTLS)](https://advancedmetalresearch.com/docs/apis/remote-access-mtls): the remote listener, PKI and WSS jog.
- [Go SDK](https://advancedmetalresearch.com/docs/apis/go-sdk), [C++ client](https://advancedmetalresearch.com/docs/apis/cpp-client) and [TypeScript contracts](https://advancedmetalresearch.com/docs/apis/typescript-types).
- [Error codes and fault states](https://advancedmetalresearch.com/docs/reference/error-codes).

## Sources

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

- `rt-core/protocol/application-v1.schema.json:3 (capabilities)`
- `rt-core/protocol/application-v1.schema.json:421 (reasons)`
- `rt-core/protocol/application-v1.schema.json:1037 (rules)`
- `rt-core/protocol/application-v1.schema.json:1115 (types)`
- `rt-core/protocol/control.json (local_jog_update)`
- `rt-core/adapters/rosie/control/http.go:52-283`
- `rt-core/adapters/rosie/control/admission.go:19-217`
- `rt-core/adapters/rosie/control/idempotence.go:12-111`
- `rt-core/adapters/rosie/control/controller.go:546-1649`
- `rt-core/adapters/rosie/control/handles.go:122-207`
- `rt-core/adapters/rosie/control/halt_linux.go:25-79`
- `rt-core/adapters/rosie/control/reset_linux.go:22-154`
- `rt-core/adapters/rosie/control/cell_io_linux.go:66-88`
- `rt-core/adapters/rosie/control/telemetry_linux.go:18-47`
- `rt-core/adapters/rosie/control/jog_linux.go:26-396`
- `rt-core/adapters/rosie/control/jog_ws.go:103-393`
- `rt-core/adapters/rosie/control/events.go:334-421`
- `rt-core/adapters/rosie/control/telemetry_publication.go:104-216`
- `rt-core/adapters/rosie/control/resources.go:358-373`
- `rt-core/adapters/rosie/control/remote_listener.go:60-145`
- `rt-core/adapters/rosie/control/link_timing.go:8`
- `rt-core/cmd/rt-control/main.go:25-39`
- `rt-core/host/rosie-rt-core.service:24-26`
- `rt-core/sdk/control/operations.go:14-157`
- `rt-core/clients/cpp/include/rosie/rt_control_client.hpp:588-906`
