docs
TypeScript SDKCore Concepts

Session Lifecycle

The nine honest phases, scale-to-zero waking UX, session expiry, idle handling, and DVR recordings

Sessions are honest by default. The SDK never hides what a GPU session is doing behind one opaque spinner: waking, waiting for capacity, expiring, and idling are all first-class, typed states with drop-in UI. This page is the end-to-end lifecycle toolkit.

The nine phases

idle → queued | unavailable | provisioning → connecting → live → ended | expired
                                                        ↘ error
PhaseMeaning
idleCreated, not yet admitted or connected
queuedWaiting in the admission queue (phase.queue = { position, depth })
unavailableNo runtime exists to serve this function — non-terminal; recovers when one appears
provisioningAdmitted; compute is being allocated/warmed
connectingTransport is establishing the media session
liveFirst media track has arrived
errorAdmission or transport failed (phase.error.kind names why)
endedIntentionally disconnected / closed
expiredReached its declared maximum length — terminal and honest, not an error

Three helpers cover most phase UI: describeSessionPhase(p) returns honest default copy ("Starting model…", a queue position, "Runtime unavailable — …"); isWakingPhase(p) names the pre-live waiting set; whenLive(session) / session.whenLive() is the "is media ready?" gate that rejects with a typed SessionFailedError (kind + full SessionStatus) instead of an opaque failure. UrunSessionStatus renders the phase line; UrunSessionGate gates children on live.

Scale-to-zero waking

Apps scale to zero, so a first session may wake a runtime. The SDK makes that wait honest and short:

  • useUrunPrewake({ function: 'generate' }) — the intent signal. While a page that will start a session is mounted, it pings the advisory prewake endpoint (on mount, then every 60s) so capacity is already warming when the user clicks start. Never throws, never affects the session flow.
  • useSessionWake(session) — a live { waking, state, reason, seconds } view of the pre-live wait, counted from phase.wakingSince as one continuous clock across queued/unavailable/provisioning (never resetting per sub-phase).
  • UrunSessionWaking — the drop-in "waking (Ns)" element; renders nothing when not waking.
import { useUrunPrewake, UrunSessionWaking } from '@urun-sh/react'

function GeneratePage({ session }) {
  useUrunPrewake({ function: 'generate' })     // warm while the page shows intent
  return <UrunSessionWaking session={session} />
}

While waiting, phase.runtime reports why: unavailable (no runtime registered), starting (cold-starting/warming), or busy (all runtimes serving other sessions), plus a frontend-safe reason.

Session clock and expiry

A function can declare a maximum session length; the platform then ends the session honestly at its deadline with the terminal phase expired — distinct from error because nothing failed.

  • session.endsAt / useSessionEndsAt(session) — the deadline as a Date, or null when no maximum was declared.
  • UrunSessionClock — an mm:ss countdown to the deadline; renders nothing without one (compose it into any header unconditionally), flags data-urgent in the last minute.
  • UrunSessionEnded — the terminal ended/expired surface, with an onNewSession hook to start fresh.

Idle warning

Attached-but-inert sessions time out (platform default; apps configure or opt out server-side). The SDK auto-stamps pointer/key/visibility activity into the session's awareness; before the kick, the server raises a warning in the control doc:

  • useSessionIdle(session) — the raised warning ({ warning, deadlineEpochS, idleSinceEpochS }), or null in the steady state.
  • UrunIdleWarning — the "Are you still there?" overlay with a countdown; its button confirms presence via session.touch().
  • session.touch() — record app-specific activity explicitly (throttled internally). Vanilla (non-React) apps can wire DOM activity with createActivityTracker from @urun-sh/core.
import { UrunSessionClock, UrunSessionEnded, UrunIdleWarning } from '@urun-sh/react'

function Shell({ session, children }) {
  return (
    <>
      <header><UrunSessionClock session={session} /></header>
      {children}
      <UrunIdleWarning session={session} />
      <UrunSessionEnded session={session} onNewSession={() => location.reload()} />
    </>
  )
}

Recordings: the DVR

Session streams are recorded by default — every session is replayable, and stream.seek(t) addresses the recording while seek('live') snaps back to the live edge. Retention is explicit:

  • Recordings start in the short-lived temp bucket; retain(id, '7d') keeps one for a duration, pin(id) moves it to the durable bucket, permanent(id) locks it, delete(id) removes it.
  • Mutations are async intents: a recording reports pending_pin / pending_delete / … until the reconciler settles it — surface that (isPendingRecording(r)) instead of assuming completion.
const recordings = await session.recordings.list()   // this session's recordings
await session.recordings.retain(recordings[0].id, '7d')

Replay after end. Recording playback outlives the session: attaching to an ended session whose recording exists (Session.attach(id)) answers a replay session — the platform replays the recording through the normal session stream surface (no GPU runtime is woken), so the same <Video> / track rendering works unchanged. Drive playback with stream('video').seek(0). Video streams replay today; an ended session with no recording stays an honest dead end rather than a faked replay.

const replay = await Session.attach(endedSessionId, options)   // replay session for an ended id
await replay.stream('video').seek(0)                            // play the recording from the start

session.recordings requires functionsUrl on your AppOptions (a clear error tells you if it's missing); the standalone RecordingsClient from @urun-sh/core covers non-session (org-scoped) access. Files the runtime produces (e.g. rendered outputs) surface separately as artifacts: session.artifacts.list() and getDownloadUrl(artifactId) return short-lived object-store URLs (RuntimeArtifactsClient is the standalone form).

Related:

  • TypesSessionPhase, RuntimeAvailability, SessionFailureKind, SessionStatus
  • Hooks — the session-state hooks in one table
  • Transport — reconnection and transport negotiation beneath the phases

On this page