scry / vita
linux·gpui·cef 151

a browser for the pages you control.

Built overnight, in use the next morning. A native browser where everything outside the page rectangle is real native chrome — tab strip, omnibox, sidebar, palette — with Chromium composited inside as a single texture. This page is the story. The link below is the spec.

§ 01   /   the hook

You spend all day inside software you don't control.

Your editor, your terminal, your browser — all shaped by someone else's product decisions, all of them treating your data as opaque strings. That was fine when writing software was the expensive part. It isn't anymore.

The moment it stopped making sense: an agent can now build you a native desktop application overnight. Not a wrapper. Not a script. A real one. Which means the calculus flipped — the software you live inside can be yours, tuned to your actual data model, changed by asking.

The proof of that claim, below, is a browser. Built overnight. In use the next morning.

§ 02   /   hand this to your agent

The one link on this page that matters.

// point your agent here https://vita-reports.ham.xyz/s/9a608cd9fc56

A dense, agent-first build guide for scry: architecture, every hard-won gotcha with specifics, dependency pinning, CEF fetch mechanics, measured build times, and a stage-by-stage build order that mirrors what actually worked.

It is written to be handed to a coding agent verbatim: "build me this."

this page is the story. that link is the spec. if you only want one, you want the spec.
§ 03   /   lineage

Second in a line.

// context

scry is the second native app in this line. The first was seance — a GPUI desktop shell for running Claude agent panes. scry reuses its patterns (control socket, session persistence, dependency pinning) and is otherwise independent.

§ 04   /   what scry is

Native chrome around a composited web texture.

A native browser where everything outside the page rectangle is real native chrome: tab strip, omnibox, sidebar rail, command palette, context menus, prompts, toasts. Web content is Chromium (CEF 151) rendered offscreen into a texture and composited as one element inside a native GPUI window.

The first architectural bet, in one sentence: an Electron shell can only draw inside the webview rectangle, and the interesting affordances live outside it.

The second bet is about content. It is deliberately not a browser for the open web — Chromium keeps that job. It is a browser for pages you control: your local web app, your dev servers, your internal dashboards, your generated reports. That constraint is what buys everything else, because both ends of the connection are yours.

And the omnibox is not a URL bar. It is a fuzzy query over the surfaces you actually navigate — in this instance 984 entries across 8 sources: 760 generated reports (from a sqlite index), 71 internal sites, 63 app routes, 36 canvases, your own links store with a frecency prior from browser history, workshop pages, live dev servers. Sub-millisecond, no debounce, live on every keystroke. Substitute your own sources: notes, tickets, repos, runbooks. Chromium cannot do this for you because Chromium does not know what any of those things are.

§ 05   /   the seams

One host, three seams.

GPUI host — native chrome tab strip omnibox sidebar palette toasts menus process root · rust · owns the window composites one GL texture per tab cef-seam CEF 151 · offscreen render → GL texture · 59 paint/s child process · not sandboxed --no-sandbox index crate 984 entries · 8 sources fuzzy · sub-ms · no debounce reports · sites · routes · canvases links · workshops · dev servers cdp crate localhost:PORT text | md | shot | eval | navigate agents read your tabs no auth by design texture + input query CDP loopback window.scry in every page it renders declare actions · toast · open tab LEGEND solid = process boundary dashed = same process
fig 01 · seam structure. GPUI host owns the window; CEF paints into a texture; the index answers the omnibox; the CDP port lets your agents read what is actually on your screen; every page it renders gets a window.scry sidecar.
§ 06   /   demo-able moments

Five beats, all measured on one box, live.

takeover
0.51s
01 / singleton

Launch it again and the running instance hands over.

Launch it again from the app menu and the second instance takes the first one over: the old one flushes its session, replies to the handshake, dies; the new one comes up with the same six tabs. One window, always.

