docs
Python SDKCore Concepts

Sessions

The honest session lifecycle from the Python side — lease, hard caps, idle timeout, scale-to-zero snapshot wakes, doc sessions, and DVR

The real-time session is uRun's product primitive. A browser invokes a deployed @app.function by name and gets back a live session; streams, docs, and store are its three faces. This page is the Python side of the session's lifecycle: how long it lives, how it ends, how it wakes, and what it leaves behind. Everything here is declared on the decorator — honest by default, no silent half-dead sessions.

How long a session lives

Three kwargs, three distinct jobs:

@app.function(
    gpus="b200:1",
    lease=3600,                # renewable — auto-renewed while the client is connected
    max_session_s=14400,       # hard cap — ends with terminal reason max_session_age
    session_idle_timeout=300,  # idle kick — platform default 300s; 0 opts out
)
async def generate(ctx: urun.Context):
    ...
  • lease — the renewable session length. While the client stays connected the platform keeps renewing it, so a healthy session runs as long as it is used. A lapsed lease (client gone) ends the session and frees the capacity.
  • max_session_s — the hard, non-renewable wall-clock cap. The control plane refuses lease renewal once the create-time-stamped deadline passes, so the session ends honestly with terminal reason max_session_age — no parallel timers, no zombie sessions. None (default) = uncapped.
  • session_idle_timeout — the attached-but-inert timeout. Client activity (pointer, keys, visibility, requests, live media) rides the control doc's awareness; ~90 seconds before the deadline the user gets an "Are you still there?" warning that any activity dismisses, and past it the session ends with reason idle_timeout. None = the platform default (300s); 0 opts out. Clients that never report activity (old SDKs, headless consumers) are exempt — never kicked for not reporting.

There is no timeout kwarg — it is retired and raises a TypeError. The renewable length is lease; the hard ceiling is max_session_s.

Scale to zero, wake from a snapshot

@app.function(gpus="b200:1", warm=0, max_concurrency=4)
async def restyle(ctx: urun.Context):
    ...
  • warm is the idle replica floor: 0 = scale to zero when the app has no live sessions, N = keep N replicas resident, omit for legacy always-on.
  • Declaring warm on a GPU function also defaults snapshot=True (multi-rank included): after the first successful boot + self-test the runtime snapshots the validated process, and every later wake restores the snapshot instead of cold-booting — model loaded, compiled, and warm. Pass snapshot=False to opt out.
  • Above the floor, capacity follows demand: queued sessions add replicas up to max_concurrency (platform default 2).

The result: idle apps cost nothing, and the first session against a cold app wakes into a validated, warm process — the self-test gate it snapshotted behind guarantees the model was loaded and exercised before anything served.

Doc sessions: session_kind="doc"

Not every session is media. An app whose requests and results ride the synced doc — with bulk outputs as artifacts, not video — declares it:

@app.function(gpus="l40s:1", session_kind="doc")
async def reconstruct(ctx: urun.Context):
    rpc = ctx.requests()               # doc-based request/response

    @rpc.on_request("reconstruct")
    def handle(req):
        ply_path = run_reconstruction(req.payload)         # writes scene.ply
        return {"artifact": ply_path}

    rpc.start()
    # bulk bytes ride the session artifact API, not media:
    # await ctx.artifacts.publish_file(ply_path, artifact_id="scene")

"doc" declares a docs+artifacts app with zero media lanes, so the platform structurally skips RTP ingest, media targets, and recording wiring for its sessions. The default, "realtime", is the full media session. Any other value fails loudly at import.

DVR: every session is recorded

Media sessions are recorded by default — the platform's DVR records produced streams (even with no consumer attached) so a finished result survives a closed laptop, and a reconnect replays it with zero GPU work. From the Python side:

  • ctx.recordings — the recordings client: retention and pinning for the current session's recordings.
  • ctx.completing_work(max_seconds=...) — guard a bounded item (one render) so a browser disconnect mid-generation can't discard it; the item finishes, streams, and is recorded.
async with ctx.completing_work(max_seconds=30):
    image = await pipe.generate(prompt)
    await ctx.stream("out").send(image)    # emits → DVR records → replayable

Recordings outlive the session: reconnecting to an ended session whose recording exists replays the recording as a normal session stream — no GPU is allocated, and the browser consumes it with the same session.stream(name) handle (.seek(0) to start from the beginning). Today this covers video streams; if no recording exists, the reconnect is an honest dead end rather than a fake replay.

Recording is opt-out and retention is explicit: pass record=False to ctx.stream(...) to keep any stream out of the DVR, or contact us to disable recordings for your organization entirely. Unpinned recordings are deleted automatically after the 24-hour default TTL — see Data handling for the full retention model.

Ending cleanly

A session ends for exactly one honest, named reason — max_session_age, idle_timeout, lease lapse, or an authoritative close. In a frame loop, cooperate with the gate so every rank stops on the same frame:

while ctx.session_live():
    frame = step()

The browser sees the same honesty: a 9-phase lifecycle with typed failure reasons and an endsAt expiry it can render — see the TypeScript SDK.

Related:

  • Compute — shapes, warm capacity, concurrency
  • App — the full decorator kwarg story
  • Context referencectx.session_live, ctx.completing_work, ctx.recordings, ctx.requests

On this page