guard — safety + audit
ActionGuard is the safety layer between the policy’s output and the response leaving /act. It engages automatically when --embodiment is set, and adds URDF-derived joint limits + an audit log when --safety-config is provided.
Two engagement levels
Section titled “Two engagement levels”Level 1: embodiment-only
Section titled “Level 1: embodiment-only”reflex serve ./my-export/ --embodiment frankaReads action_space.ranges, gripper.*, and constraints.* from the embodiment config. Clamps every action chunk to the declared per-dim ranges. Adds:
guard_clamped: true | falseto/actresponsesguard_violations: [...]listing per-dim which axes were clamped- Counters:
reflex_guard_clamped_total,reflex_guard_violation_total{dim}
This is the minimum viable deployment posture. Don’t ship to a real robot without at least this.
Level 2: safety config + audit log
Section titled “Level 2: safety config + audit log”reflex serve ./my-export/ --embodiment franka --safety-config ./robot_limits.jsonLoads URDF-derived joint limits, velocity/acceleration caps, and torque envelopes from the safety config. Layered on top of embodiment ranges (the tighter of the two wins per axis). Every clamp event lands in a structured audit log suitable for EU AI Act compliance.
Safety config schema
Section titled “Safety config schema”{ "schema_version": 1, "joint_limits": [ {"name": "joint_1", "lower": -2.8973, "upper": 2.8973}, {"name": "joint_2", "lower": -1.7628, "upper": 1.7628}, /* ... */ ], "velocity_caps": { "max_joint_velocity_rad_s": 2.5, "max_ee_linear_velocity_m_s": 0.5, "max_ee_angular_velocity_rad_s": 1.0 }, "acceleration_caps": { "max_joint_acceleration_rad_s2": 5.0 }, "torque_caps": { "max_joint_torque_nm": [87, 87, 87, 87, 12, 12, 12] }, "audit_log_path": "/var/log/reflex/audit.jsonl"}Generate the schema from a URDF:
reflex inspect guard --urdf ./franka_panda.urdf > ./robot_limits.jsonEdit by hand to tighten any axis below URDF defaults if your deployment context demands it.
Audit log format
Section titled “Audit log format”One JSONL line per /act call when guard fired:
{"ts":"2026-05-01T09:32:14.123Z","episode_id":"ep_xyz","seq":42,"violations":[{"dim":2,"requested":3.1,"clamped":2.8973,"reason":"joint_2_upper_limit"}],"action_chunk_id":"chunk_42"}Designed for grep | jq analysis after a robot incident. Stable schema; additive fields don’t bump version.
Per-violation telemetry
Section titled “Per-violation telemetry”Every /act response surfaces the full violation set when guard fired:
{ "actions": [...], "guard_clamped": true, "guard_violations": [ {"dim": 2, "requested": 3.1, "clamped": 2.8973, "reason": "joint_2_upper_limit"}, {"dim": 6, "requested": 1.5, "clamped": 1.0, "reason": "max_ee_angular_velocity"} ]}Clients can react to specific violations (slow the trajectory, request human approval, log to incident tracker, etc.).
What guard doesn’t do
Section titled “What guard doesn’t do”- Collision detection — that’s the runtime’s job, not the guard’s. Set
embodiment.constraints.collision_check: trueto engage the collision checker (Phase 1.5). - Goal-state validation — guard clamps inputs, doesn’t verify outcomes.
- Hardware fault detection — sensor errors, motor faults, etc. are the robot controller’s responsibility downstream of
/act.
Guard is the last software layer before actions leave Reflex. Layers below — robot controller, motor drivers, physical limit switches — each provide additional safety. Don’t rely on guard alone for life-safety scenarios.
Failure modes
Section titled “Failure modes”If the safety config fails to load (missing file, schema mismatch), the server refuses to start. No silent fallback to embodiment-only mode. The whole point of an explicit --safety-config is that the operator wanted the additional layer; falling through to a more-permissive posture would defeat the contract.
If the URDF and embodiment ranges disagree, guard always picks the tighter limit per axis. Diff is reported in the startup log.