Skip to content

5 Safety

This section states what a Safety Kernel MUST do. A Hub at Conformance Level control or full MUST evaluate every command through a Safety Kernel that meets every requirement here.

5.1 Guarantees

  1. One path. Every command, from any source, MUST be evaluated by the Safety Kernel. Sources include every Client, routines, the dashboard, the driver writer, bridges and the stop command itself. A Driver MUST accept a change only with a single-use Grant the Safety Kernel issued for that exact Device, target and value.
  2. Deterministic. The Safety Kernel MUST NOT use a model, network calls or randomness in its checks. The same state and the same command MUST produce the same result and the same message.
  3. Risk decides where a human comes in. Every write channel and action has a Risk Level (Risk levels). A high risk target MUST stay read-only until a human confirmed its limits. So MUST a low or medium target still marked suggested: true in a Device File whose provenance.source is none of self-described, driver-describe and library, because no range in that file came from the Device or the library. Any other low or medium target MAY take commands without a confirmation, within the Device File's range. Agents MAY tighten limits for their own session. Nothing MAY loosen a limit or lower a Risk Level except a human, and the Device File records who and when.
  4. Stop works. One stop command MUST put every Device in its safe state and block every other command until a human unlocks.
  5. Fail closed. Any unexpected exception inside the Safety Kernel MUST block the command and be logged. Nothing fails open.
  6. Everything is recorded. Every read request, command, block, confirmation, stop and unlock MUST be appended to an audit log with a hash chain.

5.2 The checks, in order

The Safety Kernel evaluates a command against the current state in this fixed order and MUST stop at the first failure. Each failure has a stable kernel code and a registry name.

#CheckBlocks whenKernel codeName
1Command shapeUnknown Device, channel or action, wrong argument names, malformed payloadunknown_target, bad_requestUNKNOWN_TARGET, INVALID_PARAMS
2Stop lockThe global stop lock is setstoppedSTOPPED
3File stateThe target is high risk, or has only suggested limits from a file that neither the Device nor the library supplied, and its limits, or the file-level limits that guard it, aren't confirmed (read-only)read_only_fileUNVERIFIED_LIMITS
4Device stateThe Driver is not connected, is in a fault state, or is applying its safe state. Also, any registered Device whose expressions name a Device that isn't registered blocks every command on the Hub, because a misspelled id protects nothingdevice_unavailable, misconfiguredDEVICE_UNAVAILABLE, MISCONFIGURED
5Principal scopeThe Principal's kind or scope does not cover this operation, or the lower of the target's Risk Level and the Device's hazard classnot_permittedSCOPE_DENIED
6UnitsThe value has no unit, the unit can't be converted to the channel unit, or the result is not finitebad_unitUNIT_INVALID
7Type and enumThe value is the wrong type, not one of the allowed values, too long, or doesn't match the patternbad_valueINVALID_VALUE
8RangeBelow min or above max, including session limitsbelow_min, above_maxLIMIT_EXCEEDED
9StepNot a multiple of step from minbad_stepINVALID_VALUE
10RateThe change from the last known value exceeds max_change_per_s times the elapsed time, the current value is unknown, or an action already ran max_runs times in its window. A low risk channel's max_change_per_s that is still suggested doesn't applyrate_limitedRATE_LIMITED
11InterlocksAn action's require doesn't hold, or an interlock that applies is not satisfied by the current and commanded stateinterlockINTERLOCK_BLOCKED
12Watchdog budgetThe value would start or continue a state whose max duration is already used upduration_exceededWATCHDOG_TRIPPED
13SerializationAnother command holds the Device for longer than 5 sbusyBUSY
14ConfirmationThe channel or action needs confirmation, the Device is high hazard, or the target is a high risk action that can't be undone, and there is no valid approvalneeds_confirmation, queue_fullCONFIRMATION_REQUIRED, QUEUE_FULL

Check 13 runs before the others. Commands to one Device MUST run one at a time. The Safety Kernel holds a command for up to 5 s until the Device is free, and then evaluates the command against the state the previous command left.

