Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Praefectus

Policy-neutral Rust library and JSON CLI for host-authorized computer-use execution.

## Layout

```
.
├── src/lib.rs # library: Engine, protocol types, NativeExecutor, CdpExecutor
├── src/main.rs # JSON CLI binary `praefectus`
├── src/semantic.rs # semantic observation and fenced target types
├── src/cdp.rs # library-only CDP executor
├── src/linux_*.rs # Linux AT-SPI2 / input
├── src/macos_capture.rs # optional macOS ScreenCaptureKit
├── src/windows_*.rs # Windows UIA / ACL / capture
├── tests/ # protocol, CLI, fencing, native, live tests
├── examples/ # runnable library scenarios
├── plugins/openclaw/ # OpenClaw host plugin (not the crates.io package)
├── plugins/hermes/ # Hermes host plugin (not the crates.io package)
├── Cargo.toml
└── README.md
```

## Build and test

MSRV is Rust 1.88 (edition 2024).

```sh
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features
cargo check --all-targets --all-features
cargo build
cargo run --example capabilities
```

CI additionally uses `--locked` and `cargo package --locked`.

OpenClaw plugin (Bun):

```sh
cd plugins/openclaw
bun install --frozen-lockfile
bun run format
bun run lint
bun run typecheck
bun test
bun run build
```

Hermes plugin (Python):

```sh
python3 -m pip install --requirement plugins/hermes/requirements-dev.txt
python3 -m ruff format --check plugins/hermes
python3 -m ruff check plugins/hermes
python3 plugins/hermes/test_plugin.py
```

## CLI vs library

CLI path: `src/main.rs`, binary name `praefectus`. Existing commands:

- `status --ledger <path> OPERATION_ID`
- `capabilities`
- `surfaces`
- `observe`
- `observe-surface SURFACE_ID`

The CLI writes one JSON envelope to stdout: `{"ok":true,"data":...}` or `{"ok":false,"error":{"code":...,"message":...}}`. Exit `0` success, `2` usage, `3` protocol/observation/serialization, `1` envelope write failure.

Library path: `Engine::new(executor, ledger_path, authority)` in `src/lib.rs`. Trusted hosts inject `Ed25519AuthorityVerifier` and call `Engine::execute`. Observation, surface listing, and CDP configuration (`CdpConfig`, loopback-only) are library APIs. `NativeExecutor::list_surfaces`, `observe_semantic`, `observe_surface`, and `observe_coordinates` are the observation entry points.

`DenyAuthority` is the CLI verifier for `status` and `capabilities`. It cannot authorize dispatch.

## Do not invent

- Do not add, document, or demo a working `praefectus execute` CLI. `execute` is library-only and requires a host-injected trusted `AuthorityVerifier`.
- Do not invent credentials, keys, signatures, issuer IDs, authority paths, or model-facing authority parameters.
- Do not print secrets, screenshot bytes, typed text, clipboard contents, selectors, or backend-error detail.
- Do not invent remote CDP endpoints, arbitrary JavaScript execution, or CLI/model CDP configuration.
- Do not invent host approval, policy, or identity ledgers. Praefectus does not own those.
- Do not treat `outcome_unknown` as cancelled or retry-safe.
- Do not add OpenClaw or Hermes plugins to the crates.io package.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ include = [
"/Cargo.toml",
"/Cargo.lock",
"/README.md",
"/examples/**",
"/LICENSE",
"/ACKNOWLEDGEMENTS.md",
"/THIRD_PARTY_LICENSES.md",
Expand All @@ -34,6 +35,10 @@ path = "src/lib.rs"
name = "praefectus"
path = "src/main.rs"

[[example]]
name = "capabilities"
path = "examples/capabilities.rs"

[dependencies]
ed25519-dalek = { version = "2.1", default-features = false, features = ["std"] }
fs2 = "0.4"
Expand Down
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
# Praefectus

Praefectus 0.7.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.
**Policy-neutral Rust library and JSON CLI for host-authorized computer-use execution.**

## Quick Start

```sh
cargo build
cargo test
cargo run -- capabilities
cargo run -- surfaces
cargo run -- status --ledger ./operations.jsonl OPERATION_ID
cargo run --example capabilities
```

`capabilities` returns the native executor's advertised platform, backend, session isolation, and actions. `surfaces` lists bounded opaque native surface references when observation is available. `status` returns the durable terminal acknowledgement when one exists. The library hello in [`examples/capabilities.rs`](examples/capabilities.rs) constructs `Engine` with `NativeExecutor` and `DenyAuthority` and does not execute an action.

The standalone CLI has no `execute` command: 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`.

## Trust model

Praefectus 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.

Expand Down Expand Up @@ -79,6 +98,7 @@ cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features
cargo check --all-targets --all-features
cargo build --release
cargo run --example capabilities
cargo package
```

Expand Down
38 changes: 38 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Praefectus Examples

This directory contains working examples demonstrating Praefectus capabilities.

## Examples

### 1. [capabilities](capabilities.rs)
Library hello: construct `Engine` with `NativeExecutor` and `DenyAuthority`, print capabilities, and read status for an unknown operation ID.

```bash
cargo run --example capabilities
```

**Demonstrates:**
- `Engine::new` with a host-selected ledger path
- `Engine::capabilities` without credentials
- `Engine::status` for an unknown operation ID (`None` when the ledger has no claim)
- Why `execute` stays library-only (`DenyAuthority` cannot authorize dispatch)

## Quick Start

```bash
cargo run --example capabilities
cargo run -- capabilities
cargo run -- surfaces
```

`capabilities` needs no desktop session. `surfaces` and `observe` use the native accessibility backend and return `observation_error` (CLI exit `3`) when observation is unavailable.

There is no `execute` example. Trusted hosts execute through the library with an injected `Ed25519AuthorityVerifier`.

## Directory Structure

```
examples/
├── README.md
└── capabilities.rs
```
17 changes: 17 additions & 0 deletions examples/capabilities.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use praefectus::{DenyAuthority, Engine, NativeExecutor};

fn main() -> Result<(), Box<dyn std::error::Error>> {
let directory = tempfile::tempdir()?;
let ledger = directory.path().join("operations.jsonl");
let engine = Engine::new(NativeExecutor::default(), ledger, DenyAuthority);
let capabilities = engine.capabilities()?;
println!("platform={}", capabilities.platform);
println!("backend={}", capabilities.backend);
println!("session_isolation={:?}", capabilities.session_isolation);
println!("supported_actions={:?}", capabilities.supported_actions);
match engine.status("example-missing-operation")? {
Some(acknowledgement) => println!("status={}", acknowledgement.operation_id),
None => println!("status=none"),
}
Ok(())
}
Loading