NATS subjects and streams
On this page
RosieOS uses NATS for two different jobs:
- Observation.
rt-natspublishercopiesrt-controlstatus 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 -.
| Subject | Publisher | Subscriber | Payload | Authority |
|---|---|---|---|---|
robot/v4/rtcore.<host>.status | rt-natspublisher | Displays, archives | JSON robot.v4.rtcore.status.v1 | None |
robot/v4/rtcore.<host>.telemetry.batch | rt-natspublisher | Displays, archives | JSON robot.v4.rtcore.telemetry.batch.v1 | None |
rosie/rt-core.<host>.telemetry.v1 | rt-natspublisher | Archives | Binary TelemetryBatch | None |
robot/v4/robot.<cell>.command | Controllers | Cartesian motion server, dense trajectory daemon | JSON robot.v4.robot-command.v1 | Leader lease commands and motion commands |
| NATS reply subject of each command | Motion servers | The sender | JSON robot.v4.command-reply.v1 | None |
robot/v4/motion-server.<peer>.status | Cartesian motion server | Controllers, displays | JSON robot_v4_motion_server_cartesian_live_status_v1 | None |
robot/v4/motion-server.<peer>.plan | Cartesian motion server (self-test plan paths) | — | JSON | None |
rt_core.status_subject, default <command subject>.status | Dense trajectory daemon | Controllers | JSON executor status | None |
--status-publish-subject | Dense trajectory daemon | Controllers | JSON ingest and executor status | None |
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| Flag | Environment | Default | Description |
|---|---|---|---|
--socket PATH | — | /run/rosie-rt-core/public/control.sock | The public rt-control socket |
--nats-url URL | ROSIE_RT_NATS_URL | none | nats://host:port. Credentials, TLS and paths in the URL are refused. |
--host NAME | ROSIE_RT_NATS_HOST | none | The deployment's identity in the subject. Must be concrete: not empty, not unknown, no @ / ? # = , \ " ' * > or whitespace. |
--cadence D | ROSIE_RT_NATS_CADENCE | 500ms | Status and telemetry-batch interval, whole milliseconds, 1 ms to 5 s |
--telemetry-cadence D | ROSIE_RT_NATS_TELEMETRY_CADENCE | the status cadence | Binary telemetry polling interval |
--telemetry-max-bytes N | ROSIE_RT_NATS_TELEMETRY_MAX_BYTES | 536870912 (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:
| Stream | Subjects | Retention | Limits |
|---|---|---|---|
ROBOT_V4_RTCORE | robot/v4/rtcore.> | limits, file storage, discard old | 250,000 messages, 256 MiB, 10 minutes |
ROSIE_RT_CORE_TELEMETRY | rosie/rt-core.*.telemetry.v1 | limits, file storage, discard old | no 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 server | Dense trajectory daemon | |
|---|---|---|
| Lease commands | leader_acquire, leader_renew, leader_release, leader_cancel | leader_acquire, leader_renew, leader_release |
| Lease request fields | controller_boot_id, request_id, campaign_generation, ttl_ms, lease_id, fence_epoch | controller_boot_id, ttl_ms, lease_id, leader_fence_epoch |
| Motion credential fields | controller_boot_id, leader_lease_id, leader_fence_epoch | the same, plus motion_intent_seq |
| Allowed senders | Roles in the manifest's leader_controller_roles (default steamdeck) | offline-programming:<instance> only |
| TTL | 500–2000 ms | leader.ttl_min_ms–leader.ttl_max_ms (500–2000 ms by default) |
| Commands | arm, disarm, home, go_home, position, stop, end_run | play, 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.