Anatomy of a uRun app
The canonical shape of a uRun app — clean imports, a frozen config, app-bound store loaders, the handler that composes inline, and the self-test gate
A uRun app is small on purpose. The heavy, stateful, and IO concerns all sit behind primitives, so a correctly-written app is mostly composition. This page is the canonical shape every reference app follows — read it top to bottom and you have the whole contract.
The whole shape
import urun
from dataclasses import dataclass
from urun import App
from urun.core import Dependencies
app = App("restyle")
store = app.store # store bound to app → cache keys auto org+app-scoped
# De-vendor upstream model code at module top (see /docs/python-sdk/de-vendoring)
REPO = store.repo("github.com/org/restyle-model@<sha>", include=["restyle"], diff="fork.patch")
DEPS = Dependencies(python=[*REPO.deps(), "torch>=2.5.0"])
@dataclass(frozen=True)
class RestyleConfig: # one frozen config — no scattered constants, no env-as-config
fps: int = 24
window: int = 24
codec: str = "h264"
@property
def bitrate(self) -> int: # derived values are @property, not more constants
return self.fps * 350_000
CFG = RestyleConfig()
@store.cache(key="restyle-14b") # AUTO org+app-scoped; weights residency + snapshot exclude/restream
def load_model(*, device): # store.cache loaders take device from the platform
with REPO: # scoped import — resolves only inside the block
from restyle import RestyleModel
return RestyleModel.from_pretrained(store.path("hf://org/restyle-14b")).to(device)
@app.function(gpus="b200:1", deps=DEPS)
async def runtime(ctx: urun.Context):
"""Live video restyle: webcam frames in, restyled H.264 out, steered by the control doc."""
device = ctx.device # platform-assigned — never hardcode "cuda"
models = load_model(device=device)
doc = ctx.doc("control")
pipeline = (
ctx.stream("cam")
| ctx.window(window=CFG.window)
| ctx.source(make_restyle(models, doc), device=device)
| doc.bind(lambda d: ctx.pipe(make_vae(models, d)), [doc.desired.vae.model])
| ctx.pace(fps=CFG.fps)
| ctx.stream("video", codec=CFG.codec, bitrate=CFG.bitrate, fps=CFG.fps)
)
with ctx.self_test(): # warmup, precompile, and validation in one end-to-end pass
_drive_one_and_assert_shape(pipeline)
await pipeline.run() # keep-alive until the session disconnectsThat is a complete, production-grade app. The sections below are the rules that keep it this small.
The five moving parts
store = app.store— the store, bound to the app.@store.cache(key=...)keys are automatically scoped to your org + app, so two tenants never collide. Content-addressed data (store.model/store.path/store.repo) stays global and deduped. See Store.REPO+DEPSat module top — de-vendor the upstream model withstore.repo("…@sha"), splice its own dependency list withREPO.deps(). No vendored trees, no hand-copiedrequirements.txt.@store.cache(key=...)loaders — load weights once. The cache is the weights-residency tier and the snapshot collaborator: cached weights are excluded from the process snapshot on capture and streamed back on restore.- The pipeline — the unified Stage DSL: the inbound stream is the head, reactive control is inline live reads +
doc.bind, the encoder is a primitive. with ctx.self_test():thenawait pipeline.run()— one representative pass validates the composed pipeline (and becomes your CPU test), then the pipeline runs continuously until disconnect. See Strict mode & CPU testing.
App-hygiene rules
These are what keep the app small, predictable, and CPU-testable. The conformance guard enforces them.
Config is one frozen dataclass, not environment variables
Configure via @app.function(...) kwargs and one @dataclass(frozen=True) config object (CFG), with derived values as @property. Environment variables are not a config mechanism. No os.environ.get("APP_*"), no setdefault forests, no module-constant forest, no escape-hatch flags. The only env vars that remain are external inputs — secrets, and launcher/k8s-injected runtime values like RANK / WORLD_SIZE / LOCAL_RANK. Determinism is also what makes the app traceable on CPU.
Compose inline in the @app.function body
Compose the pipeline, run the self-test, and call pipeline.run() inline in the function body. There is no build_*_pipeline() helper indirection, no StreamingSessionRuntime / driver base class, no control inversion. The reader sees the whole runtime in one function.
No bootstrap boilerplate
The platform launcher puts the app directory on sys.path before importing your module (tests get the same from conftest.py). So the top of your app is just clean imports — no ensure_bootstrap(...) call, no # noqa: E402 import dance.
Take device from the platform
device = ctx.device. Never hardcode "cuda" — a hardcoded device is both a placement bug and a strict-mode violation (it can't run on the CPU harness). Pass device= into your store.cache loaders and stages.
Docstrings are for a human
Write the docstring for a person reading the app — what it does and the shape, like the one-liner above.
Tests live in <app>/backend/tests/
Never inside the runtime package. The runtime package is the app; the tests are next to it, not in it.
@app.function — supported kwargs
The current @app.function kwarg set: gpus, cpus, memory, deps, credentials, env, lease, max_session_s, session_connect_timeout, session_disconnect_grace, session_idle_timeout, replicas, warm, max_concurrency, sessions_per_pod, sessions_per_user, tier, snapshot, expandable_segments, gpu_pack (+ gpu_pack_sm_partition / gpu_pack_limits), session_kind. See the full reference for each one's semantics.
Retired kwargs that raise a loud TypeError: timeout, event_name (functions are dispatched by their Python name, not an event), gpus_per_replica, topology, colocate_on_node, scaling, gang (cross-app calls via App.ref are best-effort by definition — there is no declaration surface for cross-app callees). The platform schedules and colocates; the model owns its own parallelism.
timeout was not renamed — it was split into two distinct primitives. lease is the renewable session length: auto-renewed while the client is connected, so a healthy session keeps running. max_session_s is the hard, non-renewable cap: renewal is refused past it and the session ends honestly with terminal reason max_session_age. See Sessions.
If you are porting an existing codebase or pointing an AI coding agent at uRun, the build method lives in AI-Assisted Setup.
Related:
- Streaming Pipelines — the unified Stage DSL the pipeline is built from
- Strict mode & CPU testing —
ctx.self_test+ the zero-app-code CPU harness - De-vendoring —
store.repo+REPO.deps() - Store —
app.storeand@store.cache