Studio Notebook

Claude Code Atlas

REPL Bridge Lifecycle And Session Creation

Trace how the REPL bridge creates or resumes a session and keeps its state alive.

Why this matters

The REPL bridge is the always-on side of remote control. It starts with environment registration, then a fresh session creation on first launch, then polling for work so claude.ai can hand it a live conversation. When work shows up, the bridge opens the ingress connection, moves messages through that connection, and keeps a reconnect path ready if the transport or environment drops out.

That is one lifecycle, not a pile of separate helper calls. When the bridge is resuming saved state, perpetual mode and the saved bridge pointer can let it reuse the old session instead of creating a new one. The bridge is trying to stay attached to the same conversation while the network, the server, or the process itself gets interrupted.

The bridge lifecycle, in plain English

The bridge does a few things in order:

  1. Register a bridge environment so the server knows where this local REPL session lives.
  2. Create a session on a fresh start. If the bridge is resuming saved state, it can reuse the old session instead, but only when perpetual mode and the saved bridge pointer line up.
  3. Poll for work so the server can hand back a session ingress token when a remote user types.
  4. Open ingress and stream messages once that token arrives.
  5. Fall back to reconnecting state if the transport drops or the environment is reaped, then try again without losing the session if possible.

The state labels in replBridge.ts reflect that shape: ready before the bridge is attached, connected when ingress is live, reconnecting while it is trying to recover, and failed when it gives up.

What initBridgeCore() needs before it can start

initBridgeCore() is the bootstrap-free version of the bridge. It does not read bootstrap/state or sessionStorage; the caller has to hand it the context it needs up front.

At minimum, that context includes:

  • where the worktree or repo lives (dir, branch, gitRepoUrl)
  • the machine and bridge identity (machineName, workerType)
  • the API and ingress URLs (baseUrl, sessionIngressUrl)
  • a way to read the current access token (getAccessToken)
  • functions for session creation and archival (createSession, archiveSession)
  • the session title to use now, plus an optional refresher for reconnects (title, getCurrentTitle)

It can also accept message callbacks, permission callbacks, polling config, and the optional initial replay state. Those are bridge behavior details, not prerequisites for starting the lifecycle itself.

The source shapes behind the lifecycle

The handle, state, entry point, core params, and poll recovery constants are the parts that explain how the lifecycle fits together.

export type ReplBridgeHandle = {
  bridgeSessionId: string
  environmentId: string
  sessionIngressUrl: string
  writeMessages(messages: Message[]): void
  writeSdkMessages(messages: SDKMessage[]): void
  sendControlRequest(request: SDKControlRequest): void
  sendControlResponse(response: SDKControlResponse): void
  sendControlCancelRequest(requestId: string): void
  sendResult(): void
  teardown(): Promise<void>
}

export type BridgeState = 'ready' | 'connected' | 'reconnecting' | 'failed'

ReplBridgeHandle is the outward-facing object the caller keeps hold of. It carries the session and environment IDs, exposes the ingress URL, and lets the caller write messages or shut the bridge down cleanly.

export async function initBridgeCore(
  params: BridgeCoreParams,
): Promise<BridgeCoreHandle | null> {

initBridgeCore() is the bootstrap-free entry point. Once the caller has supplied the starting context, it can register the environment, create a fresh session, or resume saved state before it enters the poll and ingress loop.

export type BridgeCoreParams = {
  dir: string
  machineName: string
  branch: string
  gitRepoUrl: string | null
  title: string
  baseUrl: string
  sessionIngressUrl: string
  /**
   * Opaque string sent as metadata.worker_type. Use BridgeWorkerType for
   * the two CLI-originated values; daemon callers may send any string the
   * backend recognizes (it's just a filter key on the web side).
   */
  workerType: string
  getAccessToken: () => string | undefined

This excerpt stays on the startup prerequisites: repo/worktree, identity, URLs, worker type, and the access-token provider. The full BridgeCoreParams surface also carries session creation, archival, and later callbacks, but those sit beyond the narrow startup slice shown here.

const POLL_ERROR_INITIAL_DELAY_MS = 2_000
const POLL_ERROR_MAX_DELAY_MS = 60_000
const POLL_ERROR_GIVE_UP_MS = 15 * 60 * 1000

Those constants describe the recovery loop when polling starts failing. The bridge waits a little at first, backs off up to a minute, and only gives up after a very long window because the server is still the authority on when a session is really dead.