Physical-Effect Class
A tool whose effect is a change of state in the physical world (motion, force, energy, a relay, a
setpoint written to a controller) declares effect_classification: physical plus a physical_effect
descriptor. The rule behind it: no model-reachable tool has an unmediated physical effect. LumenFlow
governs what an agent may ask a machine to do. It is never part of a real-time or safety-rated
control loop, and the governed path is never an emergency stop.
This page documents what ships today (the declaration and physical host, the evidence chain with fail-closed physical traces, and the governed stop). Mediated dispatch with envelopes and signed policy bundles are not available yet.
Declaring a physical capability
Section titled “Declaring a physical capability”- A physical capability is always consequential.
permission: readis refused, and nothing can label itnon_consequential. - The input schema is generated by the kernel from the descriptor: a strict object whose fields are
bounded finite numbers or vocabulary enums. You cannot supply your own schema; transforms,
defaults and open objects are refused (
physical.schema-not-generated). - Only a pack-owned capability under a host-registered
trusted-host-adaptermay declare physical. In a pack manifest that means Pack Contract v2,trust_tier: trusted-host-adapterand anadapter_id, and the tool carries noinput_schema. - A proposal capability (
proposal_effect) staysconsequentialand can never bephysical.
The host vocabulary
Section titled “The host vocabulary”The host registers a closed vocabulary of resources and operations. Each operation may carry
risk_reducing (no motion and no energy increase, so no bound parameters), safety_rated_configuration
(refused at registration), two_person (denied until quorum approvals ship) and safe_place (a
motion that brings the machine to a safe place, such as park, home or retract). Every operation that
is not risk-reducing must declare command: leased or command: time_bounded, saying how its
controller stops on its own if LumenFlow goes silent; registration refuses one that declares neither
(physical.command-not-leased). Each parameter carries the unit the host policy uses; a descriptor
bound must use the same unit, and the kernel performs no conversion.
The physical host
Section titled “The physical host”Physical capabilities register only on a physical host: a Linux host-side kernel process running as a
dedicated, non-root OS identity that alone owns the reserved devices. Pass physicalHost
({ config, vocabulary }) to initializeKernelRuntime: start-up then calls verifyPhysicalHost and
registerPhysicalCapabilities, which registers nothing unless every check passes. A manifest tool
declared physical whose resolved capability is not physical, or whose resolved physical_effect is not canonically equal to the declared descriptor (widened bounds, another enum, operation or resource), refuses start-up
(physical.classification-mismatch), and a physical tool with no physicalHost refuses it with
physical.host-precondition.
Verification checks the platform, effective identity, not-in-a-box, device ownership and mode (no
group or other access, no symlinks), relay socket permissions, the in-process handler set, full
network and reserved endpoints on allowlists, that subprocess confinement is enforced, and a
kernel-run confined reachability probe (the same confinement a tool subprocess gets, with the same network posture) that must not reach a reserved device or endpoint, and that each device is a real device node in a directory only the identity or root can write. The result is sealed: only verifyPhysicalHost produces one the gate accepts, and ToolRegistry.register refuses any physical capability the gate did not admit. macOS and Windows are
refused with physical.platform-refused.
The default resolver cannot register a physical tool. It builds a subprocess capability with the
manifest’s schema, so a manifest-declared physical tool fails physical.schema-not-generated. A
physical host supplies its own toolCapabilityResolver that returns in-process adapter capabilities
carrying the physical_effect descriptor and a kernel-generated input schema
(generatePhysicalInputSchema). The reserved endpoints are currently given twice: in
physicalHost.config.reserved_endpoints and in the reservedEndpoints option of the network
confinement port; keep them identical.
Some conditions cannot be verified by the kernel and are listed in every report as host assertions: no workload runs as or can switch to the physical identity, the firewall or namespace rule covers every path to each reserved endpoint, no other identity gains device access later, and root and administrators are trusted.
The egress proxy accepts reservedEndpoints (hostname, IPv4 or IPv6 address in any spelling, or an IPv4 or IPv6 CIDR; optional port). A reserved
endpoint is refused at connect time with proxy.reserved-endpoint, before the allowlist and any
loopback grant, and again for every address a name resolves to.
Holds and denies
Section titled “Holds and denies”With no host physical policy, a physical call that base policy did not deny returns
PHYSICAL_POLICY_HOLD. The hold is final: external-content labels cannot turn it into an approval
request, and the tool host refuses to dispatch a physical capability on any path until the policy
work unit lands. Unlike the spend branch, a physical call never turns a deny into a hold:
policy_denied, scope_denied, reserved_scope_denied and deny_overlay_denied stay denies.
Risk-reducing operations (stop, hold, de-energise) are the exception: they are never held, need no approval, skip provenance checks and run while the governed stop is tripped. A base deny from policy or scope still denies them, so a host should never deny a stop.
See the spend policy for the pattern this follows.
Evidence for physical calls
Section titled “Evidence for physical calls”Every trace entry is chained to the one before it, and the chain can be signed and timestamped
through a host port (see Evidence Store). On the
direct allow path a physical call’s evidence fails closed, reusing the consumed-approval path’s
ApprovalExecutionEvidenceError and its crashed outcome:
- Started trace not recorded: the call is refused before policy and dispatch. The error has
code
APPROVAL_EXECUTION_EVIDENCEandphase: 'pre_dispatch'. An ordinary (non-physical) tool keeps its existing behaviour and still runs. - Finished or progress trace, or the output or progress data, not recorded: the failure is never swallowed. The kernel appends a
crashedfinish for that receipt (policy idkernel.physical.evidence) and raises the evidence error throughonTraceError; after a dispatch it is thrown to the caller withphase: 'post_dispatch'. - Even the
crashedfinish cannot be recorded: that resource is held. The next physical call for it first retries thecrashedfinish for every open receipt; until each has a finish in the trace, the call is refused withPHYSICAL_POLICY_HOLDandreason_code: physical_evidence_unreconciled(the open receipts are listed indetails.unreconciled_receipt_ids). After a restart, orphan reconciliation writes the samecrashedfinish.
The hold is kept by the tool host in memory, per resource. Risk-reducing operations (stop, hold,
de-energise) are exempt: a stop whose started trace fails, or whose resource is held, still runs, and
a stop whose finish cannot be recorded never throws; the failure is reported through onTraceError
and a crashed finish naming the stop is attempted.
The governed stop
Section titled “The governed stop”Every kernel runtime has a governed stop for its workspace. It is a supervisory stop request for physical calls. It is not an emergency stop and not a safety function: the controller’s safety functions and the hardware emergency stop stay underneath it.
- Tripping. Any principal may trip it, including the model, through the
kernel:stoptool (registered on a physical host). The tool is never held, approved, policy-checked or refused, and malformed input, an already-aborted caller signal or a lost Connected Compute lease still trips. In fact every authenticatedkernel:stoprequest trips the runtime’s stop as the first step, on every host, before anything checks it: the runtime’s execution facade (even while the runtime is closing), the agent runtime’s tool policy, checkpoints and agent loop, Connected Compute, the heartbeat, the task tool command, task replay and MCPtool_executeall trip first. If the request is then refused (a closed runtime, a tampered workspace, an unreadable workspace file, a bad selector, a held approval, a tool outside the catalog, an unknown tool on a non-physical host), the refusal is reported and recorded as usual, and the stop is already tripped. The HTTP tool API and the surfaces MCP server have no stop route. Host code can trip it throughruntime.getGovernedStop().trip(...). A kill a Connected Compute runner sees (from Cloud, or from a local supervisor throughcompute run --control-file) trips it too, wherever the runner first sees it, for every assignment type (local tool, agent, playbook andagent:launch), even before the assignment’s runtime opens, and the kill’s terminal report waits until that trip is recorded. While an assignment’s runtime is open, the kill also trips that runtime at once, so a physical call in flight aborts and the next tool call is refused. A runner that cannot open a governed stop for the workspace at all exits with an error instead of reporting the kill, even if the assignment then fails. A held kill never recreates a workspace removed after it. Cloud does not deliver stops or kills to connected runners yet. Until then, a Cloud kill whose trip is not recorded when the runner ends is lost. If your own layer dispatches tool calls, route a stop throughforwardStopRequestFirstbefore your checks. - What a trip does. It aborts every in-flight physical call in this process at once (the
adapter’s
signalfires), aborts this runtime’s sandbox launches, and then records the trip on the kernel event log. Every stop request is recorded, so a second stop while tripped also stops an operator’s motion in another process. If the trip cannot be recorded, the process stays tripped, raises an owner notice (physicalHost.onGovernedStopNotice) and retries. If the state cannot be read at start-up, the runtime starts tripped. A stop is acknowledged only once it is on the event log: until then the stop tool answersGOVERNED_STOP_NOT_DURABLE(“held in memory, not yet durable”) and the operator command andruntime.getGovernedStop().trip(...)resolvefalse(the process stays alive for that bounded wait), while the process keeps retrying with bounded backoff, even after the runtime closes. Stop requests are idempotent: if you get no acknowledgement, request the stop again; a repeated request joins the one already held and adds no work. Nothing is inferred at restart, so a trip nobody requests again (the lease-loss trip, or a stop in a turn the agent loop refuses) is lost if the process ends before it is recorded. - Your own dispatcher. If you pass your own
subprocessDispatchertoinitializeKernelRuntime, build it on aKernelLaunchAbortRegistryand pass the same registry assandboxSubprocessDispatcherOptions.abortRegistry. Without it, start-up is refused: the stop must abort exactly this runtime’s launches. - Other processes. Every process on the same workspace re-reads the stop before each physical call, and while a physical call is in flight it checks every 100 ms, so a trip anywhere aborts it within 250 ms by default and never more than 1 s. If the stop cannot be read within that bound (a locked or failing event log), in-flight physical calls are aborted and new ones refused. The bound covers signalling: an adapter that ignores its signal is stopped by its controller’s lease.
- Non-abortable operations. An operation whose descriptor sets
abortable: falseis not aborted by a trip; it finishes within its lease and the next call is refused. - While tripped. Every physical call is refused with
GOVERNED_STOP_TRIPPED, except risk-reducing operations and a safe-place motion the operator runs (below). - Lease loss. Pass the run’s execution lease as
ToolExecutionOptions.leaseSignal, or mark the signal you already pass withmarkExecutionLeaseSignal(signal); losing it during a physical call trips the stop.
Operator commands
Section titled “Operator commands”Resetting the stop, and running a safe-place motion while it is tripped, are operator commands. A model can never reach them; there is no reset tool.
The runtime authenticates the operator with the host callerAuthenticator, refuses a principal the
host declares model-controlled, and asks the approval authoriser’s authorizeOperatorCommand (for
governed_stop.reset or governed_stop.safe_place_motion). Without that method, or without a
modelControlledPrincipals classifier that implements isModelControlledIdentity (for example
staticModelControlledPrincipals), every operator command is refused. Configure both on the
runtime: initializeKernelRuntime({ callerAuthenticator, approvalResolverAuthorizer, modelControlledPrincipals }),
where approvalResolverAuthorizer.authorizeOperatorCommand decides who is an operator. If a runtime
started without them, the stop cannot be reset from it: add them and restart the runtime. The trip
survives the restart, and the operator can then reset it. If the latest stop record cannot be read
(for example a corrupt record), the workspace stays tripped and reset is refused while the row is the latest; restarting the runtime (which records a fresh trip over it) or any new trip lets an authenticated operator reset, and the corrupt row stays in history. A safe-place grant is single use, bound to one tool and its exact input, and lasts 60
seconds.
Leased commands
Section titled “Leased commands”A physical adapter sends its controller only leased or time-bounded commands. runLeasedCommand
implements the driver side: it sends the command with a lease, renews it at half the lease while
the call runs, and when the kernel aborts the call (or a renewal fails) it stops renewing and then
sends the controller’s own stop command. A stop that arrives while the command is still being sent
is honoured: no renewal is ever sent after it.
If the kernel process dies, nothing renews the lease and the controller stops on its own.