Skip to content

guides

Intended Documentation

PLC Migration Patterns

Common safety-PLC patterns (Siemens F-CPU, GuardLogix, TwinCAT Safety, Pilz) translated to Intended policy clauses.

Migration Guide from Common Safety-PLC Patterns (DOC-P6)#

Audience: controls engineers familiar with safety-PLC programming (Beckhoff TwinCAT 3 Safety, Siemens F-CPU / SCL, Rockwell GuardLogix / AOIs, Pilz PNOZmulti) who are layering Intended on top of their existing safety stack.

Position: Intended does not replace your safety PLC. It adds a layer of intent-aware authority gating on top. Your e-stop chain, light curtains, and safety-rated I/O stay exactly where they are. What changes is how AI-generated or operator-issued commands get classified, authorized, and audited before they reach motion.

Layering, not replacing#

                Operator HMI / autonomous planner / AI agent
                            │
                            ▼
                   ┌──────────────────┐
                   │  Intended layer  │   ← classify intent, issue token
                   │ (this is new)    │
                   └────────┬─────────┘
                            │ token + safe-default
                            ▼
   ┌────────────────────────────────────────────────────┐
   │  Existing safety PLC + bus (unchanged)             │
   │  e-stop chain, light curtains, presence scanners   │
   └────────────────────────────────────────────────────┘
                            │
                            ▼
                       motion controller
                            │
                            ▼
                          motors

The safety PLC continues to be the authority on physical safety hazards. Intended is the authority on whether the command should be issued in the first place.

Pattern translations#

Below: common safety-PLC patterns and how they map to Intended policy clauses. The PLC pattern stays in place; the Intended clause adds an intent-aware layer above it.

Pattern 1 — Latched e-stop interlock#

Safety-PLC code (Siemens F-SCL):

scl
IF estop_button_pressed THEN
    motion_enable := FALSE;
    motion_latch  := TRUE;
END_IF;

IF reset_button AND NOT estop_button_pressed THEN
    motion_latch := FALSE;
    motion_enable := TRUE;
END_IF;

Intended policy clause (policy.json):

json
{
  "id": "deny-on-emergency-stop",
  "effect": "deny",
  "applies_to": { "oil_categories": ["OIL-1500", "OIL-1800"] },
  "predicate": {
    "type": "boolean",
    "predicate": "safety/emergency_stop",
    "equals": true
  },
  "safe_default": "stop"
}

What the layer adds: the safety PLC stops the motors when e-stop fires. Intended additionally denies issuance of new motion tokens while e-stop is latched. This means an LLM or autonomous planner cannot issue a new command into the queue during the latched state — the denial is logged in the audit chain with the structured goal that was attempted, the actor identity, and the OIL category.

That extra logging is what lets you ask "did the agent keep trying to move during the latched e-stop?" — a question the safety PLC alone cannot answer.

Pattern 2 — Light-curtain mute / re-arm#

Safety-PLC pattern: light curtain breached → mute the motion zone until cleared and operator-acknowledged.

Intended layer: add a clause that denies authority on the same predicate, plus an escalation clause for restart commands.

json
{
  "id": "deny-on-curtain-breach",
  "effect": "deny",
  "predicate": { "type": "boolean", "predicate": "safety/light_curtain_breached", "equals": true },
  "safe_default": "stop"
},
{
  "id": "escalate-restart-after-breach",
  "effect": "escalate",
  "applies_to": { "verbs": ["resume-motion", "clear-fault"] },
  "predicate": {
    "type": "compound",
    "operator": "all",
    "predicates": [
      { "type": "boolean", "predicate": "safety/light_curtain_breached", "equals": false },
      { "type": "boolean", "predicate": "safety/curtain_recently_breached", "equals": true }
    ]
  },
  "safe_default": "request-operator"
}

The second clause causes any "resume" command after a recent breach to escalate to a human operator approval, independent of whether the PLC mute is cleared. This catches the pattern where a partially-corrupted agent issues a resume-motion command before the operator has visually confirmed the cell is clear.

Pattern 3 — Speed/separation monitoring (cobot, ISO/TS 15066)#

Safety-PLC pattern: safety-rated scanner reports distance to nearest human; PLC reduces TCP speed proportionally.

Intended layer: require speed-zone classification on each motion goal.

