Skip to content

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.

Terminal window
reflex serve ./my-export/ --embodiment franka

Reads action_space.ranges, gripper.*, and constraints.* from the embodiment config. Clamps every action chunk to the declared per-dim ranges. Adds:

  • guard_clamped: true | false to /act responses
  • guard_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.

Terminal window
reflex serve ./my-export/ --embodiment franka --safety-config ./robot_limits.json

Loads 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.

{
"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:

Terminal window
reflex inspect guard --urdf ./franka_panda.urdf > ./robot_limits.json

Edit by hand to tighten any axis below URDF defaults if your deployment context demands it.

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.

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.).

  • Collision detection — that’s the runtime’s job, not the guard’s. Set embodiment.constraints.collision_check: true to 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.

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.