ADR-0017: the chromium substrate - chromium for apps and the agent, firefox for the human
Status: accepted (2026-07-10)
Context
Chromium was dropped at synthesis as an Arch-policy concern (docs/dropped.md: “no Arch, no chromium”) and firefox was made the sole browser (DEVIATIONS.md: “browser: firefox, not chromium”). With no firefox –app, the webapp roster opened as plain firefox windows - a documented UI deviation (DEVIATIONS.md: “webapps: open as plain firefox windows”). Two needs now reverse that stance:
- Upstream Omarchy’s borderless webapp UX is chromium –app; trellis had patched it away (omarchy/patches/0001-webapps-firefox.patch). Restoring it returns webapps toward the authentic upstream shape (ADR-0005).
- The agent (ADR-0008/0015) has no browser it can drive; the only agent surface is claude-see (read-only screenshots). A real, visible, isolated browser the agent can navigate and act in is a new capability worth building deliberately.
Decision
One chromium install (pkgs.chromium, beside firefox), two isolated profiles.
- Profile “you” (~/.local/share/chromium-apps) backs borderless app windows: omarchy-launch-webapp is re-pointed to chromium –app on this profile, returning webapps to the upstream shape. Firefox stays the human’s browser and the default http(s)/html handler (unchanged).
- Profile “agent” (~/.local/share/chromium-agent) is a clean, isolated, VISIBLE chromium the agent drives over CDP (the claude-web actuator, Playwright-from-nixpkgs in connect-over-CDP mode - zero npm). It is never headless, summoned on demand (trellis browse), red-bordered, and every drive action fires a mandatory announce toast - the claude-see fence contract (ADR-0008) extended to the browser.
- Chromium’s Arch policy/flags theming machinery stays dropped (omarchy-theme-set-browser stays a no-op); only the package rejoins, declared natively in the flake.
Design: docs/specs/2026-07-10-chromium-substrate-design.md. Built in four reviewed PRs (PR0 this ADR + spec; PR1 chromium + profiles + app-window flip; PR2 the visible agent browser + summon verb; PR3 the claude-web CDP drive).
Alternatives considered
- Firefox-only + a headless agent browser: loses the upstream webapp UX and makes the agent’s browser invisible - exactly the silent, unannounced actuation the fence forbids (ADR-0008). Rejected.
- One shared profile for apps and the agent: the agent would inherit the human’s logged-in sessions and could act as Sam everywhere. The creds-blast-radius is the whole reason to isolate; rejected.
- An npm browser MCP server (chrome-devtools-mcp / playwright-mcp): MCP is native to Claude Code, but every mature browser MCP server is an npm package running a persistent node process - the exact supply-chain surface the npm boundary hardens against (docs/AGENT.md). Playwright from nixpkgs driven over CDP gets the same capability with zero npm. Rejected.
- ungoogled-chromium: strips the Google API keys the “you” profile needs to sign into Google/YouTube/Maps. Rejected for pkgs.chromium.
Consequences
- The four “chromium is absent” records are reconciled in PR1 (DEVIATIONS.md
browser/webapps/exclusions lines, dropped.md, packages.nix header):
chromium rejoins as the app + agent browser, firefox stays the human
default. The webapps-as-firefox deviation is reversed; what remains a
deviation is the split itself (firefox default while chromium serves –app
- the agent).
- The agent gains a web actuator with NO new escalation surface: it is a user-space CDP client to a user-space browser on a loopback port, invoking no sudo/su/pkexec/doas/run0. The fence (checks/fence-policy.nix) is untouched. The new blast radius (a browser reachable by a possibly prompt-injected agent) is contained by four walls: a clean profile, a loopback-only debug port, a URL-policy gate (no file://, no localhost, no private/metadata ranges), and the mandatory announce.
- Chromium enters the closure (a prebuilt cache download, not a source build). Profiles live under the persisted /home, surviving the tmpfs root.