Skip to content

Overview

The top of the session's ⋯ menu shows the coding agent's current context (used out of its window) and how much of its session, day or week limit is used, when the agent reports them. Pin the menu to keep it visible while working. Missing figures show a dash. Only the owner sees usage; it travels encrypted, with no extra terminal connection. The Enter button sits above the floating session picker, below the arrow pad. Phones use a tighter arrow pad and slightly smaller Enter and picker buttons; tablets keep their existing sizes. Codex usage follows its open conversation, including when you resume one or run several sessions in the same folder. If the computer cannot identify it, figures stay unavailable. Codex's limits are asked of Codex itself every few minutes while you use it, so they match its own status screen, and the box says how old they are.

agent -> your machine -> encrypted report -> info -> pin to keep open
arrow pad -> Enter -> floating session picker

roamd has three parts. Only two of them ever hold keys that can unlock anything: your browser and your computer. Our website sits in the middle and passes along messages it can't read.

   YOUR BROWSER                OUR WEBSITE              YOUR COMPUTER
 +--------------+          +----------------+        +----------------+
 | your vault   |          | your email     |        | roamd program  |
 | (unlocked    |          | locked vault   |        | its own ID key |
 |  only here)  |          | list of your   |        | which browsers |
 |              |          | computers      |        | it trusts      |
 +------+-------+          +-------+--------+        +-------+--------+
        |                          |                         |
        |   locked conversation, end to end                  |
        +==========================+=========================+
                          (passes through, unreadable)

The three big ideas

  1. Your password never leaves your browser. Logging in is a short back-and-forth that tells us only "right" or "wrong", and gives your browser alone the key to your vault.
  2. Your data is locked before it reaches us. We store it, but only as a locked box.
  3. Your computer decides who gets in, not us. It only accepts browsers you approved while sitting at it, after checking a 6-digit code on both screens (and people you share it with, after you and they compare a code).

Why the computer connects out to us

Home internet connections don't let outsiders connect in. So roamd on your computer calls out to our website and keeps that line open, a bit like leaving a phone call connected. When you click Connect, your browser's locked conversation travels down that line.

How the page is built

The page you use is built from small, separate pieces. The pieces that hold your keys and talk to the website are kept apart from the pieces that only draw buttons and text, so the parts that matter for safety are few and easy to check.

The terminal is drawn in your browser. Mouse selection follows the visible letters, including when its pane is scaled; choosing text stays in your browser unless you let the remote program handle the mouse.

visible letter -> mouse position -> selected terminal cells

Saved conversations are read on your computer through the connection already open. Windows uses its built-in PowerShell. A failed lookup shows an error; it does not say there are no saved conversations unless the lookup succeeded and found none.

open connection -> read saved conversations -> list or error

On phones and compact tablets, Open sessions in the main menu returns to your selected terminal. Refreshing after opening a terminal stays on sessions. Tap its title or the floating sessions button to choose another. Phones and portrait tablets show short tabs, such as AS:cod, using the first two machine letters and three session letters. Swipe sideways for more; a thin line above the tabs shows your position in the row. The picker keeps full names and close buttons. The selected tab keeps its bold underline and coloured fill after a tap. Four arrow keys form a D-pad above the picker button, keeping the keyboard open. They replace the arrows in the compact keyboard row.

main menu -> Open sessions -> selected terminal -> refresh -> sessions
AS:cod tabs + top scroll line -> swipe / choose session
arrow D-pad
    |
floating sessions -> full-name picker
    |
  Enter

The floating Enter button submits the current terminal input and dismisses the keyboard. Return on an iPad, iPhone or Android keyboard does the same; tap the terminal to type again. Tap the terminal again when you want to type.

mobile Return / floating Enter -> submit -> keyboard dismissed

Read on:

Phone-width floating controls use a tighter D-pad grid and smaller Enter/picker circles; arrow touch targets and tablet dimensions are preserved.

Agent usage is read locally and included in owner-only encrypted session reports, stripped from guest reports. Only the newest figures are read, from the end of the agent's record. Unknown fields, unsupported agent formats and ambiguous conversation matches remain unknown. No pricing or model capacities are inferred. Pinning keeps the non-modal usage popover open while the selected session changes. Codex is matched to its open conversation file through local process inspection, including native launcher children, before project/time matching. This handles resumes and shared project folders without trusting a newest-file guess. Mac and Windows builds are checked; physical runtime verification of the new file matching on those platforms remains pending. Claude Code's limits and window come from its status line, which an owner can let roamd become with one switch at the top of the session's ⋯ menu; any status line already there keeps running through it. Codex's limits are read live by asking its own app-server, using Codex's own login, and the newer of that and the record's figure is shown, with the time it was read.

local usage -> signed encrypted report -> owner session stats
guest report -> usage removed
arrow pad -> Enter -> session picker (compact layout)

Components

 +------------------ browser --------------------+
 | web app + roamd core compiled to WebAssembly  |
 |  - OPAQUE + Argon2id logins, XChaCha20 vault  |
 |  - SSH client, host-key pinning               |
 +----------------------+------------------------+
                        | HTTPS + secure WebSockets
 +----------------------v------------------------+
 | hub (Rust)                                    |
 |  - accounts: email, OPAQUE record, vault      |
 |  - device registry: owner, host public key,   |
 |    token hash                                 |
 |  - relays bytes between browser and machine   |
 |  - strict CSP; rate limits                    |
 +----------------------^------------------------+
                        | secure WebSocket, outbound from the machine
 +----------------------+------------------------+
 | roamd agent (Rust) on the user's machine      |
 |  - SSH server: public-key auth only           |
 |  - approved keys = browser keys confirmed     |
 |    on this machine                            |
 |  - terminal, files, status, updates  |
 +-----------------------------------------------+