json
{
  "id": "deny-fast-motion-when-human-near",
  "effect": "deny",
  "applies_to": { "oil_categories": ["OIL-1503"] },
  "predicate": {
    "type": "compound",
    "operator": "all",
    "predicates": [
      { "type": "boolean", "predicate": "workspace/human_within_2m", "equals": true },
      { "type": "number", "predicate": "command/tcp_speed_mps", "greater_than": 0.25 }
    ]
  },
  "safe_default": "hold-position"
}

This denies a high-speed motion goal while a human is near, even if the PLC's speed override would clamp the actual TCP speed to a safe value. The denial gives you an audit record of the agent attempting unsafe-zone motion — useful when calibrating your planner.

Pattern 4 — Two-hand control / dual confirmation#

Safety-PLC pattern: require two operator buttons pressed together within a time window to enable hazardous motion.

Intended layer: N-of-M consensus operator (ECE-P4):

json
{
  "id": "require-dual-confirmation-for-hazardous-motion",
  "effect": "deny",
  "applies_to": { "oil_categories": ["OIL-1900"] },
  "predicate": {
    "type": "consensus",
    "n": 2, "of": 2,
    "predicates": [
      { "type": "boolean", "predicate": "operator/left_hand_button", "equals": false },
      { "type": "boolean", "predicate": "operator/right_hand_button", "equals": false }
    ]
  },
  "safe_default": "stop"
}

The PLC continues to enforce the millisecond-level timing of the dual- button press. Intended adds the higher-level claim that two distinct operator inputs were the source of the authorization, recorded in the audit chain with timestamps.

Pattern 5 — Continuous-time predicates ("if X for ≥250 ms")#

Safety-PLC pattern: debounce / dwell timer on a sensor before acting.

Intended layer: continuous-time predicates (ECE-P2):

json
{
  "id": "require-stable-presence-detection",
  "effect": "deny",
  "predicate": {
    "type": "continuous_time",
    "predicate": "safety/human_in_cell",
    "equals": true,
    "min_duration_ms": 250
  },
  "safe_default": "hold-position"
}

Use this when you want the policy decision to depend on stability of the input, not the instantaneous value. The PLC's debounce protects against electrical noise; the Intended dwell predicate protects against flicker in higher-level fusion (e.g. a vision stack that briefly disagrees with the safety scanner).

What you should NOT translate to Intended#

  • Hard real-time interlocks. Anything that needs ms-level guaranteed response stays in the safety PLC. Intended's edge verifier targets ≤50 ms for the hot path; sub-ms response belongs in dedicated silicon.
  • Direct I/O control. Intended issues authority; it does not drive outputs. The PLC continues to drive contactors, valves, brakes.
  • Anything safety-rated that doesn't have a corresponding bus signal. If you can't surface it as a PhysicalStateValue over a named protocol, it's not a thing Intended can reason about.

Lifecycle parity#

Treat the Intended policy file with the same rigor as your PLC program:

PLC disciplineIntended equivalent
Source control on PLC projectGit on policy.json
Change-board approval for ladder editsPR review on policy edits
Validation testing before commissioningReplay simulator (DAG-P5) on captured traces
Backup / rollback procedureGit revert + policy hot-reload
Operator change logAudit chain (we provide this for free)

Migration sequencing#

  1. Inventory. List every operator command, every AI-issued command, every autonomous planner output. Map each to an OIL v2 category.
  2. Predicates. List every safety-bus signal currently used by an interlock. Map each to a PhysicalStateProvider predicate.
  3. Bridge. Build the predicate provider against your existing bus. This is where the integration work lives — typically 1–2 weeks for a single cell.
  4. Policy. Translate your interlock catalog into policy clauses (this guide). Start with effect: deny for the obvious safety patterns; add escalate clauses where human approval is required.
  5. Shadow mode. Deploy with the verifier in shadow mode (logs denials but does not actuate safe-default). Run for 1–2 weeks. Look for false-deny rates above your target.
  6. Cut over. Enable enforcement. Keep the PLC interlocks fully active; you have not disabled them. The Intended layer is defense-in-depth.
  7. Tune. As your operator workflows mature, tighten escalate thresholds. As your AI corpus matures, lower the classifier's fail-closed threshold.

See also#

PLC Migration Patterns | Intended