For maintainers. Using T3 Code? See docs/user.
The connection runtime is shared by web and mobile. It owns connectivity, authentication, retries, transport lifetime, cached environment data, and environment-scoped operations.
Web and mobile mount this runtime once at the application root and compose it
identically: apps/web/src/connection/runtime.ts and
apps/mobile/src/connection/runtime.ts differ only in the platform layer they
supply. There is no legacy connection owner or supported mixed mode.
connection/layer.ts assembles the runtime:
ConnectionResolver(resolver.ts) resolves a catalog entry into a prepared, authenticated endpoint for primary, bearer, relay, or SSH targets.ConnectionDriver(driver.ts) prepares through the resolver, opens one RPC session, and reportspreparing,opening, andsynchronizing.RpcSessionFactory(rpc/session.ts) performs one transport attempt. It does not retry.RpcSessionis the interface it returns, exposingclient,initialConfig,ready,probe, andclosed.EnvironmentRegistry(registry.ts) owns the catalog and the per-environment scopes.ConnectionOnboardingandRelayEnvironmentDiscoverysit alongside the registry. Startup callsEnvironmentRegistry.startand streams platform registrations intoreconcilePlatform.
The registry creates one environment-scoped supervisor per environment.
acquireSupervisor serializes access per environment, reuses an existing
supervisor when the catalog entry is unchanged, and closes and recreates the
scope when it changed. createServiceScope builds an EnvironmentSupervisor
bound to a closeable scope and connects it; run and runStream execute caller
effects with that supervisor provided.
EnvironmentSupervisor owns desired state, retry scheduling, and the active
session scope. React components do not create connections, transports, retry
loops, or RPC clients.
The supervisor is the only retry owner.
- A persisted or platform registration marks an environment as desired.
- If the device is offline, the supervisor releases the active session and waits for a signal without consuming retry attempts or running a timer.
- When online, it asks the driver for one prepared connection and one RPC session.
- Transient failures retry forever with exponential backoff capped at 16
seconds (
RETRY_DELAYS_MS). A connection stable for 30 seconds resets accumulated backoff. - Authentication or configuration failures remain blocked until an external wakeup changes the relevant input.
- An involuntary session close keeps the registration and cache, then retries.
- Explicit removal closes the session and deletes the registration, credentials, shell cache, and thread cache.
Wakeup handling differs by phase, in supervisor.ts:
- During establishment,
waitForEstablishmentInterruptconsumes and ignores plain application activation. Restarting an in-flight attempt because the app came to the foreground would only delay it. The exception isapplication-active-reconnect, which mobile emits after a meaningful background suspension; it interrupts establishment and resets the retry ladder, because the OS may have silently killed the socket underneath the attempt. - Credential changes interrupt establishment only for relay targets, where a new credential changes what is being established.
- Explicit disconnect, explicit retry, and going offline interrupt establishment in every case.
- While waiting out backoff, application activation resets the retry ladder so a foregrounded app reconnects immediately instead of serving the remaining delay.
- Once connected,
monitorConnectedLeasehandles plain activation by probing the existing session (lease.session.probe, with a shorter timeout for mobile'sapplication-active-probe) rather than reconnecting; a healthy session survives foregrounding.application-active-reconnectskips the probe and replaces the lease outright.
The UI derives available, offline, connecting, reconnecting,
connected, and error from supervisor state plus explicit data-sync state.
It does not infer connection health from cached data or the existence of a
transport object. An environment becomes connected after the socket opens and
the initial config RPC succeeds, proving that the server is responsive. Shell
and thread synchronization are independent data states. A healthy RPC transport
with a failed shell subscription is shown as connected with a synchronization
error, not as a reconnect that is not actually scheduled.
Finite requests, durable subscriptions, and commands are separate APIs:
- Query atoms revalidate when the RPC generation changes.
- Subscription atoms switch to replacement sessions.
- Subscription failure handling in rpc/client.ts distinguishes two
cases. A transport failure (
isTransportFailure: every failure is an RPC client error) ends the inner subscription without resubscribing, so the outer stream waits for the supervisor to supply a replacement session. A handled domain failure runsonExpectedFailureand, whenretryExpectedFailureAfteris set, sleeps and resubscribes on the same session. A healthy transport is never torn down for a domain failure. - Mutations resolve the current environment runtime at execution time.
- Shell and thread snapshots are available while offline.
- Sync status is explicit and independent per domain. Shell status is
empty,cached,synchronizing, orlive, with a separateerrorfield; there is nofailedstatus. Thread status addsdeleted. - Cached shell and thread projections are never allowed to overwrite newer live data during a fast reconnect.
- Domain atom factories route effects through the environment registry and
resolve the current scoped service at execution time. Project and thread
commands are Atom factories under
src/state(createProjectEnvironmentAtoms,createThreadEnvironmentAtoms), as are the shell and thread state factories (createEnvironmentShellAtoms,createEnvironmentThreadStateAtoms). - Web and mobile own their Atom runtimes, React hooks, and feature composition.
The Promise bridge exists only at the React/Atom boundary. Runtime and business logic remain Effect-native.
Web and mobile provide:
- network status and network-change streams;
- application lifecycle wakeups;
- cloud session credentials;
- device identity;
- platform registrations;
- persistent catalog, credential, shell, and thread stores;
- HTTP, crypto, and telemetry layers.
Platform layers adapt operating-system capabilities. They do not implement
connection policy. EnvironmentOwnedDataCleanup is part of this contract: on
removal the registry clears its cache and calls the platform implementation, so
web clears composer drafts and mobile clears drafts plus the thread outbox.
Applications must import explicit package subpaths; the package intentionally
has no root export. The subpaths are documented in
packages/client-runtime/README.md,
with the exports map in that package's package.json as the authoritative
list. Files that are not exported are implementation details.
The application root mounts the shared connection layer, creates its own Atom runtime, and selects the domain atom factories required by that platform. Web and mobile may expose different hooks and features without changing connection ownership.
Application code must not construct RPC clients, retry loops, or raw orchestration commands. Persistence paths belong to the platform registration and cache stores, with explicit migration or invalidation policy.
Core state-machine tests use @effect/vitest and deterministic service layers.
Required coverage includes:
- offline startup and online wakeup;
- forever retry with the 16-second cap;
- explicit retry interrupting backoff;
- authentication wakeups;
- involuntary close and reconnect;
- explicit removal clearing all owned state;
- relay token reuse and refresh;
- progressive relay discovery;
- shell and thread cache hydration;
- durable subscriptions switching sessions;
- command metadata and idempotent queued-command metadata.