How the web app is built

The page is React 19 (routing by wouter) over plain TypeScript modules. Anything that holds a key or talks to the network is a plain module with no React in it; components only draw what the modules say and call them. Keys and secrets live in module variables, never in React state, and the vault key is wiped from memory when the page locks (see Accounts and the vault). The page's strict Content-Security-Policy is unchanged: React needs no eval and no inline scripts.

 +-----------------------------------------------------------+
 | components (React): panels for login, devices, settings,  |
 | terminal, voice. They draw state and call actions.        |
 +-----------------------------+-----------------------------+
                               | read state / call
 +-----------------------------v-----------------------------+
 | plain modules (src/lib): session, vault, login, devices,  |
 | terminal, voice. Keys and secrets stay here, in memory.   |
 +-----------------------------+-----------------------------+
                               | calls into
 +-----------------------------v-----------------------------+
 | roamd core (WebAssembly): OPAQUE, vault, SSH, pairing     |
 +-----------------------------------------------------------+

Every build runs the strict TypeScript checker, and the browser tests drive the real page against a real hub and real machines.

Terminal drawing and pointer mapping share a local cell grid. Font advance is measured with the canvas font API; pointer coordinates are converted from the screen's rendered bounds before selection, link lookup or remote mouse reporting. This avoids mixing scaled viewport pixels with local drawing pixels. Browser tests cover both engines and pixel densities; the owner confirmed the reported selection issue is fixed on Mac.

viewport point -> screen bounds -> local grid -> cells
                                           -> mouse report

The conversation reader starts a temporary helper on your computer over the existing encrypted terminal connection. On Windows its input is sent as UTF-8 bytes, so it does not depend on a process-start encoding property missing from built-in Windows PowerShell. A failed conversation lookup is shown as an error, not as an empty conversation list. The compatibility test models the older property surface while running real Codex; physical Windows PowerShell verification remains.

browser -> existing encrypted connection -> local helper
        <- conversation page or read error <-

Compact navigation restores terminal view while retaining the selected tab; it closes the drawer and session dialog. Entering terminal view updates the route to the sessions page; reload then restores saved tabs instead of reopening the page left behind. The title and floating sessions control still open the picker. Below 900 CSS pixels and on portrait touch tablets up to 1366 pixels wide, titles abbreviate to two machine characters and three session characters, with full accessible names retained. Other tabs activate directly; close controls stay in the picker. Scroll and resize measurements show a passive top track only when tabs overflow. Its thumb represents the visible proportion and scroll position. Action buttons use contiguous 36px-wide, 44px-high targets.

compact navigation -> selected tab -> sessions route -> reload sessions
title / floating sessions -> picker -> choose tab

Compact selected-title buttons remain transparent on hover and while expanded, so they cannot obscure the selected tab’s accent fill or inset underline.

The compact floating controls include a four-way, 44px-per-key direction pad above the session picker. It shares the picker’s keyboard and safe-area positioning. Arrow taps retain input focus and use the existing modifier-aware input path, disabled unless the current session is writable; desktop keyboard rows retain their arrows.

D-pad -> modifiers -> guarded terminal input
D-pad above picker -> Enter above picker

Mobile keyboard Return sends one carriage return and blurs the terminal input after committing native edits. Key release does not refocus the input. Line-break input events cover software keyboards without Enter keydown; composition confirmation does not submit. Desktop Return retains focus. These paths are tested in WebKit and Chromium with mobile platform profiles; actual device keyboards still need owner verification.

The floating Enter control sends one carriage return through the guarded session writer, committing any pending native edits before blurring the terminal's input field. This dismisses the on-screen keyboard. Disconnected, paused and watch-only sessions keep the button disabled.

mobile Return / floating Enter -> commit -> carriage return -> blur

Trust boundaries

Party Holds Cannot
Browser Password-derived keys, vault key (tab only), SSH private key, host pins —
Hub Emails, OPAQUE records, sealed vault blobs, device public keys, hashed device tokens, and the other records listed in What we can and can't see Decrypt the vault; log in to a machine; read or alter SSH traffic undetected
Machine Host private key, device token, approved browser public keys Learn the user's password or vault

The hub routes; the machine authorises. A compromised hub can deny service, and can try to impersonate a machine, but it holds no host key, so pinned browsers refuse it. The one exception is the web-delivered code: see What we can and can't see.

Data paths

Path Protection
Browser ↔ hub (API) TLS; the site sits behind Cloudflare, which sees what the hub sees, never SSH plaintext or vault contents
Browser ↔ machine (shell) SSH end to end: post-quantum hybrid or Curve25519 key exchange, ChaCha20-Poly1305 or AES-GCM, Ed25519 keys
Machine ↔ hub (tunnel) TLS WebSocket, device token
Browser ↔ server being set up SSH end to end over a hub TCP relay to public addresses only; host key confirmed by the user before authentication (details)
Hub → user's inbox SMTP with TLS through Amazon SES: confirmation codes, alert emails (what changed, browser and rough place, a machine's, passkey's or organisation's name) and invitations (the sender's address)
Vault at rest (hub) XChaCha20-Poly1305 under a 256-bit vault key the hub never sees
Secrets at rest (machine) DPAPI (Windows), Keychain-held key (macOS), desktop-keyring-held key or owner-only files (Linux)