# Teleoperation Sessions and Operator Dispatch

> The teleop session record, its safety states, and how the ACD matches a robot's distress call to a certified operator.

A teleop session binds one robot to one human operator for the duration of
a remote-control attachment. In ClutchCall robotics, the session is the
unit that the Operations wallboard renders and that the admin overview rolls
up: it carries the link quality, the safety state, and the operator who is
holding the robot right now.

Two console screens read this model:

| Screen | What it shows | Reads |
| ------ | ------------- | ----- |
| Admin · overview | Fleet / operator / session rollups, plus a dispatch control that raises a distress call | `api.robots()`, `api.operators()`, `api.liveSessions()` |
| Operations · wallboard | One card per live session, alarm states first, plus the robot-namespace relay panel and the safety-event feed | `api.liveSessions()`, `api.robots()`, `api.audit()` |

## The session record

Each live session exposes the following fields. The wallboard card renders
all of them.

| Field | Meaning |
| ----- | ------- |
| `id` | Session identifier. The wallboard links to `/ops/sessions?s=<id>`. |
| `robot` | Robot id the session is attached to. Joined against the fleet registry. |
| `site` | Site the robot sits in. |
| `operatorName` | The human currently attached. |
| `region` | The operator's region — the other end of the RTT path. |
| `startedAt` | Attach time. The card shows it as an elapsed "up …". |
| `rttMs` | Round-trip time on the control path. |
| `divergenceDeg` | Commanded-versus-observed arm divergence, in degrees. |
| `state` | The safety state (see below). |
| `velocityScale` | The fraction of full commanded velocity currently allowed. |

A session appears on the wallboard once it is attached; the header's
"N attached" count is simply the number of session records returned.

## Safety states

Six states exist. The console treats them as a fixed set — each has its own
colour token and its own one-line meaning, both used consistently by the
wallboard pills and the overview's *Sessions by state* table.

| State | Meaning |
| ----- | ------- |
| `BOOT` | Edge agent starting. Not yet holding a safety posture. |
| `SAFE` | Holding position — not armed. No operator command reaches the actuators. |
| `ARMED` | Live under operator control. |
| `DEGRADED` | Velocity-scaled because the link is poor. The session is still armed, but commanded motion is being reduced. |
| `DIVERGED` | The arm is not tracking the commanded pose. |
| `PROTECTIVE_STOP` | Latched stop, awaiting clear. |

`PROTECTIVE_STOP` and `DIVERGED` are the two states the wallboard treats as
alarms: the card border and background pick up the state colour instead of
the neutral panel styling, and the state hint is coloured rather than muted.

## How the wallboard orders sessions

Cards are sorted by a fixed priority so that the sessions needing a human
look are the ones at the top of the grid:

```
PROTECTIVE_STOP → DIVERGED → DEGRADED → ARMED → SAFE → BOOT
```

The screen header also carries a live count chip for `ARMED`, `DEGRADED`,
`DIVERGED` and `PROTECTIVE_STOP`, coloured per state. States with no
sessions show `0` rather than disappearing.

## Divergence, velocity scale, and RTT

The three numbers on a session card are the ones that decide whether a
session stays armed. The card highlights them at these display thresholds:

| Metric | Highlighted when | Rendered as |
| ------ | ---------------- | ----------- |
| `rttMs` | above 320 ms | warning tone |
| `divergenceDeg` | above 8° | warning tone |
| `divergenceDeg` | above 15° | the `DIVERGED` state colour |
| `velocityScale` | below 1 | warning tone, shown as a percentage |

`velocityScale` is shown as a rounded percentage of full speed, so anything
under 100% means the session is being scaled back — which is exactly the
condition `DEGRADED` describes. Treat these as the console's display
thresholds for drawing attention, not as the control limits enforced on the
edge.

On the dispatch side, the admin overview highlights a *matched RTT* above
250 ms when it reports a successful match, because that is the number the
operator will feel as soon as they accept.

## Requesting a session from the edge agent

When a robot needs a human, the edge agent asks the platform for a session.
The admin overview's **Raise a distress call** control issues the same
`POST /v1/sessions` request the edge agent makes, so the console path and
the integration path are one code path.

The request identifies the robot. The platform derives two things from the
robot record:

- `pop` — the robot's point of presence, used for the RTT ranking.
- `skill` — the first token of the robot's required certification
  (`reqCert`), split on a middot or whitespace. A robot whose `reqCert`
  reads `pick-place · tier-2` matches on `pick-place`.

There are two outcomes:

| Response | Meaning |
| -------- | ------- |
| `201 matched` | An operator was selected and their console is ringing. The response carries the session id, the matched operator, and the matched RTT. |
| `202 queued` | No Ready operator holds the required certification. The call is enqueued and the ACD serves it when one frees. |

