# The companion collector

URL: /collect/collector

Get each run's companion evidence without exporting a journal by hand. The collector reads an allowlist on the companion computer, uploads over mutual TLS, spools offline and never talks to the flight controller.



The flight log shows what PX4 did. The companion computer's journal and processes show what the autonomy stack around it was doing, and the collector brings both in after every segment. It is optional: Foxborne works from imported files alone.

## What it reads [#what-it-reads]

**Collector policy**, at the top right of the **Vehicle collectors** tab, opens a drawer that shows the policy in force under **What the collector reads**. In the example dataset it reads:

| Setting             | Value                                                                                               |
| ------------------- | --------------------------------------------------------------------------------------------------- |
| **Journal units**   | `perception.service`, `mavlink-router.service`, `chronyd.service`, `collector.service`, `kernel`    |
| **Processes**       | `perception_node`, `mavlink-routerd`                                                                |
| **Sample rate**     | 1 per second: CPU, resident memory, restart count                                                   |
| **Clock**           | Wall time, monotonic time, boot ID and chrony status on every sample. `SYSTEM_TIME` pairs from 0.8. |
| **Spool budget**    | 100 MB. When full, low-priority samples go first and a lost-data marker is written.                 |
| **Never collected** | Environment variables, process arguments, camera frames.                                            |

Only the listed units leave the vehicle, and everything else in the journal is ignored there. The `kernel` entry brings the kernel's own messages, such as an OOM kill or a USB disconnect. Admins change the lists, the rate, the budget and the redaction patterns in the [collector policy](/collect/collector/policy).

## What it cannot do [#what-it-cannot-do]

The same drawer's **What the collector cannot do** states four limits, and they hold on every vehicle:

* **Open a port.** It connects outbound only, over mutual TLS, and holds no command channel.
* **Talk to the flight controller.** It never sends MAVLink, and it cannot arm, move or reconfigure the vehicle.
* **Change the autonomy stack.** It reads the journal and `/proc`, nothing else.
* **Lose data quietly.** Offline samples are spooled, acknowledged before deletion, and any drop is written as a marker.

## How it connects [#how-it-connects]

Each collector connects outbound to your deployment over mutual TLS, with a certificate that belongs to its vehicle alone. It receives that certificate at enrollment, in exchange for a one-time token that expires 30 minutes after the console shows it. Nothing connects to the vehicle, and no port opens on it.

On the **Connections** tab, all collectors appear as one source, **Companion collectors**. Its drawer shows **Outbound only, mutual TLS** under **Access**, **Per-vehicle certificate** under **Credential** and **Batched upload after each segment** under **Schedule**.

The collector closes its data into segments and uploads each one as it closes. In the example dataset's INC-0143, its own journal lines on UGV-02 show a segment a minute. One closed while the radio was off the USB bus:

| Time, UTC    | The collector's journal line                                                             |
| ------------ | ---------------------------------------------------------------------------------------- |
| 15:40:10.004 | `segment 43 closed, 1.2 MB, uploaded`                                                    |
| 15:41:10.006 | `segment 44 closed, 1.1 MB, upload failed (network unreachable), spooled on the vehicle` |
| 15:42:10.003 | `segment 45 closed, 1.0 MB, uploaded with 1 spooled segment`                             |

## Offline spooling and the lost-data marker [#offline-spooling-and-the-lost-data-marker]

With no uplink, the collector keeps recording and holds its segments on the vehicle. Spooled data is acknowledged before the collector deletes it, and uploads resume on their own when the link returns.

The spool has a budget, 100 MB by default. When it fills, the collector drops the oldest low-priority samples first and writes a lost-data marker where they were. The gap then shows on the timeline instead of passing unnoticed.

| Example                | What the console shows                                                                                                                                                                                |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| UGV-05 behind the berm | **Buffering** with 18.4 MB spooled, and the note &#x2A;*Working behind the berm. No uplink for 12 minutes, spooling locally.**                                                                        |
| UAS-05 on 25 Sep       | **1,204** under **Dropped**, with the reason &#x2A;*Spool budget reached on 25 Sep. Oldest low-priority samples were dropped and marked.**                                                            |
| UGV-05 in INC-0137     | 31.2 MB spooled during a link loss. The timeline shows **collector: uplink restored, uploading 31.2 MB**, then **collector: upload complete, 38,904 records acknowledged**, and no samples were lost. |