A wedged instance (SIGSTOP'd) escalates to SIGTERMSIGKILL and resolves in 5.43 s with tabs surviving via the last debounced flush. Three simultaneous launches produce a takeover chain and exactly one window.

scry showing two tabs with native tab strip, sidebar rail, and a page rendered by CEF composited by GPUI
shot-two-tabs.png — native tab strip and sidebar rail; the page is a synthetic fixture dashboard, rendered offscreen by CEF and composited by GPUI as a single texture.
live restart
0.65s
02 / dogfood loop

Rebuild in front of yourself, tabs intact.

$cargo build --release && ./run.sh ctl restart

Session flushed synchronously, reply confirmed on the wire, execv in place so the pid survives, tabs restored. When the shell prompt returns, the new binary is up.

This is the actual point of the project: "add X to my browser" is a prompt, and it lands under you in under a second.

page → shell
window.scry
03 / declared actions

Pages declare native palette actions.

// in any page scry renders
scry.actions.declare([ /* ... */ ])

window.scry exists in every page it renders. A page can call scry.actions.declare([...]) and those actions appear at the top of the native command palette (ctrl+shift+p) under their own header, above the built-ins — cleared on navigation, because a declaration belongs to one document.

Invoking one calls back into the page's own JS. A page can also raise a native toast in the chrome outside the web content, and open a real tab. Outside scry, window.scry is simply absent and the same page still works in Chromium.

scry command palette on the scry.js demo page showing a violet 'page actions' section
shot-palette.png — a violet page actions section (celebrate, open health) declared by the page, above the built-in rows. Greyed rows state why they are unavailable instead of silently disappearing.
the scry.js demo page reading scry.version out of the shell
shot-scryjs.png — the demo page reads scry.version back out of the shell it is running inside.
CDP verbs
5
text · md
shot · eval
navigate
04 / agents read tabs

Your agents read what you're looking at.

$scry-cdp text | md | shot | eval | navigate

The DevTools port is on (localhost only). An agent can read what you are actually looking at without re-fetching the URL — works behind session cookies, for client-side-only pages, for localhost dev servers — screenshot any tab including background ones, or put a page in front of you instead of sending you a link.

The division of labour learned the hard way: open tabs with the control socket, read and script them with CDP. CDP-created targets are invisible orphans the user can neither see nor close.

warm search
1.23s
05 / omnibox

Query over your own data, honestly moded.

Type; get your surfaces, ranked, with kind badges. Anything marked local-only wears a local chip in gold and refuses to mint a share link.

A leading ? switches to full-text search over long-term memory — an explicit mode, never keystroke-live, because it takes 1.2–3 s warm and the honest answer to that is a mode and a spinner, not a debounce.

the omnibox open over the jump index, query 'bro', with live fuzzy matches and a gold 'local' chip
shot-omnibox.png — query bro, live fuzzy matches with report / dev kind badges and a local chip on the local-only row.
These screenshots were taken in a deliberately isolated instance — scratch state dir, synthetic reports, a fake dashboard page — so they contain no real personal data. It is also the honest way to demo something that is otherwise wired straight into a personal system.
§ 07   /   honest costs
honest costs

The gaps, spelled out.

This audience trusts a page more for having them. Nothing here is softened.

CEF · 321 MB
prebuilt blob
You don't compile Chromium — the build script downloads and arranges the matching distribution — but you do carry it. The upside of that same fact: it sits outside the build path, so incremental release rebuilds are 18 s.
Sandbox · off
--no-sandbox
The prebuilt distribution ships chrome-sandbox without root:root setuid. This is a real gap, not a shortcut; it is survivable only because the trust model is "pages we control". Fixing it means setuid at install time or a userns sandbox.
Input · Latin-only
no IME, no dead keys
Keyboard is key name → VK + CHAR. Composition needs the toolkit's input-handler trait, which is a real piece of work.
Keys · US/evdev table
native_key_code
native_key_code is reconstructed from a US/evdev table — right logical key, wrong scancode on a non-US layout.
Paint · 59 paint/s
CPU texture path
At laptop resolution, at the CEF frame cap. It will not scale to 4K; the zero-copy DMA-BUF path needs GPUI to expose its wgpu::Device, which is a fork-or-upstream decision.
Auth · 0600 mode
no CDP auth
The control socket's 0600 mode is the entire access control, and CDP has no authentication by design. Anything running as you can drive the browser and read every open tab.
window.scry · no origin gate
every page gets it
Bounded by: no new privilege (every verb is one the user already has in the palette), nothing runs without the user, and hard shape limits — 64 KB/message, 12 actions/tab, 200 chars/string.
Absent · many features
linux only
No find-in-page, downloads, JS dialogs, file pickers, drag-and-drop, or in-window devtools. Linux only.
§ 08   /   the method

The transferable part.

// built overnight by an agent

ooda rounds
6
crates
4
tests
156
commits
9
scaffold + spike 13:45 spike passes 14:15 v0.1 browser 22:15 sidebar + palette + CDP 23:00 ctl restart 23:20 page ↔ shell bridge 00:15 search + README 00:20 ~10.5 hours end-to-end · nine morning asks turned up three bugs

Timeline roughly: ~13:45 scaffold + de-risk spike (does CEF-OSR-into-GPUI even work at usable latency, with input?) → spike passes at ~14:15 → v0.1 working browser with tabs, omnibox and session persistence by ~22:15 → sidebar + palette + CDP by ~23:00 → control socket, lossless restart and <select> compositing by ~23:20 → the page↔shell bridge by ~00:15 → search mode, README, desktop entry by ~00:20. Next morning: nine asks against a shipped shell, which turned up three bugs the batch would otherwise have shipped.

What made that possible is not model magic. It is method, and it is the transferable part.

01

Retire the existential risk first.

One unknown gated the whole plan; a disposable spike with four explicit pass criteria answered it in 30 minutes. Everything after was breadth, not risk.

02

Parallelize only on disjoint crate boundaries.

Stated out loud in the brief — "agent B never edits the app crate" — so two agents can run concurrently without stepping on the same file.

03

Demand an honest quirk log every round.

Including the measurements that came out bad. The dirty-rect optimization won 0.5 % and was documented as not a speed feature. "Clean shutdown fixes zombies" was wrong — the kernel already reaped them; the real win was an orderly profile flush. Those corrections are worth more than the feature list.

04

Audit the verification, not just the deliverable.

One round's "screenshot of the help overlay" showed no overlay. Re-verifying live found the feature worked and that a different real bug sat next to it. Another round's test count was wrong because the orchestrator summed lines wrong. Your own reporting is an instrument, and instruments lie.

05

Ship the dogfood loop early.

Once ctl restart existed, every later change landed in front of the user in 0.65 s. That changes what it feels like to ask for a tweak, which changes how many tweaks get asked for.

The claim to leave you with, stated plainly and without overselling:

The software you spend your day inside is now something you can own.

Not "customize" — own. The build guide is a working recipe, the numbers in it are measured, and the whole thing was one night.