Advanced Metal Research
GitHub Contact AMR

NATS subjects and streams

On this page
  1. Subject map
  2. Observation: rt-natspublisher
  3. Streams
  4. Status payload
  5. Binary telemetry
  6. Commands: the robot command subject
  7. Related pages

RosieOS uses NATS for two different jobs:

  • Observation. rt-natspublisher copies rt-control status and telemetry onto NATS for displays and archives. These subjects carry no command authority. Nothing that reads them can move the robot.
  • Motion-server commands. The Cartesian motion server and the dense trajectory daemon take their leader lease and their commands from a per-cell robot command subject.

rt-control itself never reads NATS. Its authority is the lease on its own socket. See Control authority.

Warning

The command subjects have no authentication. The motion servers' leader lease is cooperative fencing between well-behaved controllers on a trusted network; anyone who can publish on the subject can send a stop, or try to take the lease. Keep the cell's NATS server on a private network. See the safety model.

Tip

Machine-readable. The subject map, streams and command tables below as JSON.

Subject map#

<host>, <peer> and <cell> are deployment names, not fixed strings. <host> is passed through a token filter: every run of characters outside A-Z a-z 0-9 - _ becomes one -.

SubjectPublisherSubscriberPayloadAuthority
robot/v4/rtcore.<host>.statusrt-natspublisherDisplays, archivesJSON robot.v4.rtcore.status.v1None
robot/v4/rtcore.<host>.telemetry.batchrt-natspublisherDisplays, archivesJSON robot.v4.rtcore.telemetry.batch.v1None
rosie/rt-core.<host>.telemetry.v1rt-natspublisherArchivesBinary TelemetryBatchNone
robot/v4/robot.<cell>.commandControllersCartesian motion server, dense trajectory daemonJSON robot.v4.robot-command.v1Leader lease commands and motion commands
NATS reply subject of each commandMotion serversThe senderJSON robot.v4.command-reply.v1None
robot/v4/motion-server.<peer>.statusCartesian motion serverControllers, displaysJSON robot_v4_motion_server_cartesian_live_status_v1None
robot/v4/motion-server.<peer>.planCartesian motion server (self-test plan paths)—JSONNone
rt_core.status_subject, default <command subject>.statusDense trajectory daemonControllersJSON executor statusNone
--status-publish-subjectDense trajectory daemonControllersJSON ingest and executor statusNone

