Guard Stack Explained¶
The guard stack is the heart of DAM. Four independent layers evaluate every proposed action from different angles. This document explains how each guard works, what they detect, and how to configure them.
First-Read Path¶
If you are learning DAM for the first time, read in this order:
- Guard Stack Flow
- Layer 1 Motion Safety
- Layer 2 Task Execution
- Layer 3 Hardware Monitoring
- Layer 0 OOD Detection
Use this page after you can already validate a Stackfile and identify pass, clamp, reject, and fallback decisions.
The Guard Stack Flow¶
Observation
↓
[ L0 — OOD Detection ] ← Is this state familiar?
↓ (if passes)
[ L1 — Physical Kinematics ] ← Are joint limits and motion constraints safe?
↓ (if passes)
[ L2 — Task Execution ] ← Does this command fit the current task phase?
↓ (if passes)
[ L3 — Hardware Monitor ] ← Is the hardware healthy?
↓
DECISION: Pass / Clamp / Reject
If rejected → Fallback Engine → Hold / Retreat / E-Stop
The most restrictive decision wins. If any layer says REJECT, the action is rejected. If multiple layers clamp, they are applied in order.
Layer 0: OOD Detection (Out-of-Distribution)¶
Responsibility: Detect when the robot enters an unfamiliar state.
The Problem¶
ML policies are trained on a distribution of data. When the robot encounters a state outside that distribution, the policy's output is unreliable.
Example: - Policy trained on arm configurations near a table (0.0–0.5m height) - Robot moves to 2.0m (unfamiliar state) - Policy still produces an output, but it's a hallucination
OOD Detection catches this and rejects the action.
How It Works¶
Method 1: Memory Bank (when trained)
from dam.guard.builtin.ood import OODGuard
# Training phase
guard = OODGuard()
guard.train(reference_observations) # List of Observation objects
guard.save("extractor.pt", "bank.npy")
At inference: 1. Extract a feature vector from the observation (128-dim L2-normalized) 2. Find the nearest neighbor in the trained memory bank 3. If distance > threshold → reject
Method 2: Welford Z-Score (fallback)
If no memory bank is trained:
1. Maintain running mean and variance of all observations
2. For each dimension, compute z-score: (x - mean) / std
3. If max_z > threshold → reject
4. Requires 30-cycle warm-up period
Configuration¶
guards:
- L0: ood
phase: 0
boundaries:
ood_detector:
layer: L0
type: single
nodes:
- callback: ood_detector
fallback: hold_position
params:
backend: welford
z_threshold: 3.0
When to Use¶
- ✅ Sim-to-real transfer (detects when real world looks different from simulation)
- ✅ Multi-environment deployment (detects when you move to a new room)
- ✅ Graceful degradation (rejects instead of guessing on unfamiliar states)
- ❌ NOT a substitute for good training data (train on diverse data first)
Behavior & Limitations¶
| Aspect | Status |
|---|---|
| Catches distribution shift | ✅ Usually |
| False positives | ⚠️ Possible (rejects valid states) |
| False negatives | ⚠️ Possible (misses subtle shifts) |
| Timing impact | Watch cycle latency on the target machine |
| User configuration required | ✅ Training phase optional but recommended |
Layer 1: Motion Safety (L1)¶
Responsibility: Enforce joint limits, velocity bounds, and workspace constraints.
Status: Implemented and ready for research and supervised development use.
This is the most important and mature layer. It prevents kinematic and dynamic violations.
Four Types of Constraints¶
1. Joint Position Limits¶
boundaries:
joint_position_limits:
layer: L1
type: single
nodes:
- callback: joint_position_limits
params:
upper: [1.57, 1.57, 1.57, 1.57, 1.57, 0.08]
lower: [-1.57, -1.57, -1.57, -1.57, -1.57, 0.0]
Behavior: Clamp proposed joint position to [lower, upper].
2. Velocity Limits¶
boundaries:
joint_velocity_limit:
layer: L1
type: single
nodes:
- callback: joint_velocity_limit
params:
max_velocities: [1.5, 1.5, 1.5, 1.5, 1.5, 0.5]
Behavior: If any joint velocity exceeds limit, scale all velocities by the same ratio.
# Example
proposed_velocities = [2.0, 0.5, 0.5] # rad/s
max_velocities = [1.0, 1.0, 1.0]
# Joint 0 violates: 2.0 > 1.0
# Scale factor: 1.0 / 2.0 = 0.5
executed_velocities = [1.0, 0.25, 0.25] # All scaled by 0.5
3. Acceleration Limits¶
boundaries:
joint_acceleration_limit:
layer: L1
type: single
nodes:
- callback: joint_acceleration_limit
fallback: hold_position
params:
max_accelerations: [3.0, 3.0, 3.0, 3.0, 3.0, 1.0]
Behavior: If implied acceleration would exceed limit, scale target velocity down.
# Example
current_velocity = [0.5, 0.5, 0.5]
proposed_velocity = [2.0, 2.0, 2.0] # 50 Hz, dt = 0.02s
implied_accel = (2.0 - 0.5) / 0.02 = 75 rad/s²
max_accel = [3.0, ...]
# Too fast! Reduce target velocity
target_velocity = current_velocity + (max_accel * dt)
= [0.5, ...] + [3.0 * 0.02, ...]
= [0.56, ...]
4. Workspace Bounds¶
boundaries:
workspace:
layer: L1
type: single
nodes:
- callback: workspace
params:
bounds: [[-0.5, 0.5], [-0.1, 0.6], [0.0, 1.5]]
Behavior: Compute end-effector position. If outside bounds → REJECT (cannot clamp without knowing which joints to move).
Configuration Template¶
guards:
- L1: motion
phase: 0
boundaries:
joint_position_limits:
layer: L1
type: single
nodes:
- callback: joint_position_limits
params:
upper: [1.57, 1.57, 1.57, 1.57, 1.57, 0.08]
lower: [-1.57, -1.57, -1.57, -1.57, -1.57, 0.0]
joint_velocity_limit:
layer: L1
type: single
nodes:
- callback: joint_velocity_limit
params:
max_velocities: [1.5, 1.5, 1.5, 1.5, 1.5, 0.5]
workspace:
layer: L1
type: single
nodes:
- callback: workspace
params:
bounds: [[-0.5, 0.5], [-0.1, 0.6], [0.0, 1.5]]
Decision Table¶
| Constraint | Action | Decision |
|---|---|---|
| Position exceeds limit | Clamp to limit | PASS (clamped) |
| Velocity exceeds max | Scale proportionally | PASS (clamped) |
| Acceleration exceeds max | Reduce target velocity | PASS (clamped) |
| End-effector outside workspace | Cannot fix | REJECT |
Expected Enforcement¶
| Aspect | Status |
|---|---|
| Joint limits | Enforced when configured with accurate limits |
| Velocity bounds | Enforced when configured with accurate limits |
| Acceleration bounds | Enforced when configured with accurate limits |
| Workspace bounds | Enforced when configured with a valid kinematic model |
| Collision-free | Not covered by the default guard stack; requires a dedicated simulation or collision checker |
| Timing health | Watch cycle latency in the console and configure watchdogs for the target hardware |
Layer 2: Task Execution (L2)¶
Responsibility: Enforce task-specific boundaries and constraints.
Boundaries define the safety envelope for a task phase. L2 checks if the proposed action respects the active boundary.
Example Boundary¶
boundaries:
pick_and_place:
layer: L2
type: list
nodes:
- callback: task_gripper_command_guard
params:
allowed_command: close
zone: [[-0.175, -0.025], [-0.075, 0.075], [0.075, 0.225]]
fallback: hold_position
- callback: task_gripper_command_guard
params:
allowed_command: none
fallback: hold_position
- callback: task_gripper_command_guard
params:
allowed_command: open
zone: [[0.025, 0.175], [-0.075, 0.075], [0.075, 0.225]]
fallback: hold_position
Common L2 Callbacks¶
| Callback | Behavior |
|---|---|
task_joint_speed_limit |
Reject if joint velocity norm exceeds task limit |
task_workspace_bounds |
Reject if end-effector leaves task workspace |
check_gripper_clear |
Reject if gripper is closed when it must be clear |
task_gripper_command_guard |
Clamp open/close commands that do not match the active task node or its zone by suppressing the gripper command |
Evaluation Order¶
- callback — active node's registered L2 boundary callback
- timeout_sec — node active time check
If any check fails, evaluation stops and decision is REJECT.
Custom Callbacks¶
Use built-in callbacks first. Add a custom callback only when the task needs a check that is not already covered by the callback catalog. See Boundary Callbacks for the current registration pattern and available built-ins.
Expected Behavior¶
| Aspect | Status |
|---|---|
| Boundary callbacks run | When the task activates the boundary and the Stackfile validates |
| Timeouts prevent indefinite phases | When timeout_sec is configured on the node |
| Task gripper command compatibility | task_gripper_command_guard can suppress incompatible gripper commands |
| Callback correctness | User's responsibility for custom callbacks |
Layer 3: Hardware Monitoring (L3)¶
Responsibility: Check hardware health and reject if faulted.
Health Status¶
L3 queries the hardware sink for health information:
@dataclass
class HealthStatus:
motors_ok: bool # Motor fault flags
temp_celsius: float # Motor temperature
watchdog_ok: bool # Watchdog responding
connected: bool # Hardware connected
error_message: Optional[str] # Last error
Example Sink Implementation¶
class MySink:
def health_check(self) -> HealthStatus:
motor_temps = self.read_motor_temps() # Query hardware
return HealthStatus(
motors_ok=all(not m.faulted for m in self.motors),
temp_celsius=max(motor_temps),
watchdog_ok=self.watchdog.is_alive(),
connected=self.is_connected(),
error_message=self.last_error,
)
Configuration¶
guards:
- L3: hardware
always: true
boundaries:
hardware_watchdog:
layer: L3
type: single
nodes:
- callback: hardware_watchdog
fallback: emergency_stop
params:
max_staleness_ms: 1000
Expected Behavior¶
| Aspect | Status |
|---|---|
| Hardware faults detected | When the adapter exposes health status |
| Temperature monitored | When temperature sources are configured |
| Watchdog enforced | When watchdog boundaries are active |
| Hardware damage prevention | Depends on sensor accuracy, thresholds, and independent stop procedures |
Guard Layering Strategy¶
Start with all guards enabled. Disable individual layers only after you have validated that their checks are not needed for your deployment.
guards:
- L0: ood
phase: 0
- L1: motion
phase: 0
- L2: execution
phase: 1
- L3: hardware
always: true
For simulation without hardware, set L3 to enabled: false. For production, keep all four active.
Performance¶
| Guard | Typical Latency | GPU Required |
|---|---|---|
| L0 OOD | 0.1–1.0 ms | Optional |
| L1 Motion | < 1 ms | No |
| L2 Task | < 1 ms | No |
| L3 Hardware | < 0.5 ms | No |
Total per-cycle: 1–5 ms at 50 Hz. See Common Stackfile Edits for full configuration examples.
Next Steps¶
- Boundary System -- defining safety envelopes
- Safety Model -- what DAM does and does not guarantee
- Stackfile Guide -- complete field reference