The offer that is pushed to the operator's console carries:

| Field | Meaning |
| ----- | ------- |
| `sessionId` | The session the operator will attach to on accept. |
| `robotId`, `robotModel`, `site` | Which robot is calling, and from where. |
| `rttMs` | The RTT the ACD measured for this operator to this robot's PoP. |
| `operatorId` | The operator being rung. |
| `offeredAt` | When the offer was raised. |
| `expiresAt` | `offeredAt` plus 30 seconds. |

## How the ACD picks an operator

The dispatch selector is `claim_lowest_rtt`. It takes the robot's PoP and
required skill, plus a pool of operator presences, and returns the
lowest-RTT operator who is certified for that skill — or nothing, in which
case the call is queued.

The pool is assembled in this order:

1. **Live readiness.** Operators who have broadcast readiness from the
   operator *Ready* screen — in any tab — over the ring channel. A
   live-Ready operator is always preferred.
2. **Roster fallback.** If no operator has broadcast readiness, the
   directory roster is used instead: operators whose presence is `ready` or
   `attached`, mapped into presences whose certification list is the set of
   their certification skills. This exists so a freshly opened console can
   still dispatch.

Each presence carries `operatorId`, `name`, `available`, `region`, the
operator's `certs` as skill names, and `since`.

## Certifications and skill matching

Matching is exact on the skill token: the ACD only considers operators
whose certification list contains the skill derived from the robot's
`reqCert`. Certification is a hard filter, applied before the RTT ranking —
a nearer operator without the certification is never selected over a
farther certified one. If the filter empties the pool, the result is
`202 queued`, and the response names the skill that went unserved.

## Queued calls and offer expiry

A matched call is an *offer*, not an attachment. The offer is pushed over
the ring channel to the named operator's console, which rings. The operator
accepts and lands in the cockpit on that session id.

The offer window is 30 seconds: `expiresAt` is set to 30 seconds after
`offeredAt` at the moment the offer is raised.

A queued call (`202`) holds the robot's skill requirement and waits. The
ACD serves it when a certified operator frees up.

## Protective stop handling

`PROTECTIVE_STOP` is latched — the session stays in it until it is cleared,
and the wallboard says so in the card hint ("latched, awaiting clear").
Because it sorts first in the card priority, a latched session is always at
the top of the grid.

Neither the wallboard nor the admin overview exposes a clear action. Both
screens are read-only with respect to safety state: they report it, colour
it, and route you to the session detail view. Operator-initiated stops also
land in the wallboard's **Safety events** feed, which streams the audit
records for the org.

## When session state and registry status disagree

The session record and the fleet registry are two different sources. The
session says an operator is attached; the registry says whether the edge
agent is heartbeating. The wallboard joins them per robot and, when the
registry reports anything other than `online`, stamps a warning chip on the
card reading `robot <status>`.

That chip means the session record may be stale. Do not read the card's
RTT, divergence or velocity numbers as a live link until the robot
heartbeats again.

## Session state versus the relay data plane

"Attached" is a control-plane fact. Whether teleop frames are actually
moving is a data-plane fact, and the wallboard shows it separately: a relay
panel for the `robot` namespace, scoped to the current org.

These two come apart exactly when it matters. A held session that has
stopped delivering frames looks identical to a healthy one on the session
cards alone — the relay panel is what distinguishes them.

## Link health floors

The wallboard's **Link health** table lists the propagation floor for each
operator-to-robot path in use, alongside how many live sessions currently
ride it:

| Path | Floor | Note |
| ---- | ----- | ---- |
| BLR → US-East (Virginia) | 275 ms | QUIC buys jitter and recovery, not propagation |
| BLR → US-West (Oregon) | 238 ms | — |
| Manchester → UK (London) | 177 ms | Shortest glass reach |

The floor is the distance, not the stack. No transport change moves it,
which is why the ACD ranks on RTT and why operator region is part of the
session record.

## What the admin overview counts

The four headline metrics on the admin overview are rollups over the same
live data the wallboard renders:

| Metric | Definition |
| ------ | ---------- |
| Live sessions | Number of session records — sessions attached now. |
| Robots online | Robots with registry status `online`, over the total fleet. |
| Operators available | Operators whose presence is `ready` or `attached`. |
| E-stops · 24h | Operator-initiated stops in the trailing day. |

The *Sessions by state* table below them counts each state from that same
live roster, so its rows always sum to the "Live sessions" headline.

## Related

- [Modalities overview](/modalities/overview)
- [Authentication](/concepts/authentication) — relay tokens and namespace scope
- [Telemetry](/platform/telemetry) — metrics, traces, and record streams
