Humans and AI agents observe and operate the same GUIs — under granular, revocable, capability-scoped authorization.
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 claim | What makes it true |
|---|---|
| one form | A grant: (principal × resource × verbs × constraints). Not a role, not a config flag — a row the core checks on every single action. |
| five minutes | Expiry is a constraint on the grant, enforced at the chokepoint. There is no path that skips it. |
| can't see the other window | The 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 mouse | Physical input is origin-tagged at the core and preempts agent input by construction — not by a race between two clients. |
| hold Escape | The dead-man switch: every live grant dies, transitively. The agent's next call fails revoked. |
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.
From a clean clone to an agent driving a real app.
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
Run the demo agent against a real application, headless.
cargo xtask demo --headless
# → xtask demo: PASS
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)
Legacy apps never touch the trusted core. Each gets its own private compositor, which is itself an unprivileged client.
The entire TCB: capability kernel, grant store, scene composition, input routing, consent, journals. Window management and theming stay out of it, permanently.
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.
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.
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.
| Milestone | Proven by |
|---|---|
| M1.2 — buffer path | test_real_app.py — real core + real shim + real weston-terminal |
| M1.3 — observation | test_real_capture_fidelity.py — through the enforcement chokepoint |
| M1.4 — actuation, consent, dead-man | test_real_actuation.py, test_real_consent.py, test_real_deadman.py |
| M1.5 — demo | test_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.
Clean clone to a passing run, and what each step actually did.
The Python SDK end to end, including every way actuation is refused.
Grants, consent the core draws itself, and the dead-man switch.
Framing, the interfaces, and the fatal-vs-recoverable razor.
The canonical vision doc: object model, threat model, roadmap.
DCO not CLA. What gets a change rejected, stated up front.