A command that passes every check gets a Grant and runs. A command that reaches check 14 without an approval goes to the confirmation queue, and hal/write or hal/action returns a pending result with its requestId.

5.3 Unknown values

  1. A channel's value is unknown when nothing has been read since the Hub connected, when a sensor reading is older than max(10 s, 3 x heartbeat), while a command to that channel is in flight, after a command to it failed or timed out (it may have landed), and after an action that lists it in effects.
  2. A write channel's last commanded value stays known while the Device is connected, because the Device holds it.
  3. The Hub SHOULD read such channels back right away when it can.
  4. A check that depends on an unknown value MUST NOT pass. Interlocks treat an unknown value as not satisfied, rate limits block until a fresh value arrives, and watchdog rules keep counting.
  5. While a command to one Device is in flight, the channels it may change are unknown to every other Device's interlocks.

5.4 Rules for specific checks

  • Units. Values are converted to the channel's declared unit with one pinned unit registry. 85 degF against a max: 30 degC channel is converted to 29.44 degC first, then compared. Bare numbers MUST be blocked for number channels.
  • Range tolerance. A value within 1e-12 relative of a limit passes.
  • Step tolerance. A value within 1e-6 of one step passes.
  • Rate of change. The reference value is the newest known value for that channel, whether the Safety Kernel passed the value or read the value from the Device. If nothing is known since connecting, the first command is measured from the channel's safe-state value, or from min if there is no safe state. If the value became unknown after connecting, the command is blocked until a fresh reading arrives. The allowed change is max_change_per_s * elapsed_seconds, with elapsed time on a monotonic clock, capped at 3600 s, and a relative tolerance of 1e-9. Commands that fail this check are blocked, not ramped. To move further, a Client issues steps that each pass.
  • String patterns MUST run with a time budget so no value can stall the Safety Kernel. The reference uses 50 ms. A value whose check runs out of time is blocked.
  • Interlocks use the expression language of Device files and are parsed by the Hub's own parser. Expressions MUST NOT be evaluated as code.
  • Session limits. A Client MAY narrow min, max or max_change_per_s for its own session. A request that would widen a limit MUST be blocked with cannot_loosen (MISCONFIGURED). This version exposes session limits through MCP and REST only.

5.5 Risk levels

Every readable channel, write channel and action has a Risk Level, low, medium or high. A Hub sets the level from the Device File and the Device's own description.

  1. Reading a channel MUST be low.
  2. A write channel or action MUST get the highest level any rule below gives it, and medium when no rule applies.
  3. The rules MUST NOT name a Device, a product or an id. They read the file's hazard, the unit's physical quantity and the maximum it allows, the words of the target's own name, the Device's name when the target's name has none of the words, the channels an action changes, the motion axes on the Device and the pin each target drives.
  4. A target MUST be at least the file's hazard.
  5. A missing unit (dimensionless) or a unit the rules don't list MUST raise the level by one and MUST NOT lower it. Words from a description or an enum value MUST only raise a level.
  6. Targets whose map.pin is the same MUST share the highest level among them. A target mapped to a bare pin, whose name has none of the words, MUST be at least medium.
  7. Three or more write channels with an angle or length unit on one Device MUST each be high.
  8. A risk entry with by and at MUST set the level, up or down. A risk entry without by MUST only raise. Tools that draft a Device File MUST drop by and at from any risk they copy.
  9. Raising a level takes effect at once. Lowering MUST need a human Principal and the typed phrase confirm, or a human's approval of a pending change with the phrase approve. Agents MUST NOT change a level, and a Hub SHOULD show every level to them.
  10. Every level change MUST be recorded in the audit log and the Device File, so it survives a restart.
LevelDefaults
lowTakes commands at once within the Device File's range, which a self-described file takes from the Device. A suggested rate doesn't apply. No confirmation unless a human sets one
mediumTakes commands at once within the Device File's range and rate. No confirmation unless a human sets one
highRead-only until a human confirms its limits (Limits provenance). An action that can't be undone needs a human's approval at least once

