Praefectus
Praefectus 0.8.0 implements protocol v2 as a policy-neutral Rust library and JSON CLI for computer-use execution. Models propose strict ActionRequest values; the host retains planning, identity, approval, permissions, and policy ownership. The host signs one bounded AuthorityGrant with Ed25519, and Praefectus verifies it against a host-pinned issuer key before it claims or dispatches an operation.
The protocol provides durable at-most-once dispatch. Desktop and browser APIs are not transactional, so a crash, cancellation, or verification failure after dispatch can produce outcome_unknown; Praefectus never reports those cases as safely cancelled or retries them automatically.
CLI
The CLI writes one stable JSON envelope to standard output: {"ok":true,"data":...} on success or {"ok":false,"error":{"code":...,"message":...}} on failure. Exit code 0 means success, 2 means invalid CLI usage, 3 means a protocol, observation, or serialization failure, and 1 is reserved for failure to write the JSON envelope itself. Only that last failure writes a diagnostic to standard error.
observe returns one bounded semantic accessibility snapshot from the active surface with short tags such as e1; a trusted host resolves a selected tag to its fully fenced SemanticTargetRef through the library. surfaces lists bounded opaque native surface references, and observe-surface observes one exact host-selected surface without activating it. Observation commands use a 30-second deadline and return observation_error with exit code 3 when observation is unavailable. status returns the durable terminal acknowledgement when one exists.
execute is intentionally unavailable in the standalone CLI because a same-user process cannot establish an independent host-authority boundary from a caller-selected file. Trusted hosts execute through the library with an injected Ed25519AuthorityVerifier. Screenshot bytes, typed text, clipboard contents, selectors, credentials, and backend detail are excluded from trajectory logs, receipts, ledgers, and semantic plugin results; the authorized Hermes visual tool is the only model-facing image path.
Library
Use Engine<E> with a host-selected Executor and AuthorityVerifier. Protocol v2 uses strict, versioned, unknown-field-denying request, target, acknowledgement, terminal, receipt, and verification types. A host computes the canonical action hash, atomically consumes its own approval, binds its UID/subject, session, policy generation, risk, expiry, operation ID, and hash into AuthorityGrant, and signs it. Ed25519AuthorityVerifier::new rejects invalid identifiers and duplicate issuer/key identifiers instead of choosing one key. Praefectus never owns host approval or policy ledgers. Hosts can use CancellationToken for cooperative cancellation and implement another executor. Engine calls the boundary-aware observation, resolution, and shared-context methods; custom executors should override them when their provider can honor cancellation or deadlines within a blocking operation. The host must isolate its ledger and observation directory from untrusted same-UID filesystem writers; user-only permissions do not create a boundary between processes already running as that user.
NativeExecutor is Praefectus-owned. It uses macOS Accessibility (AX), Windows UI Automation (UIA), and Linux AT-SPI2 to produce bounded semantic trees and execute actions on the active backend. All platforms expose fenced semantic invoke and set_value; invoke means the platform accessibility activation action and is never a pointer click. macOS additionally exposes fenced semantic select_text, which sets the accessibility selected-text range and succeeds only when the readback matches the requested range, and perform_secondary_action, which dispatches only an allowlisted accessibility action name that the live element still advertises. perform_secondary_action never dispatches the activation action that invoke owns, and the activation-equivalent names in the allowlist satisfy the same invokable fence as invoke. All platforms additionally expose click, type_text, press, paste, hotkey, move, and scroll as input actions, and drag, which requires a fenced coordinate target and carries a second fenced coordinate destination inside the action so both endpoints are bound by the action hash. Linux exposes effects only when the authenticated local X11 socket peer is a generation-bound native Xorg process, the server supplies RandR topology, and the XWAYLAND extension is absent; Wayland sessions use the xdg-desktop-portal RemoteDesktop interface for input and the Screenshot interface for capture. Windows additionally exposes one-step semantic scrolling when UIA reports the matching scroll pattern. macOS scrolls through the accessibility page-scroll action when the live element advertises it, which synthesizes no input at all; it falls back to a per-process wheel event otherwise, and reports the weaker per-process route in both cases rather than claiming the stronger one. Every semantic target binds its observation generation and expiry, backend provenance, process identity and generation, window identity, display geometry, opaque element identity, and element fingerprint. Missing, hidden, disabled, unstable, stale, ambiguous, or mismatched elements are rejected. Private state is restricted with Unix permissions or a protected current-user Windows ACL.
Browser hosts can opt into CdpExecutor with a host-constructed CdpConfig. Configuration accepts only an exact IPv4 loopback endpoint and binds one explicit target ID to a live browser PID and process generation. The implementation observes the selected page or webview's root frame through the DOM Snapshot and Accessibility domains, uses an isolated world for fixed actionability probes, and exposes fenced semantic invoke, single-step element scroll, and value actions only after a current observation supplies real display geometry. CDP invoke revalidates the exact root-frame object, document, geometry, accessibility fingerprint, and actionability immediately before one fixed object-bound native HTML activation; it never advertises or dispatches pointer click. CDP scroll requires amount 1 and VerificationPolicy::None, revalidates the root-frame target, geometry, exact hit target, and directional scrollability, then runs one fixed internal offset update on the resolved node. It succeeds only when that exact node reaches the intended offset; it does not send wheel input, activate a target, or fall back to caller JavaScript, an ancestor, or another DOM node. SemanticObservation::target(tag) is the intended short-tag selection path; CDP dispatch performs its own stricter live actionability and identity checks rather than requiring the generic conservative route_action helper. CDP sessions are shared-desktop by default; background invoke, scroll, and value actions require the host to construct an isolated session because the CDP executor has no native foreground or cursor sentinel. CDP configuration is library-only: it is not a CLI or model parameter, it does not accept remote endpoints, and it does not expose arbitrary JavaScript execution.
A host can supply an OutcomeLedger alongside its Executor and AuthorityVerifier. OutcomeLedger::begin is keyed by OutcomeKey, which is the operation ID and the canonical action hash, and returns Dispatch, Recorded with the stored terminal, or Interrupted. Praefectus consults it before dispatch: a recorded terminal is returned without re-dispatching the effect, and an operation the ledger already granted but never recorded terminates as outcome_unknown without dispatch. Praefectus records each terminal through the same ledger and never owns its storage. NullOutcomeLedger records nothing and always grants dispatch, which leaves the built-in per-operation file ledger as the only durability. MemoryOutcomeLedger is a process-local reference implementation for tests and single-process hosts. An attempt counter is deliberately not part of the key: a retried attempt of the same operation and action must resolve to the recorded outcome rather than dispatch again.
INTERRUPTED_OUTCOME_MESSAGE is the stable statement a host surfaces to the model when an outcome was lost: the outcome was not recorded, and the actual state must be verified before anything with side effects is redone. It appears in the outcome_unknown terminal message for a durable claim without a terminal receipt and for a ledger grant without a recorded outcome. It carries no screenshot bytes, typed text, clipboard contents, selectors, credentials, or backend detail.
ActionRequest.interaction_mode is required and authority-bound. interactive permits the executor's advertised route. background_only refuses any action whose runtime capability cannot satisfy the host-selected session isolation. Capabilities report that executor-owned session_isolation before execution, and receipts bind it afterward. A model cannot assert that a session is isolated: SessionIsolation belongs to the executor configuration constructed by the host.
On a shared native desktop, only target-addressed accessibility actions are background-capable. Praefectus samples the foreground process/window, keyboard or accessibility focus, and pointer before dispatch and again afterward. A change or an unavailable sentinel makes the result outcome_unknown, even when target verification succeeds. unchanged_at_boundaries states only what those two samples observed; it does not claim continuous noninterference. Receipts report the requested interaction mode, actual session isolation, delivery route, and context-preservation result. Global pointer delivery is never advertised as shared-desktop background execution.
macOS delivers input aimed at a fenced element to that element's process through CGEventPostToPid, reported as the per_process_event delivery route. That delivery does not move the pointer or change the foreground application, so it is background-capable under the same sentinel guard as the target-addressed actions; it is not a noninterference claim, because the target process still receives synthetic input. A coordinate target carries no process identity, so click, move, and drag keep the global pointer route and are never background-capable. On every platform, an input action that cannot be delivered to a specific process is refused before any effect unless the host sets PRAEFECTUS_ALLOW_GLOBAL_INPUT=1. Linux and Windows have no per-process delivery path, so all of their input synthesis requires that opt-in. Praefectus reports these refusals as no effect, never as an unknown outcome, because nothing was dispatched.
macOS accessibility trees are read through AXChildren, falling back to AXRows and then AXVisibleChildren when the preceding axis is absent, empty, or larger than the traversal bound. The same precedence resolves observation paths, so an element's index means the same thing at observation and at dispatch. Some applications, notably Chromium and Electron hosts, only publish a full tree once AXEnhancedUserInterface or AXManualAccessibility is set on their application element. Praefectus never sets those implicitly: the host opts in with PRAEFECTUS_AX_OPT_IN=AXEnhancedUserInterface,AXManualAccessibility, and every attribute actually applied is recorded in SemanticProvenance::host_opt_ins, which is covered by the provenance hash that fences every target. A target observed under an opt-in therefore cannot be replayed against an observation taken without it.
Strong noninterference requires a host-owned isolated desktop, browser profile/process, session, container, or virtual machine. host_isolated records that trusted executor configuration; Praefectus does not create or coordinate those environments. CDP invoke, scroll, and value actions are background-capable only under that isolation contract. This follows the common split in the reviewed systems: exact semantic or protocol-addressed delivery can avoid activation, while dependable concurrent pointer input requires a separately owned environment.
Protocol v2 accepts native coordinate targets only from a current NativeExecutor::observe_coordinates record whose display geometry and screen-content hashes still match. A host-authorized Screenshot action uses that same fenced coordinate target and the public ScreenCaptureKit backend when macOS Screen Recording is available. The image is written to a host-owned path and is never placed in the action request, receipt, outcome ledger, log, or trajectory; a Hermes host bridge may return one bounded, validated PNG data URL as a multimodal tool result.
application, window, open, and clipboard_write remain auxiliary host-level actions. They name an application or window by string rather than by an observation-fenced target, are not advertised in the semantic action capabilities, and are not verified against a fenced element. screenshot is different: it is a target-addressed, coordinate-fenced read-only action gated by Screen Recording, coordinate capture, and private host state. On macOS the auxiliary desktop actions are carried out through the platform's own scripting and utility programs, resolved from a fixed directory list rather than through PATH; window uses accessibility actions rather than synthesized pointer input. A host that needs these effects bound to a specific observed window should close or focus through a fenced semantic target instead.
Protocol guarantees
- Strict protocol, action, target, and verification versions and JSON fields.
- Same operation ID and action hash replays the stored terminal result.
- Same operation ID with a different hash is rejected as a conflict.
- A durable claim without a terminal result becomes
outcome_unknownon recovery, and its message carries the interrupted-outcome statement. - A host outcome ledger resolves a re-presented operation from its recorded terminal instead of dispatching again.
- Every effect requires an observation-fenced semantic element or native coordinate target; unfenced targets fail before authority consumption.
- Cached element targets are rejected when their live process, process generation, window, display, provenance, observation generation, or element fingerprint is stale.
- Cancellation and deadlines are checked before every effect and between repeated or chunked operations.
- Receipts contain hashes and backend metadata, not screenshot or action content.
- A host-supplied custom executor can report a click with
VerificationPolicy::Noneonly asexecuted_unverified; built-in executors reject click and never describe it as verified. set_valuerequiresVerificationPolicy::TargetValueHashmatching the requested value, then compares only the post-action value hash on the same fenced target.- Requested verification that cannot prove the expected result terminates as
outcome_unknown, never success, and is not retry-safe.
Programmatic sequences
Trusted Rust hosts can call Engine::execute_batch(&requests, &cancellation) with individually signed ActionRequest values for native or browser-backed executors. It returns an ordered Vec<Result<ExecuteReport, ProtocolError>> for the attempted prefix, including any stopping error. Execution continues only after a terminal Succeeded receipt with Effect::Verified; rejection, failure, cancellation, expiry, unverified success and unknown outcomes stop the sequence. Empty input does nothing.
Every step uses the same authority checks, observation fencing and durable operation ledger as execute. A batch is not a transaction: completed effects remain, targets may become stale, and unknown outcomes require reconciliation rather than new operation IDs and retries. Observe and authorize the next batch after a step changes the UI. The host still owns approvals and signing; no standalone execute CLI or arbitrary script interpreter is enabled.
For a local macOS demonstration, open a disposable TextEdit document named exactly Tofu Praefectus Demo.txt, foreground it, and run cargo run --example tofu_textedit_demo -- --approved-demo. An optional second argument selects a host-observed surface ID when a hidden dialog interferes with foreground observation; the exact TextEdit process and document title checks still apply. This opt-in replaces only the observed editable field in that named TextEdit window with fixed research notes, then verifies its value hash. Its durable ledger is tofu-demo-operations.jsonl beside the default operation ledger. The example refuses other apps/windows and does not save or close documents. Its ephemeral demo signer is not a production approval system; production hosts must retain their own trusted approval and signing boundary.
Host bridges and plugins
The native OpenClaw and Hermes plugins are repository integrations, not members of the neutral crates.io package. They submit strict action proposals to a host-owned bridge. Hermes semantic execution preserves observation-fenced invoke, set_value, and foreground interactive-only click; its praefectus_observe tool asks the host to obtain a fresh coordinate observation and return one authorized multimodal PNG result. The plugin does not mint targets or authority. The bridge performs its host's approval/access check and atomic approval consumption, adds subject/session and protocol fields, signs and verifies the complete request with its pinned keyring, then invokes the library. It must return retry_safe: false for unknown execution or capture outcomes. Authority material is never a model tool parameter.
Poke Around and Folk Around adapters may invoke Praefectus only after their existing host access and approval checks; their full and per-action approval modes remain host policy. An Omi adapter may prepare a Praefectus authority claim only after atomically consuming its UID-, generation-, and expiry-bound approval, after which an Omi host signer issues the authority. Praefectus does not implement, replace, or persist any of those host policies or approval ledgers.
Optional features
The screencapturekit feature is off by default and macOS-only. It enables the public ScreenCaptureKit backend that supplies the screen content hash fencing coordinate targets and performs authorized visual capture, and it requires an Xcode toolchain and links the Swift runtime. Without it coordinate_capture is false and coordinate targets and visual capture do not resolve. Pointer delivery uses the public per-process event route only; no private framework or SPI is used.
Development
License
ISC. See LICENSE.
Acknowledgements
Praefectus provides its own native runtime and contains no copied code from the comparison projects reviewed during its design. See ACKNOWLEDGEMENTS.md for pinned project revisions, licenses, platform specifications, and the no-endorsement notice. Third-party Rust dependencies retain their respective licenses and copyright notices; see THIRD_PARTY_LICENSES.md for the locked dependency inventory.