The exact command and status subjects come from command-line flags or from a deployment manifest (robot_command_subject and each node's cold_path_subjects). The Cartesian motion server only accepts subjects that name its concrete peer: robot/v4/motion-server.<peer>.… or robot/v4/robot.<peer>.…. The dev stack uses robot/v4/robot.dev-cell.command and robot/v4/robot.dev-cell.status for the dense daemon.

Observation: rt-natspublisher#

rt-natspublisher reads the public rt-control socket, never the core's private IPC, and holds no grant.

rt-natspublisher --socket /run/rosie-rt-core/public/control.sock \
  --nats-url nats://127.0.0.1:4222 --host cell-a --cadence 500ms
FlagEnvironmentDefaultDescription
--socket PATH—/run/rosie-rt-core/public/control.sockThe public rt-control socket
--nats-url URLROSIE_RT_NATS_URLnonenats://host:port. Credentials, TLS and paths in the URL are refused.
--host NAMEROSIE_RT_NATS_HOSTnoneThe deployment's identity in the subject. Must be concrete: not empty, not unknown, no @ / ? # = , \ " ' * > or whitespace.
--cadence DROSIE_RT_NATS_CADENCE500msStatus and telemetry-batch interval, whole milliseconds, 1 ms to 5 s
--telemetry-cadence DROSIE_RT_NATS_TELEMETRY_CADENCEthe status cadenceBinary telemetry polling interval
--telemetry-max-bytes NROSIE_RT_NATS_TELEMETRY_MAX_BYTES536870912 (512 MiB)Byte retention of the binary telemetry stream
--print-env——Validate the settings and print them as a systemd environment file

The installed unit rosie-rt-natspublisher.service reads /etc/rosie-rt-core/natspublisher.env.

Before publishing, the publisher subscribes to its own status subject for one cadence. If another publisher is already live on that identity, it refuses to start (another publisher is active on the deployment status subject).

Streams#

The publisher creates or checks both JetStream streams on connect:

StreamSubjectsRetentionLimits
ROBOT_V4_RTCORErobot/v4/rtcore.>limits, file storage, discard old250,000 messages, 256 MiB, 10 minutes
ROSIE_RT_CORE_TELEMETRYrosie/rt-core.*.telemetry.v1limits, file storage, discard oldno message or age limit; --telemetry-max-bytes

At a 1 kHz cycle, one 100 s telemetry ring window is about 539 MB, so the default 512 MiB cap can evict records before a full window is kept.

Status payload#

{
  "schema": "robot.v4.rtcore.status.v1",
  "stream": "ROBOT_V4_RTCORE",
  "subject": "robot/v4/rtcore.cell-a.status",
  "host": "cell-a",
  "component": "rtcore",
  "published_at": "2026-09-29T12:00:00.000Z",
  "publisher_state": "publishing", "last_error": "",
  "reconnects": 0, "drops": 0, "dropped": 0,
  "status": {
    "name": "robot-v4-rtcore", "state": "running",
    "rtcore_mode": "sim", "simulation_mode": true, "ethercat_live": false,
    "servos_armed_requested": false, "home_valid_axis_mask": 511,
    "axis_enable_mask": 0, "configured_axis_count": 9,
    "safe_min_rad": [ … ], "safe_max_rad": [ … ],
    "rt_core": { "core": { … }, "motion": { … }, "execution": { … }, "axes": [ … ],
                 "configuration_sha256": "…", "machine_sha256": "…" }
  }
}

status is null until the publisher has a valid core sample. rt_core holds the public rt-control status and the deployment digests; the flat fields beside it keep the shape an earlier consumer expected, and fields that rt-control cannot supply are null. For the meaning of the rt_core fields, see the rt-control status.

The telemetry batch message (robot.v4.rtcore.telemetry.batch.v1) carries one sampled record per cadence in records, with sequence, source_monotonic_ms and interval_ms. For every cycle, use the binary stream instead.

Binary telemetry#

rosie/rt-core.<host>.telemetry.v1 carries rt-control's binary TelemetryBatch exactly: a 312-byte header and 5,392-byte cycle records, split to fit the server's max_payload. The publisher keeps its own cursor and advances it only past acknowledged records. If rt-control or the core restarts, it stops with an error rather than join two incarnations. See Events and telemetry streams.

Commands: the robot command subject#

Both motion servers subscribe to the cell's robot command subject and share its envelope, robot.v4.robot-command.v1, with schema, command, robot, command_id and sender_id. Each ignores the commands the other owns. Their lease fields differ:

Cartesian motion serverDense trajectory daemon
Lease commandsleader_acquire, leader_renew, leader_release, leader_cancelleader_acquire, leader_renew, leader_release
Lease request fieldscontroller_boot_id, request_id, campaign_generation, ttl_ms, lease_id, fence_epochcontroller_boot_id, ttl_ms, lease_id, leader_fence_epoch
Motion credential fieldscontroller_boot_id, leader_lease_id, leader_fence_epochthe same, plus motion_intent_seq
Allowed sendersRoles in the manifest's leader_controller_roles (default steamdeck)offline-programming:<instance> only
TTL500–2000 msleader.ttl_min_ms–leader.ttl_max_ms (500–2000 ms by default)
Commandsarm, disarm, home, go_home, position, stop, end_runplay, pause, go_home, stop

stop needs no lease on either server. Full field tables are on each server's page: Cartesian motion server and Dense trajectory daemon.