Evidence that arrives late keeps its event time. The evidence inspector shows **Ingested** beside it, with how long after the event it arrived, so a reviewer sees both times.

## Redaction on the vehicle [#redaction-on-the-vehicle]

The collector redacts configured patterns before a line leaves the vehicle. The policy's **Redact from journal lines** holds regular expressions, by default `AKIA[0-9A-Z]{16}` and `Bearer [A-Za-z0-9._-]+`. Text that matches is replaced on the vehicle, before upload.

**Settings** keeps named **Patterns removed from logs**, such as **AWS access keys** and **Bearer tokens**. It states that they are **Applied on the vehicle before upload**. See [Retention, holds and redaction](/security/retention).

Environment variables, process arguments and camera frames are never collected at all.

## A clock on every sample [#a-clock-on-every-sample]

Each sample carries wall time, monotonic time, the boot ID and chrony's status. Process samples share the journal's clock and its bound, ±6 ms in run R-0931 of the example dataset. The run page lists them as **Companion monotonic, same boot as journal**, anchored by **Shared boot ID**.

The tab's **Clock** column shows chrony's view on each vehicle. It reads **Synced +0.8 ms** with the current offset, **Not synchronised** when chrony has no source or **Unknown**.

## The Vehicle collectors tab [#the-vehicle-collectors-tab]

A summary strip opens the tab. In the example dataset it reads **Enrolled** 15 of 16, **Online** 13, **Buffering offline** 1 and **Offline** 1. **Clock warnings** shows 1, and **Collector version** shows 0.8.1 with **2 on 0.7.3**.

The status bar at the bottom of every screen repeats the count as **13 of 15 collectors reporting**.

| Column          | What it shows                                                                                                                                     |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Vehicle**     | The vehicle ID, with the companion's host name under it, for example UGV-02 and `ugv02-agx`                                                       |
| **Collector**   | The installed version. Version 0.7.3 adds **No SYSTEM\_TIME**, and a vehicle without a collector shows **Not installed**.                         |
| **Clock**       | **Synced** with chrony's offset, **Not synchronised** or **Unknown**                                                                              |
| **Local spool** | **None**, or a bar with the megabytes held, for example **18.4 of 100 MB**                                                                        |
| **Last upload** | When the last segment arrived, or **Never**                                                                                                       |
| **Dropped**     | Samples discarded when the spool filled, with the reason on hover, or **0**                                                                       |
| **State**       | **Online**, **Buffering**, **Offline** or **Not enrolled**, with a note under the chip when there is one, such as **In maintenance since 24 Sep** |

Rows without a collector offer **Enroll**. Rows on an older version offer **Update**.

## Versions 0.8.1 and 0.7.3 [#versions-081-and-073]

| Version | `SYSTEM_TIME` pairs                                 | In the example dataset         |
| ------- | --------------------------------------------------- | ------------------------------ |
| 0.8.1   | Recorded                                            | 13 of the 15 enrolled vehicles |
| 0.7.3   | Not recorded, and the tab shows **No SYSTEM\_TIME** | UAS-07 and UGV-06              |

Version 0.8.1 records MAVLink `SYSTEM_TIME` pairs, which give companion evidence a shared anchor with the flight log. The difference shows when chrony fails. In the example dataset's INC-0139, UAS-07 ran 0.7.3 and chrony never synchronized, so its journal and samples stayed on their own clock.

That report could not say whether a perception crash came before or after the obstacle data stalled. An engineer can still [align the clock by hand](/investigate/align-a-clock), and [Update an older collector](/collect/collector/install#update-an-older-collector) moves a vehicle to 0.8.1.

## Next [#next]

<Cards>
  <Card title="Install and enroll the collector" href="/collect/collector/install" icon="terminal">
    Install the package and register a vehicle with a one-time token.
  </Card>

  <Card title="Set the collector policy" href="/collect/collector/policy" icon="sliders">
    Units, processes, sample rate, spool budget and redaction.
  </Card>

  <Card title="Collector JSONL records" href="/reference/collector-jsonl" icon="file">
    Every field of a process sample.
  </Card>

  <Card title="Align a clock by hand" href="/investigate/align-a-clock" icon="clock">
    Anchor a companion clock that chrony never synchronized.
  </Card>
</Cards>
