# Teleop session log

> The per-session record behind the robotics session log: fields, state and fault columns, command counters, epoch attribution, and CSV export.

The session log is the teleop equivalent of a Call Detail Record. Where
the voice gateway writes one CDR row per call, ClutchCall writes one
**session record** per operator attach to a robot. Every attach produces a
row, whether it ended cleanly, was taken over, or faulted out.

The screen loads the whole set through a single read (`sessionLog()`) and
renders it as a filterable table. It performs no mutations: nothing on
this screen starts, stops, or re-attributes a session. It is a record of
what already happened, plus the sessions that are still open.

## What a session record contains

Each row is one session. These are the fields the log carries and the
column each one renders into:

| Field           | Column       | Notes                                                                 |
| --------------- | ------------ | --------------------------------------------------------------------- |
| `id`            | Session      | The session identifier. The table shows a shortened form; the full value is on the cell's tooltip. |
| `operatorName`  | Operator     | The human who held control for this session.                          |
| `region`        | Operator     | Rendered after the operator name, as a caption. Where the operator connected from, not where the robot is. |
| `robot`         | Robot        | The robot this session was attached to.                               |
| `startedAt`     | Duration     | The attach time. See below — the column is derived, not stored.        |
| `state`         | State        | Session lifecycle. Rendered as a state pill.                          |
| `fault`         | Fault        | How the session ended, or its current fault condition.                |
| `counters.accepted`   | Accepted   | Commands the robot accepted.                                     |
| `counters.superseded` | Superseded | Commands displaced by a newer command.                           |
| `counters.badMac`     | Bad MAC    | Commands rejected on message-authentication failure.             |
| `epoch`         | Epoch        | The control-authority generation the session's commands were stamped with. |

Three of these columns are display transforms rather than stored values,
which matters if you are reconciling the screen against exported data:

- **Session** is truncated for width. The record holds the full id.
- **Duration** is computed from `startedAt` as time elapsed. For a session
  that is still open this is the live elapsed time; the record itself
  stores only the start instant.
- **Fault** and **Bad MAC** are tinted. The fault colour comes from a
  shared tone function keyed on the fault value, and the bad-MAC count is
  tinted only when it is non-zero. The colour carries no information that
  the text does not.

## Session states and terminal faults

State and fault are two different columns and answer two different
questions. **State** is where the session sits in its lifecycle — it
distinguishes a session that is live and holding control from one that
has finished. Live and past sessions share the table, so state is how you
tell them apart, and it is the field the state pill renders.

**Fault** is why the session is in that state. It is populated on every
row, not only on failures: a session that ran to completion and detached
normally still reports a fault value, and the tone function renders that
value differently from a genuine fault so the two do not read alike at a
glance. When a session is terminal, the fault is the terminal fault — the
last condition observed before control was released, and the single field
you want when someone asks why a robot stopped taking commands.

A fault on a row does not by itself say whether the operator, the link, or
the robot was at fault. Pair it with the counters in the same row.

## Command counters: accepted, superseded, and bad MAC

Every command sent inside a session lands in exactly one of three
counters. Reading them together tells you more than any one alone.

**Accepted** counts commands the robot took and acted on. This is the
session's useful work, and it is the only counter the table formats with
thousands separators — it is normally orders of magnitude larger than the
other two, because a teleop session streams commands continuously rather
than sending discrete requests.

**Superseded** counts commands that were displaced by a newer command
before they took effect. In a continuous control stream this is expected
and not an error: when a fresher setpoint arrives, the stale one is
dropped rather than queued, because executing a stale setpoint is worse
than skipping it. A small superseded count alongside a large accepted
count is the normal shape of a healthy session. A superseded count that
grows toward the accepted count is the signal worth chasing — it means
commands are arriving faster than they are being consumed, or arriving
out of order.

**Bad MAC** counts commands rejected because their message authentication
code did not verify. These are not dropped for timing; they are refused
because the robot could not prove the command came from the holder of the
session's key. This counter is the reason the column is tinted: on a
correctly configured session it stays at zero, and any non-zero value
means commands reached the robot carrying signatures it could not
validate. Investigate a non-zero bad-MAC count as a security or
key-distribution question, not a performance one.

## Epochs and attribution

An **epoch** is the control-authority generation in force for a session.
It is the value commands are stamped with, and it is what makes a command
attributable to one specific grant of control rather than merely to a
robot and a moment in time.

Authority changes hands. An operator detaches and another attaches; a
session is taken over; a robot is re-attached after a fault. Each of those
produces a new session record, and the epoch on the new row differs from
the epoch on the old one. That is the purpose of putting the column on
this screen: two rows for the same robot, adjacent in time, are
unambiguously separate authority periods rather than one continuous one,
and a command carrying an epoch can be matched back to exactly one row in
this table.

The log records the epoch that was in force. It does not assign or roll
epochs — nothing on this screen changes the authority of a session.

Together, `operatorName`, `region`, and `epoch` are the attribution triple
for a session: who held control, where they connected from, and under
which grant of authority. Every attach is recorded with all three, which
is what makes the log usable as evidence that a given robot motion is
traceable to a named operator.

## Exporting the log

The screen offers a CSV export of the session log. Export carries the
session record, so the exported data is the field set in the table above —
including the values the table only shows in transformed form. Expect the
full `id` rather than the shortened display form, and `startedAt` as a
timestamp rather than the elapsed-time string the Duration column renders.
The colour applied to the fault and bad-MAC columns is a display concern
and has no representation in the export.

Export is a read of the same data the table shows. It does not archive,
expire, or remove records, and this screen exposes no retention controls —
how long session records are kept is configured outside it.

## Related

- [Telemetry](/platform/telemetry) — the voice-side CDR schema this record
  parallels
- [Authentication](/concepts/authentication) — relay tokens and namespace
  scope, which govern which robotics topics a session may command
- [Telephony metrics](/glossary/metrics) — metric definitions for the
  voice modality