A low or medium target still marked suggested: true in a file from a manual, a sketch, an agent, a bridge or a hand-written file is read-only until a human confirms its limits, as a high one is, because no range in that file came from the Device or the library.

An action can be undone when every effect it lists is a write channel, when the safe state runs it, or when the Device has the action that undoes it, such as resume for pause. Stop, the safe state, the watchdogs, the audit log and every limit a human set apply at every level.

5.6 Grants

  1. A Grant names one Device, one target (write:<channel>, action:<name> or safe_state) and one exact value in the channel's unit.
  2. Only the Safety Kernel can create a Grant. Constructing one any other way MUST fail.
  3. A Grant MUST be consumed on use, and MUST fail verification if the Grant was already used, has expired, or names another Device, target or value. The reference expiry is 5 s.
  4. A Driver receives a verifier bound to its own Device, not a reference to the Safety Kernel.
  5. A stop and a lost connection revoke every live Grant for the Device.

5.7 Principals and hazard classes

HazardExamplesAgent scope neededConfirmation
lowsensors, LEDs, small fans, displayscontrol_lowas set in the Device File
mediummotors, pumps, printers, heaterscontrol_medium, or control_low for a low risk targetas set in the Device File
highlasers, CNC spindles, high voltage, high-power heaters, robots that can injurecontrol_medium to request, never to executeevery command is held for a human's approval, every time

A Device File with no hazard counts as medium for scope, so its low risk targets need only control_low.

  1. An agent with scope read MUST NOT change write channels or run actions.
  2. A human Principal's own command counts as confirmed. Every other check still applies to it.
  3. The system Principal is internal to the Hub. It applies safe states and watchdog actions and is never accepted from a Client.
  4. A routine runs with the scope of whoever enabled it, capped at control_medium (Routines).
  5. A routine MAY drive a high hazard Device only when the Device File holds a human-signed approval for that routine's content hash (Device files). Changing the routine invalidates the approval.

5.8 Confirmations

  1. Agents never confirm. A command that needs confirmation is queued as a Confirmation Request.
  2. A Confirmation Request holds the exact command, the requesting Principal, the dry-run result at the time of the request and an expiry. The reference expiry is 5 minutes.
  3. Each Principal MAY have at most 20 pending requests, and the queue holds at most 100. A full queue blocks new requests with QUEUE_FULL and MUST NOT evict another Principal's request.
  4. Only a human Principal may approve or reject. Other Principals may read the status of their own requests.
  5. Approval MUST re-run every check against the current state. If the command would now be blocked, it is blocked, not executed.
  6. Under tier confirm, one approval covers identical repeat requests from the same Principal for 10 minutes. Under tier confirm-each-time, one approval covers exactly one execution. High hazard is always confirm-each-time.
  7. A routine runs unattended, so a routine's command that needs confirmation is blocked with CONFIRMATION_REQUIRED, except on a high hazard Device whose file carries a valid signed approval for that routine.
  8. Every change of a request's status (expired, cancelled, approved, rejected) MUST be logged. Expired requests close automatically with the status expired.

5.9 Stop and unlock

  1. Any Principal MAY stop, including agents. Only a human MAY unlock.
  2. A stop runs in this order. Set the lock, cancel pending Confirmation Requests, clear approval windows, disable routines, cancel any command still in flight, apply every Device's safe state, then report the result per Device.
  3. Every safe-state step MUST run even if an earlier one fails. A Driver with native safe-state support runs its own safe behavior first, then the Device File's steps.
  4. The result per Device is safe, partial (some steps failed), failed, no_safe_state, or pending when a command was still running and couldn't be cancelled in time.
  5. When a command that was in flight during a stop returns, the Hub MUST reapply the safe state, so the stop takes precedence over the command.
  6. Unlock MUST be blocked while a stop is still being applied, and an unlock clears approval windows again.
  7. Unlock requires the typed phrase unlock. The reference CLI also requires an interactive terminal by default.

