An agent-first display server

Humans and AI agents observe and operate the same GUIs — under granular, revocable, capability-scoped authorization.

The sentence today's stack cannot express

An agent is allowed to fill in one form, in one Firefox window, for the next five minutes. It cannot see the password manager open beside it. The moment you touch the mouse, you have control back. Hold Escape for a second and its authority is gone — mid-click, mid-keystroke, whatever it was doing.

Every clause is a mechanism, not a policy setting:

The claimWhat makes it true
one formA grant: (principal × resource × verbs × constraints). Not a role, not a config flag — a row the core checks on every single action.
five minutesExpiry is a constraint on the grant, enforced at the chokepoint. There is no path that skips it.
can't see the other windowThe agent's Firefox is in a realm, talking to its own private nested shim. The other window is not hidden from it; it is not in its universe.
you touch the mousePhysical input is origin-tagged at the core and preempts agent input by construction — not by a race between two clients.
hold EscapeThe dead-man switch: every live grant dies, transitively. The agent's next call fails revoked.

See it run

An agent connects, petitions for a capability, a human approves a prompt the core drew itself, and the agent drives a real Firefox inside a realm.

Recording in progress. Until it lands, the demo is two commands and about five minutes on your own machine — run it yourself. It drives the real wlroots shim and a real application; nothing in the path is mocked.

Get started

From a clean clone to an agent driving a real app.

1

Build the core and the per-app shim.

# the Rust side: vitrind, xtask, fixtures
cargo build --workspace

# the C side: a real wlroots compositor, one per app
bash shim/ci/install-deps.sh
meson setup shim/build shim && meson compile -C shim/build
2

Run the demo agent against a real application, headless.

cargo xtask demo --headless
# → xtask demo: PASS
3

Write your own — the SDK is pure Python, standard library only.

import vitrin_os

conn  = vitrin_os.connect(sock, identity="agent://demo")
grant = conn.request_grant(verbs=("observe", "actuate.pointer"),
                           expiry_ms=300_000)
grant.await_consent()          # a human decides

grant.observe().to_png("before.png")
grant.pointer.click(640, 84)

How it fits together

Legacy apps never touch the trusted core. Each gets its own private compositor, which is itself an unprivileged client.

human agent principal physical input SDK client │ │ │ capability-native wire protocol │ handshake · grants · observe · actuate ▼ ▼ ┌───────────────────────────────────────────────────┐ │ vitrind — the trusted core │ │ capability kernel · grant store · compositor │ │ input router · consent surface · journal │ └───────────────────────────────────────────────────┘ ▲ ▲ │ frames up (fd) │ frames up (fd) │ input down (origin-tagged) │ input down ▼ ▼ ┌──────────────────┐ ┌──────────────────┐ │ realm │ │ realm │ │ per-app shim │ │ per-app shim │ │ ↓ │ │ ↓ │ │ unmodified app │ │ unmodified app │ └──────────────────┘ └──────────────────┘

Small trusted core

The entire TCB: capability kernel, grant store, scene composition, input routing, consent, journals. Window management and theming stay out of it, permanently.

Legacy complexity, exiled

Serving the full Wayland surface is a large, messy job. It happens in an untrusted, disposable, per-app shim — not in the process holding the grant table.

Unspoofable consent

The prompt is drawn by the process that owns the screen, composited above every client, with an exclusive input grab. No client can paint over it.

Status: Phase 1 complete

Every milestone closed on a named test that runs against the shipped binaries with no mock on any seam it claims — the project's decision D12. Mock-based tests are labelled component tests and close nothing.

MilestoneProven by
M1.2 — buffer pathtest_real_app.py — real core + real shim + real weston-terminal
M1.3 — observationtest_real_capture_fidelity.py — through the enforcement chokepoint
M1.4 — actuation, consent, dead-mantest_real_actuation.py, test_real_consent.py, test_real_deadman.py
M1.5 — demotest_demo.py — asserting the typed text actually landed

There is no sandbox yet. No namespaces, no seccomp, no Landlock. A realm's app runs as the core's own uid with the core's full view of the filesystem and network. Environment hygiene confines the well-behaved; it does not contain the hostile.

Do not run untrusted applications, or untrusted agents, against this. Every other known gap is listed here — the 24-hour fuzz soak that has never been run, wlcs conformance at 3/180, and the rest.

Read on

Run the demo

Clean clone to a passing run, and what each step actually did.

Your first agent

The Python SDK end to end, including every way actuation is refused.

The security model

Grants, consent the core draws itself, and the dead-man switch.

The wire protocol

Framing, the interfaces, and the fatal-vs-recoverable razor.

PRD & architecture

The canonical vision doc: object model, threat model, roadmap.

Contribute

DCO not CLA. What gets a change rejected, stated up front.