Browse the documentation

AEL 1.0.0

Robot safety and replay

With AEL, the Agent Engineering Language, safety stays deterministic: AI proposes, validators check freshness and limits, and the watchdog and emergency stop stay in charge. This tutorial writes the validator of a robot agent, runs the agent against a simulator first, and records and replays a run to find where its behaviour changed.

Step 1: add the robotics packages

Optional robotics packages provide typed units, coordinate frames, timestamps and sensor and actuator schemas. Add the ones you use:

ael pack add robotics/transforms
ael pack add robotics/simulation
ael pack add robotics/middleware/ros2

Robot agents connect over ROS 2 and DDS, including the DDS-XRCE standard for constrained hardware. Each package version states the message types, settings and transports it covers.

Step 2: let the model propose

A model-driven node plans through typed proposal tools, and its output is a proposal, never a command:

# agents/robot.agent.ael
agent robot(input: Observation) -> RobotStatus {
    config {
        nodes: [planner = plan_motion, guard = check_command];
        edges: [plan_to_guard(planner, guard)];
        starts: [planner];
        completion: guard.result;
    }
}

plan_motion is the model-driven node; check_command is deterministic and calls no model.

Step 3: write the validator

The guard checks every proposal before any command is admitted. Its rules are plain code, not prompt text:

struct Proposal {
    speed_mm_s: u32,
    age_ms: u32,
    sequence: u32,
}

enum Verdict {
    Admit(u32),
    TooFast(u32),
    Stale(u32),
    OutOfOrder(u32),
}

fn validate(proposal: Proposal, last_sequence: u32, max_speed: u32, max_age: u32) -> Verdict {
    if proposal.sequence <= last_sequence {
        return Verdict::OutOfOrder(proposal.sequence);
    }
    if proposal.age_ms > max_age {
        return Verdict::Stale(proposal.age_ms);
    }
    if proposal.speed_mm_s > max_speed {
        return Verdict::TooFast(proposal.speed_mm_s);
    }
    return Verdict::Admit(proposal.speed_mm_s);
}
  • A rejected proposal leads to the local behaviour you declared, such as holding position or a safe state.
  • Units and frames are part of the type in the robotics packages, so a pose in one frame does not combine with a pose in another without an explicit conversion.
  • The watchdog and the emergency stop do not depend on the model, the network or a parent agent, and no model output can disable a stop path or widen what an actuator may do.

Step 4: run against a simulator first

The same code runs against virtual hardware, a simulator or a real robot. Run the agent against the simulator until its behaviour is what you want. A simulation or a replay never gains access to a physical actuator by accident: moving real hardware needs its own permission.

Step 5: record and replay

You record control and orchestration runs and replay them to find the first point where behaviour diverged:

  1. Record a run: its inputs, timing, scheduling choices, random seeds and the responses of models, tools and services, with the identity of the build, prompts and configuration.
  2. Change the prompt or the code.
  3. Replay the recording. The recorded responses are fed back in, and no live call or real side effect happens.
  4. Read the divergence report: the first event that differs, the source and prompt involved, and the expected and observed typed values.

An exact replay needs the same build and target; replaying across a changed build is a separate, best-effort mode, and says so. Reporting, logs and replay describes recordings in full.

Honest limits

  • None of this is a safety certification. Testing on physical hardware needs your own test rig, interlocks, operator control and a verified stop path, and regulated uses need their own qualification.
  • A cancelled run, a lost connection or a model's reply is never taken as proof that a robot stopped.
  • Native code alone does not make a control loop hard real-time, and replay does not make a live model's output reproducible.