Skip to the content.

RFC 0028: Browser Runtime & Render Pool

Status: Draft

Summary

Make the logged-in browser a first-class daemon runtime instead of one shared external CVM. The daemon runs a pool of isolated browser containers in the pod; a browser-path read (twitter timeline, any screenshot/DOM plugin) leases one, the requester’s cookie jar is injected for that lease via the credential broker, the browser is driven, the result returns, and the container is reset (cookies/storage cleared) before the next lease. This replaces login-with-anything — a single Neko CVM with one global session that any pod user could read and any one call could lock.

Problem (what today’s single CVM gets wrong)

Today oauth3/browser.ts drives one external CVM (login-with-everything) over its bridge:

Design

  1. A browser runtime in the daemon (proxy/runtimes.py), alongside deno/image. The daemon already spawns per-project isolated containers with health checks (RFC 0009), restart (0010), and an egress proxy; a browser container (Neko/Chromium + the bridge) is one more managed image.
  2. A warm pool. N browser containers on the pod’s internal network behind the egress proxy. A lease picks a free one (or spawns up to a cap); a queue with per-lease timeouts provides fairness so one caller can’t starve the rest.
  3. Per-lease jar injection, per-lease reset. On lease, the requester’s jar is injected for the target domain only; on release, the container is reset (clear cookies/storage/nav) before it returns to the pool. No cross-user residue, no shared login. browserFeed’s “read the current session” antipattern is deleted — every read carries its own jar.
  4. Auth by broker, not a shared secret. The lease is a scoped delegation (RFC 0018): the jar reaches the leased browser through the broker socket, path-scoped to that plugin/subject, not a flat bridge password. The interim BRIDGE_SECRET retires with the external CVM.
  5. Attested like any project. Browser containers carry the daemon’s mode axis (#16), so a consumer can verify the render code (RFC 0020) the same way as any app.

What finishes it (the dependency ladder — answering “if we wait for dev to lock in”)

| Piece | State | Needed for the pool | |—|—|—| | Daemon container runtime + health/restart/durability | built (0009/0010/0017) | the substrate a browser runtime reuses | | Credential broker — scoped, expiring jar delegations | spec’d (RFC 0018), impl pending | how a leased browser gets only this user’s jar | | Attestation evidence | spec’d (0020) | so the render is verifiable | | browser runtime type (spawn Neko+bridge as a managed runtime) | new | the core of this RFC | | Lease/pool/queue + per-lease reset | new | isolation + fairness (the multi-user fix) | | oauth3 browser.ts → pod pool endpoint (drop the external CVM) | new (small) | cutover |

So: the substrate exists; the new work is the browser runtime + the lease/pool/reset layer, riding the credential broker (0018) for per-user jar scoping. Until 0018 lands, a leased browser can’t be given a properly-scoped jar, so 0018 is the gating dependency.

Non-goals / relations

Decisions

Open questions