Flagship Proof — ROS 2 Actuator Policy

9.0 m/s in. 2.0 m/s on the wire.

A 9.0 m/s velocity command sent against a 2.0 m/s policy was delivered to the wire as 2.0 m/s — and zeroed under emergency stop. This page is the evidence: verbatim transcripts from the validation session, and exactly where the boundary of that evidence sits.

2026-07-26Raspberry Pi 5Ubuntu 26.04ROS 2 Lyrical Luthagents/ros-agent
Why This Matters

Text-level controls end where motion begins.

On a ROS 2 robot, a planner's decision ultimately becomes a geometry_msgs/Twist message on a command topic. Every AI-security control that inspects prompts, tool calls, or model output has finished its work before that message exists. Whether the plan was compromised, hallucinated, or simply buggy, the command topic is where it turns into motion.

So that is where we put the control. In interpose mode the agent sits between the planner and the motor driver: it subscribes the raw command topic, evaluates every message against speed, acceleration, rate, geofence, and safety-zone policy, and republishes the sanitised command on the topic the hardware listens to. What follows is not a score or a dashboard — it is the stream of messages a motor driver would have received.

The Run

One publisher. One policy. Four transcripts.

A 5 Hz test publisher sent geometry_msgs/Twist messages with linear.x = 9.0 m/s to /cmd_vel_raw. The agent ran in interpose mode with a 2.0 m/s linear limit, republishing the sanitised command on /cmd_vel — the topic a motor driver would subscribe to.

Setup — the interpose pipeline

[test publisher, 5 Hz, linear.x = 9.0]
        │
        ▼
  /cmd_vel_raw ──▶ actuator_policy (interpose, linear limit 2.0 m/s) ──▶ /cmd_vel
                                                                            │
                                                                            ▼
                                                          [motor driver listens here]

01 — The wire

Start with the only measurement that matters: what hardware would receive. This is ros2 topic echo on /cmd_vel, the output topic, while the 9.0 m/s commands were arriving at 5 Hz.

ros2 topic echo /cmd_vel

$ ros2 topic echo /cmd_vel
linear:
  x: 2.0
---
linear:
  x: 2.0
---
linear:
  x: 2.0
---

Excerpt. Every message on /cmd_vel during the clamp run carried linear.x = 2.0 — 9.0 in, 2.0 out, at the publisher's full rate. The 9.0 m/s command never appeared on the output topic.

02 — The agent’s account

The agent reported what it was doing — throttled into a bounded incident rather than a 5 Hz log flood.

Agent event stream

[WARNING] actuator_policy: Actuator command clamp: Linear velocity 9.00 exceeds limit 2.0 (+50 similar suppressed); ongoing, next report in up to 60s
[WARNING] actuator_policy: Actuator command clamp: Linear velocity 9.00 exceeds limit 2.0 (+299 similar suppressed); ongoing, next report in up to 60s

Suppressed repeats are counted, never discarded: +50, then +299, with the next report bounded at 60 seconds.

03 — Emergency stop

Mid-run, an emergency stop was asserted on /cyberarmor/emergency_stop. From that message on, the verdict changed from clamp to zero — for every command, at any magnitude.

Agent event stream

EMERGENCY STOP activated: asserted on emergency-stop topic
[CRITICAL] actuator_policy: Emergency stop asserted -- all actuator commands will be zeroed
[CRITICAL] actuator_policy: Actuator command emergency_stop: Emergency stop is active (+5 similar suppressed); ongoing, next report in up to 10s

ros2 topic echo /cmd_vel — during e-stop assertion

linear:
  x: 2.0
---
linear:
  x: 0.0
---
linear:
  x: 0.0
---

On the wire, /cmd_vel flipped from x: 2.0 to x: 0.0 and held at zero for every subsequent message until the stop was released. The e-stop latches: no command of any magnitude passes until an operator explicitly releases it.

04 — Incident close

When the e-stop took over and clamp verdicts stopped, the incident model closed the clamp incident with a full count — not an open alert, not a lost tally.

Agent event stream

[INFO] actuator_policy: Actuator base no longer producing clamp verdicts after 844 commands over 169s

One bounded incident: 844 clamped commands over 169 seconds — opened, throttled, counted, and closed with a recovery event.

Design Decisions

Built for the failure modes robots actually have.

Zero, don't drop

A rejected or e-stopped command publishes an explicit zero instead of silently dropping the message. Most motor drivers latch the last velocity they received until a watchdog expires — silence keeps a robot moving.

No feedback loop by construction

Interpose refuses any configuration where the output topic equals the input topic. The agent cannot be configured to re-ingest its own sanitised output.

Observe mode is the default

Installing the agent cannot change how a robot moves. Enforcement on the command topic is a deliberate, per-robot operator opt-in.

Incidents, not log floods

Repeat alerts back off 1 s → 10 s → 60 s. Suppressed repeats are counted, never discarded, and every incident closes with a recovery event carrying the full tally.

The e-stop latches

Once asserted, every subsequent command is zeroed regardless of magnitude until the stop is explicitly released. There is no timeout and no auto-release.

Honest Limits

What this run did — and did not — prove.

The same status discipline as /status: we state exactly where the boundary of the evidence sits.

  • Validated on a bench Raspberry Pi 5 with a CLI publisher and subscriber — there was no physical motor driver downstream.
  • The geofence policy was evaluated with synthetic /odom data.
  • No adversarial DDS participant was involved beyond standard CLI tools.
  • Lidar and chassis integration have not yet been exercised end-to-end.

Verify it yourself.

The fastest way to evaluate CyberArmor.AI is to run it. The local PoC brings the platform up on your own laptop in 15 minutes.

Discuss a robotics pilot See the full capability status