Repository navigation
Remote access with iroh: integration plan and feature naming #376
Description
Decision (2026-09-20, maintainer-confirmed)
This issue is the plan of record for remote access. It supersedes every earlier direction recorded below, including the "no relay service" statement from #329 — that sentence was a scoping note to keep one iteration small, not a project principle. The governing text is CONTRIBUTING.md › Principles › 5.
Transport
- iroh is the only transport for native clients. It replaces the current plain HTTP/WebSocket stack rather than running beside it: the hand-rolled HTTP/1.1 reader, the plaintext WebSocket path and the parts of pairing that are bound to that transport retire once iroh carries the existing application protocol. The LAN case also goes through iroh (direct connections with local discovery); two parallel transports would mean two security models to maintain forever.
- End-to-end encrypted by construction. The "a LAN capturer can read the device token" limitation goes away with the plaintext transport.
The browser client uses iroh's browser support, which is relay-only today (docs). Tcode adopts it as it is — experimental status is not a reason to wait (Principle 9).Superseded 2026-09-21: the browser client is direct access only and does not use iroh; remote access uses the native client.The headless HTTP entry that serves the browser bundle remains only until the browser transport lands, then retires.Superseded 2026-09-21: the browser client stays a direct-access-only HTTP/WS entry; see the implementation plan below.- Adapt iroh streams at the existing unified host entry (
RemoteServer::admit, prepared in Prepare unified native remote establishment and trusted stream admission #384). Pairing, the main stream and Preview keep one connection owner; one logical entry does not require one physical socket.
Traverse: the official discovery and relay service
Tcode will operate a service, branded Traverse, providing iroh discovery (finding a paired machine without a shared LAN) and relay fallback when hole punching fails.
- No accounts. Pairing stays a one-time code exchanged for a device token; the service never learns who a user is.
- On by default, switchable in Settings to a self-hosted instance or off.
- Never required. On a LAN or overlay, direct iroh connections give the full product without the service. Traverse only introduces devices to each other and relays ciphertext.
- Sees only ciphertext. This replaces the README's former "no cloud service" claim.
- Abuse limits without accounts: the relay only carries traffic between peers that hold each other's node id; discovery queries, pairing handshakes and relay bandwidth are rate-limited per node id and per IP, degrading to direct-only rather than refusing service; a pairing code is invalidated after five failures on the service, as locally; no SLA is promised, and a rate-limited user can switch to a self-hosted instance at any time. No traffic content is logged, no geo-blocking, and abuse is never solved by introducing accounts.
- Thresholds are implementation decisions.
Naming
Traverse is the chosen name for the service and the user-facing remote capability. "Connect", "Farfold" and "Handspan" are retired. Technical configuration stays descriptive (endpoint, relay, discovery), and the UI reports actual direct/relayed state independently of the name.
Sequence
- iroh endpoint ownership, host identity and routing, and admission adapters at the unified entry; native clients over iroh on a LAN, direct only.
- Discovery and relay via Traverse; self-host option; settings.
Browser client over iroh's browser transport; retire the headless HTTP entry.Superseded 2026-09-21: browser listener reduced to direct access; see plan.- Remove the plaintext transport and its tests; rewrite
docs/remote.mdfor the new model.
Preparation is merged in #384. No iroh dependency, relay deployment or Traverse infrastructure exists yet.
Implementation plan (2026-09-21)
Code survey and iroh research are complete; no code has changed yet. Facts below were verified against iroh 1.2.0 (2026-09-09) and the current crates/remote code. The maintainer has confirmed that a full rewrite of the local implementation and UI is acceptable; no compatibility with the HTTP/WS transport, hosts.json, remote.json or existing pairings is kept. How the official Traverse instance is operated is not part of this issue.
Facts that shape the design
crates/remoteruns on smol; iroh requires tokio. The transport crate is rewritten on tokio (one runtime thread owns theEndpoint,async_channelto GPUI).crates/protocol,mux.rs, and all ofcrates/client(Transport, outbox, recovery, heartbeat) are transport-agnostic and stay.- iroh 1.2.0: MSRV 1.91, MIT/Apache. TLS is raw public keys only;
Connection::remote_id()is the authenticated peer identity. Hole punching is QAD-only (relay UDP 7842); STUN is gone.Connection::paths()/path_events()report direct vs relayed. The relay protocol is WebSocket over HTTPS at/relay; the relay forwards by destination EndpointId only, has no per-pair policy hook, and its rate limit is a per-connection rx token bucket.AccessControl::on_connectsees the authenticated EndpointId, URI and HTTP headers, not the peer address. - DNS-based lookup (
DnsAddressLookup) needs an authoritative DNS zone per instance and mainline DHT is off by default upstream; Traverse uses pkarr over HTTP only, which also keeps every client on one discovery path.
Identity, pairing, connection
- Machine identity = iroh
EndpointId; the existingidentity_seedbecomes the irohSecretKey.host_idUUID is dropped. - Device identity = the device's own
EndpointId(desktop and phone; persisted in the device data directory). - Pairing = presenting the invitation's single-use secret over ALPN
tcode/pair/1to join the machine's allow list. Maintainer decision 2026-09-21: there is no six-digit code. Since first contact is always by scanning or pasting the invitation, the link carries a 128-bit random secret instead; five-minute lifetime, single use, a new invitation replaces the previous one, all enforced on the machine only. Traverse never sees an invitation. Afterwards the device connects with ALPNtcode/1andEndpointHooks::after_handshakerejects any EndpointId not in the list. Revocation removes the entry and closes live connections. Bearer tokens, token hashes and the/identitychallenge are removed. - One QUIC connection per device attachment. Streams: the main NDJSON stream (protocol version 5, hello without token); one bi stream per Preview target with a
CONNECT host:port/HTTP host:portfirst line, replacing the HTTP forward proxy and Basic auth. macOS port mapping and the Windows/Android loopback bridge translate local connections into streams. - Invite QR/link:
tcode://pair?v=2&id=<endpoint id>&secret=<base64url>&name=<name>&traverse=<base url>&relay=<url>&addr=<ip:port>.... The UI shows the QR and a copy-link action only.
Discovery
Maintainer decision 2026-09-21: no automatic discovery. First pairing is always by invitation: scan the QR or paste the same tcode://pair?… link. The invite carries the machine id, its relay and its current direct addresses (MemoryLookup), so LAN connections work without any service; afterwards iroh keeps learning direct addresses through the relay path. Cross-network lookup via pkarr HTTP publish/resolve against every pkarr URL in the Traverse manifest, publishing relay URLs only. Update 2026-09-22: a device still needs to find a machine it is already paired with on a LAN without internet or Traverse, and recover from address changes. This is implemented on iroh's AddressLookup extension point, resolving paired ids only and with no UI: the machine advertises standard DNS-SD _tcode._udp (via mdns-sd, with PTR records and normal TTLs so system browsers see it) and binds a fixed UDP port (47420 by default, headless --port); the device tries the last successful address, then every other saved address for that machine, then a DNS-SD browse filtered to the paired id (iOS through the system NWBrowser, no multicast entitlement). Nothing else: no subnet probing, no other fallback. iroh's own mDNS crate is not used (its advertisements carry no PTR and TTL 0, invisible to DNS-SD browsers). No DNS lookup of our own, no DHT.
Traverse as a product
Traverse is one server binary, tcode-traverse (new crate crates/traverse-server, released with the existing workflow as archives and a Docker image): an embedded iroh-relay server with QAD and ACME/manual/reloading TLS, a built-in pkarr store (Ed25519 verification, 1072-byte cap, 409 on stale timestamp, per-IP rate limit, eviction of unrefreshed records), a self-describing GET /relays.json manifest, /healthz, Prometheus metrics on a private address, and an optional region lock (AccessControl implementation that reads RTT/client-IP headers only when a trusted reverse proxy is configured, and bounds far clients by a quota). Denial reasons are logged, not shown by the client. The official instance runs this same binary; there is no official-only code path.
Manifest:
{ "v": 1,
"relays": [ { "url": "https://relay.example/", "quic_port": 7842, "region": "…" } ],
"pkarr": [ "https://traverse.example/pkarr/" ] }Official instance, first release (maintainer decision 2026-09-21): the official Traverse manifest lists n0's public relays and https://dns.iroh.link/pkarr, which n0's documentation allows for production use when performance is acceptable (DNS address lookup docs). No Tcode-operated infrastructure is required for the first release. The manifest is a JSON file in the repository (crates/traverse/src/traverse_manifest.json), bundled into the client and re-fetched from the repository's raw URL with the same cache/TTL/fallback rules as the Claude model manifest (crates/agent/src/claude_manifest.rs); official and self-hosted manifests share one fetcher and one construction path. Self-hosting is supported from the same release. Because a dialer connects to whatever relay the callee's address record names, only the machine's home relay matters; any open self-hosted relay is reachable by every device without configuration on the device.
Clients fetch <base>/relays.json, build RelayMode::Custom(RelayMap) from it and one PkarrPublisher/PkarrResolver per pkarr URL (pkarr over HTTPS is the only wide-area lookup; no DNS lookup); iroh picks the home relay by measured latency. Review follow-up 2026-09-22: home_rtt_max_ms is not advertised in the manifest (the region lock is a server-side setting only). Fetch failure falls back to the cached manifest for that source; the official source additionally has the built-in copy. A self-hosted source with nothing cached starts the machine relay-less (LAN only) and applies the manifest live once it loads; it never falls back to the official manifest.
Settings: the machine has one field traverse = official | <base url> | off (tcode-headless serve --traverse …); off disables relay and pkarr, leaving mDNS and QR addresses. Devices store the Traverse URL per machine, taken from the invitation, so one phone can use an official-Traverse machine and a self-hosted one at once. Minimum self-host requirements: public TCP 443 and UDP 7842, a domain and certificate; without UDP the manifest marks the relay as relay-only.
Three wording corrections to Principle 5 follow: the relay cannot restrict forwarding to paired pairs (it forwards by destination id; unpaired peers are rejected by the endpoint after one handshake); pairing-code failures are counted on the machine, not on the service; and the browser client does not use iroh's browser support over a relay but is a direct-access-only HTTP/WS entry.
Upstream requests to n0 (Principle 9): peer address/RTT in ClientRequest, per-client limits from Access::Allow, or a per-pair forwarding hook.
Browser client (direct access only)
Maintainer decision: the browser client is for direct access only (same machine or same LAN) and does not use Traverse; remote access to a headless machine is done with the native Tcode client over iroh. The browser does not adopt iroh's wasm support. tcode-headless keeps a reduced HTTP/WS listener for the browser: static bundle, password login, /ws into the same HostMux; native pairing, /identity and the forward proxy are removed from it. It binds 127.0.0.1:47420 by default; a LAN bind is explicit and requires a password. It is plain HTTP; tunnelling it elsewhere is the user's own setup. This listener is reimplemented inside the tokio-based transport crate.
Sequence and acceptance
| Phase | Deliverable | Acceptance |
|---|---|---|
| 0 Spike (~1 week) | Standalone example: desktop host ↔ Android over a relay and mDNS; measure direct ratio, handshake time, background recovery | Works on same LAN, cellular, corporate Wi-Fi; paths() reports direct |
| 1 Transport rewrite | crates/traverse: endpoint ownership, pairing, allow list, main stream, mDNS, QR v2, remote.json/hosts.json v2, headless serve/pair; delete HTTP/WS/proxy/probe code and tests |
Existing weak-network, reconnect/replay and dedup tests pass over iroh; revocation closes within one tick |
| 2 Traverse | Bundled + fetched manifest (model-manifest mechanism); tcode-traverse binary for self-hosting; settings; direct/relayed status in UI; region lock |
Cross-network pairing and reconnect; LAN fully functional with Traverse off; rate limiting degrades without errors |
| 3 Preview | CONNECT/HTTP streams; macOS mapping and Windows/Android bridge over streams | Rewritten proxy tests pass; HTTPS tunnels stay opaque |
| 4 Browser | Headless browser listener moved to the tokio crate, loopback by default, pairing/proxy paths removed | Browser login, reconnect and outbox work locally and on LAN; no /pair, no CONNECT |
| 5 Wrap-up | Rewrite docs/remote.md (incl. self-hosting); correct Principle 5 wording; CI: MSRV 1.91, cargo machete |
All validation commands pass |
Release note: every device must pair again; older clients and new machines are mutually incompatible.
Status (2026-09-22): implemented in #498 (branch traverse/transport)
All phases are implemented on the branch and pass cargo fmt --check, cargo clippy --workspace --all-targets -D warnings, cargo nextest run --workspace (907 tests), cargo-machete, and the web/iOS/Android target checks:
crates/traverse(tokio + iroh 1.2): machine/device identities, invitation-only pairing with a single-use link secret, allow list and live revocation, one QUIC connection per attachment with hello + NDJSON and Previewconnecttunnels, bundled + fetched Traverse manifest (official / self-hosted / off, live relay and lookup updates), native client host and Preview adapters (native), and the browser listener on loopback by default (browser, protocol 5, password login).crates/remoteis deleted.crates/traverse-server(tcode-traverse): self-hostable instance with relay + QAD, pkarr store,/relays.json, TLS modes, optional region lock behind a reverse proxy, metrics; release archives added to the workflow.- Desktop hosting and Machines pages, phone/browser hosted page, connection banner with Direct/Relay; Android network-change and iOS foreground hooks; no automatic discovery.
docs/remote.md, README and Principle 5 rewritten for this model.
2026-09-22: LAN lookup for paired machines landed (DNS-SD advertisement visible to Apple's mDNSResponder, saved addresses, fixed UDP 47420). Remaining before release: on-device Android/iOS runs (including the iOS system browser path), a relayed (off-LAN) end-to-end test, and the maintainer's decisions listed below.
Maintainer decisions still open
- Default bind of the browser listener (loopback proposed; LAN explicit and password-protected).
- Region-lock defaults shipped in
tcode-traverse(home_rtt_max_ms, far-client quota).
Earlier research, plan and naming discussion (superseded, kept for reference)
Current direction: iroh integration and feature naming (2026-09-09)
The goal is to reach a Tcode machine from outside its LAN without manually maintaining a tunnel. The working direction is iroh integration at the existing unified host entry, with both self-hosted infrastructure and a possible Tcode-operated service considered. The user authorized and completed the preparatory refactor; actual iroh integration and final product naming remain outstanding.
Connection model
Keep the existing application protocol and one logical host entry. Pairing, the main WebSocket and Preview reach that entry through the common native connection owner. Integrating iroh should replace the underlying connection at this boundary, rather than require callers to manage separate networking systems for chat, terminal and Preview. One logical entry does not require one physical socket or one byte stream.
The existing deployment model remains valid: client HTTPS/WSS → external TLS-terminating middleware → Tcode HTTP/WS entry. HTTPS pairing is the existing /pair code-to-token exchange, not certificate-based pairing. Tcode did not gain a built-in HTTPS server in the preparation work. Preview additionally needs the intermediary to forward its CONNECT/proxy requests; ordinary website reverse-proxy support alone is insufficient. This deployment option does not require iroh itself to use an HTTP reverse proxy.
Iroh plan and infrastructure options
- Adapt iroh streams to the shared native connection and imported-stream admission boundaries, retaining existing authentication, host dispatch, recovery and Preview ownership.
- Use native direct connectivity where available, with encrypted relay fallback. Public STUN servers alone do not supply iroh's peer introduction and fallback relay. Default public iroh relays can be used for development/testing; they are rate-limited and offer no uptime/performance guarantees. Official relay documentation.
- Compare a Tcode-operated relay service and self-hosted relay/discovery infrastructure using the same client integration. An embedded connector is the recommended packaging direction; a separate connector executable remains an option. Operating a hosted service, its enrollment model and production defaults have not been selected.
- Implement endpoint lifetime, host identity and routing information, discovery, and their relationship to existing pairing/device authorization. These are remaining implementation decisions, not features supplied by the completed refactor.
- Adapt the browser WebHost deliberately. Iroh has browser WASM support, with relay-only browser connections in the researched version; the current browser fetch/WebSocket path and Preview page delivery are not automatically replaced by it. Browser documentation.
Preparation is merged in #384, with full local checks and all six hosted checks passing. No iroh dependency, live relay, off-LAN iroh test or final brand has been delivered. Details and evidence follow below. This issue remains open.
Feature naming
| Candidate | Meaning and Chinese direction | Status |
|---|---|---|
| Tcode Farfold | Coined from “far” + “fold”: bringing a distant machine within reach. Chinese concept: 咫尺. | Preferred research candidate; awaiting the user's selection. |
| Tcode Handspan | A hand's reach; a tangible expression of closeness. Chinese concept: 咫尺. | Backup candidate; awaiting the user's selection. |
Connect was rejected as the final product name. Neither shortlisted name is approved or cleared for trademark/domain use. The earlier screening was preliminary only. Brand the user-facing capability; keep technical configuration descriptive, and report actual direct/relayed connection state independently of the name.
The records below retain earlier research and implementation evidence. All withdrawn AI-authored constraints remain non-authoritative; this update does not introduce a new release checklist.
Implementation preparation completed (2026-09-09)
Merged PR #384 as 79b3b7c6d2949341cd812dcbf0cdf3d9bda7b0c1. This completes the requested preparation for replacing the underlying connection at the existing unified host entry. Iroh itself is not integrated and this issue remains open.
The user clarified that one logical protocol/host entry does not mean one physical socket, nor a requirement to split business features into separately managed transports. The existing application protocol and server dispatch remain shared.
- Native pairing HTTP/HTTPS, the main WebSocket and paired Preview now use one connection-establishment owner for origin validation, DNS, address racing and TCP/TLS. The existing HTTPS
/paircode-to-token flow is preserved; no certificate-based pairing or new authentication mechanism was introduced. RemoteServer::admitaccepts imported duplex streams into the existing HTTP/WS/CONNECT dispatch with remote provenance. Imported traffic cannot acquire local/admin/pairauthority from loopback forwarding or caller-supplied headers. Direct-local behavior remains unchanged.- macOS Preview retains its URL mappings and can now reach HTTPS paired origins. Windows/Android native proxies use an attachment-owned raw bridge into the same entry; Windows remote WebView2 environments receive isolated temporary directories. Authentication, cancellation, shutdown and write-half close stay with their existing owners.
- The final reviewed PR head
6b33382054f3629a7ad004ce6ef8c95549c27f87passed full local formatting, Clippy, workspace build/tests, dependency hygiene and iOS/Android/Web checks. All six hosted CI checks passed. Meaningful regressions cover existing HTTPS pairing compatibility, WS/CONNECT, imported-admin denial, timeout/cancellation, bridge half-close/lifetime and existing reconnect/replay behavior. Native Windows/Android GUI was not exercised.
Next integration work remains iroh endpoint ownership, connection/admission adapters, host identity/routing/discovery and browser WebHost integration. No iroh dependency, relay deployment, account system or final product name was added. The dated source observations below describe the earlier research snapshot; this update supersedes the connection-establishment and Preview HTTPS gaps described there.
Public STUN servers alone are insufficient for iroh: its relay exchanges initial connection information and carries encrypted traffic when direct connectivity fails. Default public iroh relays can support development/testing, with rate limits and no uptime/performance guarantees. Official relay roles and service boundary.
Research progress (2026-09-09)
The user's objective is remote access to their Tcode machine without manually maintaining a tunnel, including consideration of self-hosted infrastructure and a Tcode-operated connector service. All earlier AI-authored issue constraints have been withdrawn by the user. The recommendations below are research findings, not an approved architecture, required checklist, final name, or completed implementation. The original report is preserved below as non-authoritative history.
Recommended first candidate
Investigate an embedded host connector using iroh, with native direct connections where available and an encrypted relay fallback, sharing one client/protocol between Tcode-operated and self-hosted services. Keep existing LAN/manual-origin configurations eligible. A standalone connector process is optional packaging, not a product prerequisite.
The official iroh browser documentation and v1.1.0 source establish generic WASM support: browser connections are relay-only and remain encrypted. There is no bundled generic browser npm package. This corrects the stale FAQ sentence that iroh does not run in browsers; media-specific iroh-live is not the evidence. Native relay transport uses HTTPS upgraded to WebSocket; ordinary HTTPS reachability does not establish WebSocket availability. Replacing the relay alone does not replace default address lookup. Network dependencies.
Alternatives compared
- frp / rathole: practical self-hosted reverse-tunnel building blocks. frp offers broader WSS/proxy configuration; rathole focuses on raw TCP with TLS/Noise. Neither automatically supplies Tcode pairing or end-to-end client-to-host privacy. frp, rathole.
- Tailscale / Headscale / NetBird: strong bring-your-own-network choices. Tailscale tsnet/libtailscale and NetBird Go embed are real embedding options, not external-install-only products. Rust/Go distribution, browser support and service identity models need deliberate integration. NetBird's management/signal/relay/combined components are AGPLv3, not all BSD. tsnet, NetBird embed, license.
- libp2p / WireGuard: eligible, but libp2p's exact browser-to-NATed-Rust-host direct mode is not established by WebRTC Direct alone; WireGuard alone does not provide discovery, enrollment and TCP fallback. Browser matrix, WireGuard limitations.
- Cloudflare / ngrok / Microsoft Dev Tunnels: managed alternatives with different ingress and operating terms. Cloudflare public HTTP forwarding is not arbitrary CONNECT transport; its documented TCP route requires a matching client path. ngrok has official embedding SDKs but metered endpoints/domains/traffic. Dev Tunnels remains a public-preview development service without SLA, so it is a reference/optional workflow rather than the preferred production default. Cloudflare routing, ngrok SDK, Dev Tunnels.
Current Tcode integration facts
Source snapshot: main db761c0, after merged macOS Preview PR #383. #375 is no longer a blanket-disabled macOS blocker. Its loopback port-mapping implementation is separate from external reachability.
- PairedHost still stores one origin. WebHost uses the page origin and relative pairing route. Identity/routes and browser application transport need an explicit design.
- The server determines local
/admin/pairaccess from the socket peer's loopback address. A reverse connector blindly dialing localhost can make remote requests appear local. A dedicated remote ingress or trusted internal provenance is the proposed remedy; caller-controlled forwarded headers are not authority. This is a source-based integration risk, not a claim of an executed exploit. - macOS PreviewRoutes currently requires an HTTP paired origin and dials plain TCP CONNECT. Merely substituting an HTTPS relay URL does not integrate Preview. Reuse its slot lifetime and URL-mapping owner with an appropriate stream adapter; native proxy paths and browser development-page publication need their own adapters.
- Browser WASM can carry the Tcode application protocol but does not globally intercept page fetch, iframe, WebSocket or navigation. URL rewriting, gateways and other pragmatic publication options remain eligible.
Product and trust decisions
First-time off-LAN pairing is a separate flow from reconnecting already-paired devices. Compare a locally approved short-lived invitation, an authorized existing device issuing an invite, and an explicit recovery/unattended provisioning flow. The existing desktop path is not asserted to implement all of these. Accounts are an option for enrollment, quotas and recovery, not a forbidden or mandatory premise.
For an official service, prefer authenticated client-to-host encryption so the relay cannot passively read application payload or observed bearer credentials. Hop-TLS gateways remain an eligible trusted deployment model. Browser code delivery remains a separate trust boundary: whoever can replace the served JS/WASM can alter the client. Do not promise operator-proof browser security. Transport identity and relay membership do not themselves grant Tcode device permissions.
Iroh public relays are limited development infrastructure; managed hosting starts at $19/month, while safe consumer credential issuance is not a turnkey consequence of that price. Do not embed a project-wide API key in shipped clients. Hosting, credential FAQ.
Cost and deployment findings
A public DigitalOcean price anchor is $6/month including 1,000 GiB, then $0.01/GiB outbound. This is a billing example, not evidence of relay capacity or Mainland reachability. At an assumed 5,000 GiB relay egress, that single-node base calculation is $46 before domains, operations and redundancy. VM pricing, bandwidth billing.
ngrok PAYG has a $20 base including $20 usage. Active endpoint hours ($0.02) and custom-domain active hours ($0.01) are distinct charges; the earlier research simplification that counted only domain hours was incomplete. Transfer beyond 5 GB is $0.10/GB, with request/connection and other usage charges. Wildcard-to-internal routing changes the endpoint-hours model and must be costed for the actual topology. Pricing, billing definitions.
Both Mainland China and overseas are in the comparison. No node was deployed or measured, so no geographic availability, users-per-node, observed relay fraction or cost-per-user is claimed. Official and self-hosted deployments should be replaceable, including discovery dependencies.
Naming shortlist
Tcode Farfold is the preferred creative candidate: far + fold, with the Chinese concept 咫尺. Handspan is a backup. Keep configuration/CLI terms descriptive and display actual direct/relayed status independently of the brand. Neither name is selected or cleared. Preliminary screening found no clear same-name remote-connectivity product for Farfold, but the string exists in a GitHub topic; Handspan has unrelated existing uses. Bilin/比邻, Lodestar, Threadway, Waypost and Elsewhere have more relevant existing product usage and were deprioritized. No trademark/domain registration or purchase occurred.
Evidence level and next step
Two bounded primary-source lanes plus independent product/naming critique are complete. The lead reconciled high-impact browser support, current source seams, licenses and pricing. Next recommended work is a small iroh integration experiment covering a real off-LAN new-device flow, browser application stream, native Preview adapter, remote-ingress authorization and reconnection, with measured traffic and network conditions. These are proposed decision experiments, not a new mandatory issue checklist. No implementation, deployment, installed-product test or final branding decision occurred in this research.
Historical report — superseded and non-authoritative
The following body is preserved verbatim for provenance. Its AI-authored constraints, acceptance checklist, source snapshot and earlier architecture proposals must not be treated as current user requirements. The dated research above supersedes obsolete implementation statements.
Next-release requirement
Tcode must let users reach their machine from outside its LAN without manually provisioning or maintaining a tunnel before the next release. This issue records the current-state brief and decisions needed for research; it does not select a relay, NAT-traversal strategy, deployment model or final feature name. "Connect" was a placeholder and is not accepted branding.
Product and problem
Tcode is a coding-agent workspace with desktop hosts, a headless host, and clients sharing a responsive shell. Users need access to their own machine and its working environment while away from home/office, including across different networks. Today they must arrange network reachability themselves, and configure a usable origin. This should become a supported product flow, with understandable setup, authentication, reconnection, failure and revocation behavior.
This packet is intended to be pasted into GPT Pro web by the user for research and naming. Next step: use this brief for architecture and naming research before implementation decisions.
Verified current state
Source snapshot: current GitHub main cacab223d81d6cc23badc81c1bfbefc827fccb62, inspected 2026-09-08. Source links below are immutable permalinks. Code/document inspection only; no new runtime networking or platform probes were performed.
| Area | Existing implementation and evidence |
|---|---|
| Hosting and deployment | Headless CLI serves on 0.0.0.0:47420 by default; accepts --listen, --name, --data-dir, --password. There is no serve --connect flag. Desktop hosting owner persists enabled/port/name and starts the listener/beacon. Settings contain those fields, not a relay registration setting. Release workflow builds the Web bundle and embeds it in headless packages. Deployment instructions describe running headless/systemd and user-managed VPN/tunnels; no first-party relay or public discovery service is supplied. |
| HTTP entry points | Server dispatch handles /ws, /pair, browser /auth/state, /auth/setup, /auth/login, loopback-restricted /admin/pair, static files, and separate forward-proxy requests on the same TCP listener. A reachability design must inventory these routes, not just /ws; preserve admin restrictions when intermediaries change apparent peer addresses. |
| Authentication/storage | AuthStore stores host identity, device token hashes and browser password state in remote.json; native pairing uses a short-lived six-digit code and browser access uses password setup/login. Token issuance creates random bearer tokens; the server stores hashes, not the original token. Native storage saves raw paired credentials in hosts.json with Unix 0600 writes. Web storage uses origin-scoped tcode.hosts localStorage. These are not encrypted credential vaults. |
| Address model | PairedHost and migration have exactly one HTTP(S) origin, plus identity/token/last connection time. Legacy addrs/port records migrate to one origin. race_addresses races DNS socket addresses for that one origin at staggered 250 ms intervals; it is not LAN-versus-relay origin racing. Native client host owns connection and LAN-origin refresh. WebHost fixes pairing to the page origin. Any multiple-route design needs an explicit persistence/migration/selection decision. |
| Protocol and recovery | Protocol wire types encode commands, queries, subscriptions, acknowledgements and events as NDJSON records. WebSocket handshake negotiates protocol and validates the bearer token. Native connection uses HTTP/WS or HTTPS/WSS with standard trusted roots and hostname validation. Existing backoff and heartbeat policies, replay/outbox/deduplication behavior must survive transport changes; see recovery documentation. |
| Preview traffic | Server proxy is a separate authenticated HTTP forward proxy with opaque CONNECT byte tunnels, sharing the listener port rather than riding the command NDJSON pipe. Basic user tcode plus the paired token authorizes host-network egress. Plain HTTP forwarding also handles WebSocket upgrades. Desktop proxy builder currently requires a direct HTTP origin and rejects attached macOS creation. Ordinary HTTPS website forwarding or forwarding /ws alone does not establish forward-proxy/CONNECT support. |
| Platform boundary | Remote documentation records Windows/Android proxy support and macOS containment. wry embedding is not proof of enforceable machine-network routing. iOS currently lacks embedded Preview; browser code cannot override the browser proxy. iOS bootstrap restores a saved host target with local: None; iOS host adapter uses NativeClientHost with discovery and QR scanning. Existing iOS attachment must not be silently removed. |
| Security baseline | Security documentation states LAN HTTP/WS is plaintext, tcode does not provision TLS, and device tokens grant broad machine/project/proxy access. HTTPS tunnel protection is hop/transport security, not an existing application end-to-end encryption layer. Pairing a bearer token does not establish an implemented E2E key agreement or key lifecycle. |
Corrections to the earlier proposal
PairedHost.addressesplus a relay address is not current storage. A single saved origin and DNS-address racing are current behavior.- Preview is not carried by the same NDJSON pipe today. Any proposed reachability option must account for HTTP forwarding, CONNECT, WebSocket upgrades, credentials and WebView proxy capabilities, or explicitly identify what additional integration is needed.
- E2E encryption does not already exist. The previous "optional E2E later" line was a proposal, not a security guarantee or approved postponement. Decide the threat model, intermediary visibility, key establishment/storage/rotation/revocation and whether E2E is a release requirement before asserting privacy claims.
- A new relay crate, host registration secret, hello variant, hosted default, stateless deployment, LAN-first routing, exclusion of P2P, and exclusion of accounts were proposed shapes, not user-approved architectural constraints. Evaluate rather than inherit them as settled decisions.
- "Connect" is rejected as final branding. Use neutral wording until naming research and a product decision are complete.
Constraints and unresolved scope
- Required outcome: access without a user-managed tunnel. Do not equate that outcome with one predetermined transport architecture.
- Preserve authenticated access, device revocation, host identity, isolation between machines, existing data and recovery semantics. Define safe compatibility/migration for existing clients and saved origins.
- Preserve Preview's machine-network boundary and no-address-rewriting requirement in macOS: support attached remote loopback Preview #375. No client-local fallback for attached requests; do not expose development-server ports as an unexplained workaround.
- macOS: support attached remote loopback Preview #375 is a separate required next-release macOS desktop Preview track. Remote access with iroh: integration plan and feature naming #376 must expose a suitable transport capability or identify its integration blocker, but cannot by itself fix WKWebView bypass or close macOS: support attached remote loopback Preview #375. Release acceptance needs both tracks and their integration.
- Original user scope statement: "一个客户端连接另一个客户端的功能,目前我只明确在 ios 不做吧!" Its interpretation is unresolved; no peer-to-peer exclusion is assumed. Lead decision: retain existing iOS behavior and leave iOS: embedded preview (WKWebView) backend is not implemented #368 untouched. Current iOS runs no local host but does attach to a host. The precise intended exclusion remains unresolved and needs separate clarification only if a future implementation requires it; it is not a blocker for this issue-body update. Do not infer removal of existing iOS host attachment.
- Hosted versus self-hosted service, accounts, onboarding when not on the same LAN, supported client/host matrix, minimum OS versions, reliability targets, operating budget, abuse controls and E2E requirements remain decisions. Record owners and measurable thresholds before implementation.
Focused research questions for GPT Pro
- Which feasible designs provide outbound-friendly remote access across NAT/CGNAT and restrictive firewalls for this actual HTTP/WS/forward-proxy surface? Compare managed relay, self-hosted service, overlay and direct-with-fallback approaches without assuming a winner. Use primary sources, dates, licensing, platform constraints, operational costs and failure modes.
- How can a selected option support native clients and an origin-bound browser client, initial onboarding and browser assets/auth routes, reconnect/replay and host identity without weakening loopback-only admin boundaries? What persistence/protocol migrations are needed?
- How would each option carry authenticated forward-proxy HTTP, CONNECT and development-server WebSockets? Distinguish a transport proof from the macOS WebKit enforcement proof owned by macOS: support attached remote loopback Preview #375. Which options are incompatible with unchanged URLs or platform WebView APIs?
- What data can each intermediary observe? Compare TLS termination and application E2E explicitly. How would pairing, authenticated key exchange, key recovery, token revocation, device loss and browser delivery trust work? Do not assume the existing bearer token is an E2E protocol.
- What is the minimum defensible operated service: enrollment/identity, quotas, abuse prevention, audit/metadata retention, availability, upgrades, region strategy, cost limits and self-hosted compatibility? Which risks need prototypes before choosing architecture?
- What empirical acceptance matrix proves success across LAN, cellular, separate NATs, corporate proxies, sleep/wake, network switching, host restart, service outage, protocol mismatch and revoked devices? Propose measurable targets for user approval, not fabricated existing commitments.
- Preserve existing iOS behavior. If a future implementation depends on interpreting the quoted exclusion, identify the precise decision needed rather than silently deriving a topology or changing iOS: embedded preview (WKWebView) backend is not implemented #368.
Naming brief
Product name: Tcode. Name the user-facing capability for reaching one's Tcode machine remotely without manual network setup. "Connect" is only a rejected placeholder. Request a short candidate list with rationale, clear English and Chinese pronunciation/meaning, fit with existing machine/device vocabulary, and confusion risks with generic action labels or other products. Prefer understandable names that do not promise a particular relay/P2P technology, universal availability or unimplemented privacy. Research obvious naming conflicts with sources; distinguish a preliminary screen from legal clearance. Provide recommendation criteria and tradeoffs; final name remains the user's decision.
Acceptance for the next release
- User-approved architecture, security model, platform scope, naming and deployment/operating plan are recorded after research; unresolved requirements become explicit blockers.
- A supported onboarding flow and access from a separate external network work without manual tunnel provisioning or public-origin configuration. Pairing/login/revocation and offline/unreachable states are understandable and verified.
- Existing LAN/manual-origin setups and saved data have verified compatibility or an explicit migration; reconnection, replay and command deduplication remain correct.
- Transport security and intermediary visibility match documented, tested claims; authentication/revocation and cross-machine isolation have meaningful negative tests.
- Required Preview transport capability is demonstrated, including HTTP/HTTPS and WebSockets, and integrated with independently accepted macOS: support attached remote loopback Preview #375 routing behavior. Neither issue is closed on evidence for only the other.
- Representative network/platform failure cases, operating limits and recovery targets are verified; release docs/configuration match shipped behavior and state real gaps.
Earlier issue body (preserved historical proposal, not approved architecture)
The earlier body below is retained for provenance. The requirements, corrections and open decisions above supersede its prescriptive shape, assumed address model and provisional branding.
Problem
Today a machine (desktop hosting or tcode-headless) is reachable over plain HTTP/WebSocket on the local network, and from elsewhere only through a user-managed tunnel (Cloudflare Tunnel, Tailscale, ngrok, an SSH forward). That works, but it is a setup step outside tcode, it needs a stable hostname, and every device has to be told the public origin by hand. Phones on mobile data cannot reach a home or office machine at all without it.
Goal
A first-party relay ("tcode Connect") that lets any paired device reach its machine from anywhere, with the same pairing model and the same pipe as the LAN path:
- The machine opens one outbound, authenticated, long-lived connection to a relay and keeps it alive (reconnect with the existing backoff/liveness rules in
crates/client). - Devices connect to the relay with their existing device token; the relay forwards the NDJSON pipe (WebSocket frames) opaquely between device and machine. The relay never sees decrypted content if we add end-to-end encryption on top of the token (device and machine already share a secret from pairing).
- Pairing stays local-first: the six-digit code / QR flow on the LAN or the headless web page issues the device token; Connect only adds reachability. A machine can opt in ("Reachable through tcode Connect") from the Other devices settings, and each
PairedHostgets a relay origin as an additional address for the existing address racing, so LAN wins when available. - The preview proxy keeps browsing from the machine through the same relayed pipe (
crates/remote/src/proxy.rssemantics unchanged).
Shape
crates/connect-relay: a small stateless relay binary (WebSocket in, WebSocket out, keyed by machine id, rate limited, no storage beyond live sessions). Deployable by us or self-hosted; the relay URL is a setting with our hosted default.- Protocol: a
connecthello variant carrying the machine id and a relay-issued session id; no changes toCommand/Query/topics. - Client:
PairedHost.addressesgains the relay origin;race_addressesprefers direct addresses and falls back to the relay; the connection banner shows "via tcode Connect" so the user knows which path is active. - Host:
tcode-headless serve --connectand the desktop hosting switch register with the relay; registration state shown next to the connection code. - Security: relay authenticates machines with a per-machine key created at first registration; devices present the device token to the machine, not the relay; optional E2E layer later.
Out of scope for the first cut
- NAT traversal / direct peer-to-peer.
- Accounts or sign-in; the relay is keyed by machine id and per-machine secret only.
Related: docs/remote.md "Use tcode from other devices", #375 (macOS preview proxy), #369 (browser navigation restore).
Activity
[-]tcode Connect: reach a machine from outside the LAN without manual tunnels[/-][+]Remote access without manual tunnels: next-release requirements and research brief[/+][-]Remote access without manual tunnels: next-release requirements and research brief[/-][+]Remote access without manual tunnels: architecture, deployment and naming research[/+][-]Remote access without manual tunnels: architecture, deployment and naming research[/-][+]Remote access with iroh: integration plan and feature naming[/+]Rewrite CONTRIBUTING around maintainer principles and retire the deri…