5.10 Watchdogs

  1. Heartbeat. The Hub checks each Device at its heartbeat interval. On connection loss the Hub applies the safe state when on_loss is safe_state, marks the Device unavailable and blocks commands until the Device reconnects.
  2. Max duration. A rule's budget starts when its expression becomes true and only resets once the expression has been false for reset_after, so a brief dip doesn't refill it. While the budget runs, an unknown value counts as true.
  3. When a budget runs out, the Hub applies the rule's then. A failed action is retried with backoff, and a then mapping that fails three times escalates to the safe state.

5.11 Limits provenance

  1. A high risk target, and a low or medium target whose limits are still suggested in a file that neither the Device nor the library supplied, take commands only when a human confirmed their limits in the Device File (Device files). Other low and medium targets don't need a confirmation (Risk levels).
  2. A confirmation that carries sha256 is bound to the file's limits, so any later change makes those targets read-only again.
  3. A LIMIT_EXCEEDED reason MUST name the Device File and the human confirmation behind the limit. data.confirmedBy and data.confirmedAt come from the channel's own confirmed, or else the file's.

5.12 Error registry

A blocked or failed command is a JSON-RPC error (Messages). data.name is what Clients branch on. Numbers are stable across versions and MUST NOT be reused. Every kernel code maps to exactly one name.

CodeNameKernel codesMeaning
-32700PARSE_ERRORInvalid JSON
-32600INVALID_REQUESTNot a JSON-RPC request
-32601METHOD_NOT_FOUNDUnknown method, or one above the Hub's Conformance Level
-32602INVALID_PARAMSbad_requestParams don't match the schema
-32603INTERNAL_ERRORinternal_errorA bug in the Hub
-32001NOT_INITIALIZEDhal/initialize hasn't run
-32002UNAUTHORIZEDMissing or unknown token
-32003SCOPE_DENIEDnot_permittedThe Principal's token scope doesn't cover this Device or method
-32004UNKNOWN_TARGETunknown_targetNo such Device, channel or action
-32005DEVICE_UNAVAILABLEdevice_unavailableDisconnected or not ready
-32010LIMIT_EXCEEDEDabove_max, below_minOutside the Device File's confirmed limits
-32011RATE_LIMITEDrate_limitedFaster than max_change_per_s allows right now
-32012INTERLOCK_BLOCKEDinterlockAn interlock or action requirement doesn't hold, or a value it needs is unknown
-32013CONFIRMATION_REQUIREDneeds_confirmationNeeds a person. data.requestId names the request when it was queued
-32014STOPPEDstoppedThe Hub is stopped until a person unlocks
-32015UNVERIFIED_LIMITSread_only_fileThe Device File's limits aren't confirmed by a person, so it is read-only
-32016INVALID_VALUEbad_value, bad_stepWrong type, step, enum value or pattern
-32017WATCHDOG_TRIPPEDduration_exceededA watchdog budget is used up
-32018BUSYbusyAnother command holds the Device
-32019QUEUE_FULLqueue_fullThe confirmation queue is full
-32020DRIVER_ERRORdriver_errorThe Device or its protocol failed, and the channel is unknown until a readback
-32021MISCONFIGUREDmisconfigured, cannot_loosenThe Hub's Device Files are inconsistent, or a change would loosen a confirmed limit
-32022REQUEST_CLOSEDexpired, rejectedThe Confirmation Request expired or was rejected
-32023UNIT_INVALIDbad_unitA missing, unknown or incompatible unit, or a value that isn't finite

5.12.1 Reason messages

error.message is formed from data, one template per name. The Safety Kernel's own message stays available as data.kernelMessage. Every reason SHOULD name the rule, the limit and what to do next.

The LIMIT_EXCEEDED templates are normative.

Kernel codeTemplate
above_max{channel} {requested} exceeds the limit of {limit} ({file}, confirmed by {confirmedBy} {confirmedAt})
below_min{channel} {requested} is below the limit of {limit} ({file}, confirmed by {confirmedBy} {confirmedAt})

For an action argument, {channel} is {action} {argument}, as in water duration 120 s exceeds the limit of 30 s. For the canonical example the reason reads extruder_target 300 degC exceeds the limit of 260 degC (printer.hal.yaml, confirmed by owner 2026-10-24).