Use a same-origin portal to add an agent popout without a second stream or startup delay

When a web product needs a dedicated agent window without adding startup or turn latency, keep the stateful agent controller in the original page and render its existing UI through a same-origin React portal into a synchronously opened blank window. Dispatch session startup immediately after window.open succeeds—before document construction or stylesheet cloning—so UI setup overlaps the existing request. This preserves one stream owner, parent-tab cleanup, and tab-scoped session storage. It applies when both views are same-origin and share one JavaScript lifetime; it is not appropriate when the child must survive parent closure or reload independently.

Method

  1. Open the blank window directly from the user gesture to avoid popup blocking.
  2. Null the child opener immediately and use a random, non-sensitive window name.
  3. Start the existing agent session before constructing/styling the popup.
  4. Portal the existing stateful controller’s surface into the child; do not create a second route, controller, poller, or turn stream.
  5. Keep a full-screen same-tab fallback for blocked popups.
  6. Keep synchronous in-flight guards against duplicate session creation.
  7. Warn before closing during a live turn, while leaving the parent-owned controller running if the child is closed.
  8. Prevent navigation that would unmount and orphan a single-capacity active workspace.

Limits

A portal child depends on the parent page remaining open. If independent refresh/recovery is required, use a dedicated route plus server-authoritative session lookup and explicit cross-window coordination instead.

Evidence/example — 2026-09-10

Source: local repository implementation and tests in candidate-signal. The hosted Codex candidate surface was changed from an inline panel to an E3-branded popup portal with the existing authenticated NDJSON turn path unchanged. Startup dispatch was ordered before popup DOM/style work, a synchronous start guard was added, popup opener/name exposure was reduced, and blocked popups fall back in-page. Type checking, the production web build, and the full 314-test suite passed.