apps/web is the installable React/Vite PWA. It has a stable v2 WebSocket client, reconnects to its configured Hub, and never discovers Agents directly. It groups threads by the App Server's canonical cwd, keeps editable pending messages in browser memory, and sends same-turn guidance through the stable turn.steer method.
apps/hub authenticates users, creates pairing codes, binds device identities, tracks presence, and routes request/response/event frames. SQLite contains metadata and audit action names, not Codex content. MetadataStorage is the boundary for a future PostgreSQL implementation.
apps/agent is a user-level macOS/Windows process. Pairing writes a pinned configuration under the user profile. The Agent opens WSS outbound and translates stable remote calls into codex-adapter operations. Uploaded attachments are validated and written with user-private permissions under ~/.codex-remote/attachments, then passed to Codex as localImage or mention input.
The Agent enforces a single-writer invariant for Codex history. When the local desktop host is running, remote list/read operations remain available but mutations are rejected. When resuming an existing thread after the desktop has closed, the Agent restarts its App Server before loading the thread so no stale in-memory branch is reused. This guard is required because separate App Server processes do not share loaded-thread or event-subscription state.
packages/codex-adapter is the only package that knows Codex App Server method names or generated experimental types. packages/protocol is the stable network contract.
flowchart TB
subgraph PERSONAL["Personal security domain"]
PW["Personal PWA"] --> PH["Personal Hub"]
PA["Personal Agent"] --> PH
end
subgraph COMPANY["Company security domain"]
CW["Company PWA"] --> CH["Company Hub"]
CA["Company Agent"] --> CH
end
PH -. "no route" .- CH
APP_MODE must equal TRUST_DOMAIN. A company Agent persists its exact Registry URL and rejects replacement or a company-to-personal switch. Registry pairing responses must echo the same URL and trust domain requested by the Agent. There is no network-based automatic selection.
- User logs in and receives a short-lived signed credential.
- PWA opens authenticated WSS and sends
rpc.requestwith protocol version, request ID, device ID, and timeout. - Hub verifies device ownership and online connection, creates a volatile timeout route, and forwards the frame.
- Agent applies the desktop single-writer guard, calls the Codex adapter, and returns
rpc.response; streamed normalized notifications arrive asagent.event. - Hub forwards frames to the owning browser and removes volatile route state. No body is written to SQLite.
For attachments, the browser base64-encodes at most four bounded files inside the RPC frame. The Hub routes the frame without inspecting or persisting it. The Agent decodes and stores the file locally before calling Codex. The current queue is browser-memory state; queued bodies never enter the Hub until dispatched.