Shell & rendering (Prism / Xenon)
This layer is the window the user actually looks at. Everything below it — the device key, the serving tunnels, the fleet catalog — exists so that a person can open one trustworthy app and, from inside it, run every other app their computer offers. That one trustworthy app is Prism: the super-app shell. The browser it prefers to run inside is Xenon, Agent54’s Isolated-Web-App-enabled Chromium. This chapter describes both as they are built today, and marks — loudly — the two places where the design has run ahead of the code.
As-built truth. Code citations are repo-relative in
agent54/prismatmain @98acaa3; every quoted header, manifest, and line number below was re-read against that commit. The Xenon fork is described as recorded in the HQ packets (P-171, P-190/191, P-192, P-195/197) andagent54/android-iwa— no Chromium tree was cloned. Where design and code diverge, this chapter says so and points at the design review. Decisions are cited as d/NNN against the decisions ledger.
Primer: the words this chapter assumes
You do not need any prior browser-internals knowledge to read this chapter. The five terms below are all you need, and each links to its upstream source.
What is an Isolated Web App (IWA)? An ordinary website is fetched from a server over HTTPS and its identity is a domain name. An Isolated Web App is instead a bundle of files signed by a key; its identity is the key, and it runs at a special
isolated-app://origin the operating system installs like a native app. Because the whole app is signed and sealed, the browser can safely grant it stronger capabilities than a normal web page. Explainer: WICG/isolated-web-apps.What is a signed web bundle (
.swbn)? The file format an IWA ships in: a single.swbnfile that packs every resource (HTML, JS, CSS, icons) plus a signature. The signing key derives the app’s origin, so the origin cannot be forged without the key. Spec + tooling: WICG/webpackage · signed-web-bundles explainer.What is a Controlled Frame? A
<controlledframe>is like an<iframe>that only an Isolated Web App is allowed to use. It embeds another app, but gives the host app (Prism) far more control over the embedded guest — its own storage partition, the ability to observe or script it, navigation control — while keeping the guest strongly isolated from the host. It is the mechanism by which one super-app can host many ordinary apps. Explainer: WICG/controlled-frame · spec: wicg.github.io/controlled-frame. It is a real Chrome API, currently gated to IWAs.What is a Content-Security-Policy (CSP)? A set of rules, shipped as an HTTP header, that tells the browser exactly which origins a page may load scripts from, connect to, or frame. A tight CSP is a declared trust boundary: anything not on the list is refused by the browser itself. MDN: Content-Security-Policy.
What is Xenon / Helium? Helium is a privacy-forward Chromium build that ships Isolated-Web-App support. Xenon is Agent54’s IWA-enabled Chromium fork in that same family (Helium is its base; d/053) — the browser the Xe Computer offers to download when no IWA-capable browser is already present. Chromium project: chromium.org.
Zoom 1 — the shape of the layer
Three things make up the shell & rendering layer, and they nest:
- Prism — the super-app shell. Prism is itself an Isolated Web App: a signed bundle that installs like a native app and shows no address bar. Its whole job is to host other apps and broker what moves between them.
- Controlled Frames — the panes inside Prism. Every ordinary app the user
runs is loaded into a
<controlledframe>: partitioned, unprivileged, and observed only through named channels. This is where isolation lives. - Xenon — the browser Prism prefers to run in, because hosting Controlled Frames requires IWA support that not every browser guarantees. On Android that support is delivered by a small patch series, not a fork (below).
The design rule that organises all of it is d/101 — most apps are just
http(s). An ordinary app needs no signing, no bundle, no special manifest; it
is served plainly at a local origin (http://<app>.localhost:5454, d/100) and
Prism’s Controlled Frame supplies all the isolation. The heavyweight IWA
machinery — signing, .swbn, cross-origin isolation — is reserved for the
shell itself. Prism is, in the words of its own product copy, “a super web
app: interchangeable, never privileged — swap this shell and keep your apps,
your data, and your journal” (src/spectrum/spectrum.ts:163-165). That single
sentence is the sovereignty litmus’s exit-without-loss criterion written
into the product.
flowchart TB
subgraph OS["Device OS · Xenon (IWA-enabled Chromium)"]
subgraph IWA["Prism — the super-app shell (isolated-app://, signed bundle)"]
direction TB
home["Fleet catalog home<br/>iwa-controlledframe.html"]
jr["Hash-linked journal<br/>(tamper-evident, on-device)"]
ch["FrameChannel<br/>port + nonce + schema"]
cf["controlledframe<br/>(one visible pane)"]
home --> cf
home --> jr
cf <-->|"tab.* verbs only"| ch
end
plane["Xe local plane · 127.0.0.1:5454<br/>/xe/api/catalog"]
end
cf -->|"src = launch_url<br/>http://app.localhost:5454"| appA["ordinary app A<br/>(plain http, unprivileged guest)"]
home -->|"reads catalog (connect-src)"| plane
plane --- appA
Zoom 2 — the subsystems
Prism is an Isolated Web App
Prism ships as a signed web bundle built with the standard Google IWA toolchain
(wbn + wbn-sign). The recipe (packaging/bundle/Makefile:11-24) stages the
web assets, derives the app origin from the signing key, builds an unsigned
bundle, then signs it into evidence/prism-dev.swbn. The proven bundle is
163 297 bytes with a key-derived origin id of
f4ujayjmxhlpwhujmxd63rxuwlmok3xxduygwx3r46y5ad6ltjyqaaic
(evidence/prism-iwa-status.md:12-15).
The signing today is development signing, and the app is honest about it:
its installed name is literally “Prism (DEV-SIGNED)” (web/manifest.webmanifest),
so the trust tier is visible in the app title itself. The packaging README
states the limit plainly: “The development signature proves bundle consistency
only. It does not establish production identity or independent review.
Production signing needs the Xe Computer key ceremony and must not reuse the
development key” (packaging/bundle/README.md:18-21). That matches d/066,
which lets the orchestrator hold development-tier keys for the build phase
only; the real key ceremony (Luke + Jan, offline root) is still ahead. The
review tracks this as G1.
Prism was installed and rendered end-to-end on an Android emulator (P-195):
InstallIsolatedWebAppCommandSuccess, launched standalone with no address bar,
with a bundled demo app rendering inside a real HTMLControlledFrameElement
(merged prism PR#17 @5b2ed70).
Controlled Frames — where isolation lives
Prism’s home page (web/iwa-controlledframe.html) hosts one visible
Controlled Frame, created programmatically in src/iwa-controlledframe-app.ts
(ensureFrame, :196-218). Every guest app is loaded into it. Two properties
make the frame an isolation boundary rather than a mere embed:
- Partitioning. The frame is pinned to the storage partition
persist:prism-fleet-apps(:58). A guest’s cookies, storage, and DOM live in that partition; the shell’s own storage is a different address space the guest cannot reach. (A second, hidden partitionpersist:prism-fleet-catalog,:59, is used only as a transport to read the catalog JSON when Prism runs as an installed IWA and cannotfetch()the loopback plane directly — it navigates a throwaway frame to the endpoint and reads the text back out,:266-316. A clean, if roundabout, seam.) - Named-channel-only privilege. The guest gets a
src, a partition, and nothing else by default. It cannot reach the shell’s control surface except through one authenticated channel (below), and the only capabilities on that channel are four tab verbs. d/101 states the posture directly: “isolation is already delivered by the controlled frame (partitioned, unprivileged guests).”
The privilege that does exist runs the other way: the shell can call
executeScript into the guest (:236) to read it — the “observed” rung of the
agency ladder, below. The guest cannot script the shell.
The baked CSP — the trust boundary in one line
Every response inside Prism’s bundle carries a Content-Security-Policy baked in
at build time (packaging/bundle/headers.json). It reads, verbatim:
content-security-policy:
default-src 'self';
connect-src 'self' http://127.0.0.1:5454;
frame-src 'self' http://*.localhost:5454;
img-src 'self'; style-src 'self'; script-src 'self'
Read plainly, this is the local-trust boundary: Prism’s own scripts, styles
and images are self-only; the only origin it may connect out to is the
local plane http://127.0.0.1:5454 (to read the catalog); and the only
origins it may frame are the per-app plane origins http://*.localhost:5454
(d/100). The same header file sets cross-origin-opener-policy: same-origin
and cross-origin-embedder-policy: require-corp, which together put the shell
into cross-origin isolation — the mode a browser requires before it will
let a page host Controlled Frames at all.
So the shell’s trust reduces to one sentence: Prism trusts whatever answers
on 127.0.0.1:5454 and *.localhost:5454. What binds those ports, and how
that binding is authenticated, is the subject of
G4 in the review.
The fleet catalog — the launch seam (d/104 PROPOSED)
Prism’s home page renders a catalog of apps across the user’s fleet, fetched
from GET http://127.0.0.1:5454/xe/api/catalog (schema xe/fleet-catalog/v1,
validated at iwa-controlledframe-app.ts:111-131). Each entry carries a
launch_url — a plane origin like http://app.localhost:5454. Offline apps
are greyed and disabled with a last-seen time, never hidden
(renderCatalog, :161-193) — honesty over tidiness.
Launching an app is one line: openEntry() sets the frame’s source to the
catalog’s launch URL —
activeFrame.src = entry.launch_url; // iwa-controlledframe-app.ts:258
⚠ PROPOSED, pending Luke — d/104. This “catalog descriptor is the launch seam” is a proposed architecture decision, not yet ratified. The code above already works this way on desktop; d/104 asks Luke to bless the catalog descriptor (
app label → http://<app>.localhost:5454) as the single controlled-frame launch seam on every platform, rather than inventing a separate install-time launch contract per app. It matters because Android’s frame-cell story depends on it: P-202 could not repoint an installed Android Prism from its baked guest URL to a new plane origin through any seam it was allowed to touch — the fix is mechanical once the catalog build is the launch path. Until Luke disposes, treat the Android frame-cell as blocked on this decision. See d/104 and the review’s session questions.
The agency ladder — how much the agent may touch
The shell does not offer a single “run this app” mode. It offers a ladder of increasing agency, from a page the agent never touches to the shell acting on the user’s behalf. The rungs are Luke’s own words (d/082, extended by d/091), and the rule riding along every rung is consented + receipted + undoable, and never faked — a rung that needs installed mode says so; a denied permission is a first-class demonstration state, not a failure.
flowchart TB n["1 · nothing<br/>a plain tab, untouched"] a["2 · allowed / denied / shielded<br/>the page asks; the agent answers —<br/>allow, deny, or a user-chosen value"] o["3 · observed<br/>read out: DOM + requests (copy data OUT)"] e["4 · enhanced<br/>add in: styles, elements, annotations"] m["5 · mutated<br/>filter/rewrite requests on the user's behalf"] c["6 · controlled<br/>the agent acts in the page: navigate, click, fill"] d["7 · driven<br/>full automation over the DevTools (CDP) layer"] s["8 · composed / generated / delegated<br/>carry between apps · A+B→C · resident agent acts"] z["9 · super user agent<br/>the shell itself: hosts, brokers, journals"] n --> a --> o --> e --> m --> c --> d --> s --> z
The middle rungs are implemented in Prism’s Spectrum surface; the “driven” rung runs live over the Chrome DevTools Protocol (d/091). Crucially, each rung above “nothing” leaves a receipt in the journal, and permission answers can be shielded — the agent returns a user-chosen value (an approximate location, a fixed answer) so the page never sees the real device. The design status of that shielding, for installed apps and for capabilities beyond geolocation, is the headline gap of this beat: G6.
The Prism↔app channel — a deliberately tiny surface
An app can talk back to the shell, but only through one narrow, authenticated
door. The transport is a MessageChannel: one port is transferred to the guest
with its origin pinned, and a 128-bit nonce is sent only over that transferred
port. The shell accepts a message only after three checks in order — port
identity → nonce equality → exact {nonce, verb, args} schema
(docs/channel.md; src/channel/message.ts:41-63). The entire verb
vocabulary is four items:
tab.open{url} · tab.close{} · tab.reload{} · key{key} // message.ts:15
Every accepted verb is journaled; every rejection is journaled too, with the
offending origin and the check that failed (src/channel/frame-channel.ts:18-35).
A static lint (scripts/check-channel-boundary.mjs) forbids control ever
riding over console output, so this port is the only control path. The
channel’s own documentation is candid about the limit: “any script that obtains
the transferred port and nonce holds the capability” — the door is small, but
whoever holds the key is trusted. Extending this surface (window titles,
notifications) is G2.
The journal — tamper-evident memory
Prism keeps a hash-linked journal in on-device storage: each record carries the
SHA-256 of its predecessor, so the log is tamper-evident and can be verified
after export by a CLI (journal:verify). Navigations, channel rejections,
permission answers, and leased reads all land here. This is the shell’s
contribution to the sovereignty litmus’s auditable actions criterion — the
user can inspect, and prove, what their agent did.
Xenon — a patch series, not a fork
Hosting Controlled Frames needs IWA support, and not every browser ships it reliably, so the Xe Computer offers Xenon — Agent54’s IWA-enabled Chromium (Helium-based; d/053) — as the download when none is present. It is important to be precise about what “the Xenon fork” actually is on Android today:
It is not a full Chromium fork.
agent54/android-iwais, in the words of the forge registry, “patch files + gn args + validation scripts, NOT a full chromium fork” — a small series of three patches on a stock Chromium base, plus build arguments and test scripts:
- 0181 — a scoped
isolated-appbaseline permissions policy plus a guest-view gate, which fixes the bug where Controlled Frame would not register on Android;- 0182 — scoped IWA header injection (
.well-knownmanifest allow + cross-origin isolation);- 0183 — standalone launch through the existing web-app activity (own task, no address bar).
The choice was to ride Chromium’s desktop-android variant plus a targeted Controlled-Frame carve-out, rather than build the whole extensions stack on Android (P-171). Because Controlled Frame and IWAs are real, upstream Chromium features (currently in origin trial), the divergence is deliberately small — which is the whole point, and the subject of G5, fork maintenance. It was proven with an arm64 APK and a kitchen-sink IWA installed on the emulator, with a rollback APK preserved.
Two engines, one bundle (P-192)
A quiet but strategically large result: the same signed bundle
(hello-xe-0.6.2.swbn) was rendered by two different browser engines — on
Blink (Chromium) via a real IWA install, and on Servo (Mozilla’s
independent Rust engine) via an isolated-app://-style scheme handler that
serves the bundle’s bytes only after verifying its Ed25519 signature offline
(a tampered bundle is refused). This is what turns “a Chromium-fork feature”
into “a web-platform shape with more than one implementation.” It is described
honestly as CF-shaped, not CF-strong: the Servo side has the API surface
only — no process isolation, OS sandbox, or cross-origin isolation yet. Why it
matters for the standards story is drawn out in the
review.
Zoom 3 — module detail
The baked headers (exact)
| Header | Value | Effect |
|---|---|---|
content-security-policy | default-src 'self'; connect-src 'self' http://127.0.0.1:5454; frame-src 'self' http://*.localhost:5454; img-src 'self'; style-src 'self'; script-src 'self' | Self-only code; one connect target (the plane); framing limited to plane app origins |
cross-origin-opener-policy | same-origin | Isolates the browsing-context group |
cross-origin-embedder-policy | require-corp | Every subresource must opt in |
cross-origin-resource-policy | same-origin | Blocks cross-origin resource leakage |
The last three together yield crossOriginIsolated: true, without which the
browser refuses to host a Controlled Frame.
The channel verb set (exact)
| Verb | Args | Journals as |
|---|---|---|
tab.open | {url} | navigate.tab |
tab.close | {} | navigate.tab |
tab.reload | {} | navigate.tab |
key | {key} (non-empty string) | navigate.tab |
Any message failing port / nonce / schema is refused and journaled as
channel.reject with the origin and failing check. There is no verb for
titles, notifications, focus, or liveness — see G2.
Partitions
| Partition | Purpose |
|---|---|
persist:prism-fleet-apps | The visible guest pane; each app’s storage lives here |
persist:prism-fleet-catalog | Hidden; a transport to read the plane catalog when installed |
An honest discrepancy: composition still uses iframes
Prism has a Composition surface (its root /, web/index.html) that frames
two demo apps side by side and carries a consented, receipted, undoable “carry”
record between them. It is a genuine and working demonstration — but it is
built on plain <iframe> elements (web/index.html:28-29), not
Controlled Frames. The fleet home uses real Controlled Frames; the composition
surface has not yet migrated to them. Stated plainly here so the book never
implies uniform Controlled-Frame composition everywhere; the migration path
(d/103, compose UIs as plane origins) is G3 in the review.
Where this layer meets the rest of the stack
- Below: the plane origins Prism frames (
http://<app>.localhost:5454) and the catalog it reads are produced by the serving plane and peer serving & the fleet. The CSP’s two allowed origins are exactly that layer’s outputs. - Beside: what makes an app installable and which tier it belongs to is the app model (d/101 lives on both beats).
- Above: the agency ladder is where the agent layer — darc, becoming Reagent (d/053) — reaches into panes to observe, enhance, and drive.
Links
- Isolated Web Apps: WICG explainer
- Signed web bundles / web packaging: WICG/webpackage · signed-web-bundles explainer · tooling webbundle-plugins
- Controlled Frame: WICG explainer · spec
- Content-Security-Policy: MDN
- Helium: github.com/imputnet/helium · Chromium: chromium.org
- Servo (second engine): servo.org · github.com/servo/servo