Example apps that run on the Livepeer live runner — go-livepeer's new way to run any app on the network. Each app is a plain HTTP / WebSocket / video service: an orchestrator running the live runner hosts it, and a client calls it through the orchestrator with the livepeer-gateway SDK.
The point is to swap the compute without changing your app — permissionlessly, no lock-in. Your app stays a plain service with little or no Livepeer-specific code, so you're never tied to us. And the network is permissionless: anyone can run or extend it, no one gatekeeps what you deploy, and no single party can take your app down. Write the app once; move the compute freely.
Note
Live runners ship in mainline go-livepeer since v0.9.0. The Python SDK still comes from the ja/live-runner branch until it is published to PyPI.
Your app is a plain service that clients reach through the orchestrator — the SDK handles discovery / session / payment, and, on-chain, a remote signer settles it. The client never talks to your app directly.
flowchart LR
client["Client<br/>(livepeer-gateway SDK)"]
orch["Orchestrator<br/>proxy · discovery · payment"]
app["Your app<br/>HTTP / WebSocket / trickle"]
signer["Remote signer<br/>(on-chain)"]
client -->|"single-shot: discover → call · persistent: + reserve/release"| orch
orch -->|"forwards your endpoints, unchanged"| app
app -.->|"dynamic: register_runner · static: runners.json"| orch
signer <-.->|"micropayment tickets"| orch
The orchestrator is a transparent reverse proxy: every endpoint you expose is passed through to your app unchanged, so you write an ordinary service and it runs on the network as-is. The transports supported today:
- HTTP request/response — the common case. (
hello-world,tiles,api-proxy) - HTTP + SSE — streamed / token responses. (
vllm) - Trickle — continuous realtime video in/out. (
echo) - WebSocket — long-lived bidirectional sessions. (external:
scope)
Need a schema that isn't here? Open an issue.
| Example | Goal | Registration | Mode | Transport | Pricing |
|---|---|---|---|---|---|
hello-world |
The simplest app: one request, one response | dynamic | single-shot | HTTP (JSON) | fixed |
tiles |
Capacity fan-out — one call per tile | dynamic | single-shot | HTTP (base64 PNG) | fixed |
api-proxy |
Pass calls through to a hosted API — the operator holds the key, callers pay per call | static | single-shot | HTTP (JPEG bytes) | fixed |
echo |
Realtime video, transformed and echoed back | dynamic | persistent | trickle | — (offchain only) |
vllm |
Drop-in OpenAI API; the client stays unmodified | static | persistent (single-shot by nature) | HTTP + SSE | hour |
Start with hello-world (the smallest end-to-end path); the others each layer on one new idea. More will follow, including a full example that exercises every feature. Each is self-contained and runs offchain (free, no wallet); most also run on-chain (paid) — see each README.
This set stays minimal and curated: it covers each value of the axes above (registration, mode, transport, pricing), not one example per app. New examples land here only when they fill a gap in that table — apps built on the runner belong in their own repos, listed under External examples.
How the app attaches to the orchestrator:
- Dynamic — the app self-registers via the SDK (
register_runner) and heartbeats; the orchestrator drops it when heartbeats stop. Best for apps that come and go. (hello-world,echo) - Static — the orchestrator is configured with the app's URL in a
runners.jsonand health-polls it; the app needs no SDK. Best for fixed, long-running deployments. (vllm,api-proxy)
The arrow flips — dynamic, the app announces itself; static, the orchestrator is told about a passive app:
flowchart LR
subgraph Dynamic
direction LR
a1["App<br/>(embeds SDK)"] -->|"register_runner + heartbeat"| o1["Orchestrator"]
end
subgraph Static
direction LR
o2["Orchestrator<br/>(reads runners.json)"] -->|"health-poll"| a2["App<br/>(no Livepeer code)"]
end
Chosen at registration (above); defaults to persistent — set on both register_runner(...) and in runners.json. The examples set it explicitly.
- Persistent — a held-open session the client reserves and releases, billed per second of wall-clock (or once, with fixed pricing). Best for realtime / streaming. (
echo,vllm) - Single-shot — one request in, one response out; the orchestrator reserves a session per call and releases it when the response returns, so the client manages no session at all. Best for batch / request-response. (
hello-world,tiles,api-proxy)
Note
The vllm example is single-shot by nature but stays persistent for now: it meters per second across a reserved session, and true per-token billing is brokerage for the gateway/signer layer.
The client side depends on the runner's mode:
- Single-shot — discover → call: find the app via
runner_selector, then onecall_runner. The orchestrator reserves a session for the call and releases it when the response returns; on the paid pathcall_runneranswers the 402 payment challenge inline. (hello-world,tiles,api-proxy) - Persistent — discover → reserve → call → release: reserve a session (
reserve_session), call it —call_runner, streamed frames, or a WebSocket, depending on transport — then release it (stop_runner_session), which settles payment on-chain. (echo,vllm)
Each example's client.py shows its exact calls — grep # Livepeer: to find them.
Apps that integrate the live runner and live in their own repos — production deployments and standalone examples alike. This table is links-only: the code, CI, and support stay with the author.
| Project | What it is | Transport |
|---|---|---|
| daydreamlive/scope | Real-time AI video with downloadable LoRA models | WebSocket + trickle |
| livepeer/api-proxy | Attach several API endpoints dynamically — key storage and request stats for operators | HTTP |
Built one? Open a PR that adds a row. To make your repo easy to find, follow the community convention:
- Name the repo
<app>-livepeer-runner(e.g.comfyui-livepeer-runner). - Add the
livepeer-runnerGitHub topic. - Mention Livepeer in the repo description.
Each example is self-contained and its README has the run commands. Everything below is the shared setup they all build on — the examples spin up a local orchestrator (and, on-chain, a signer) via the compose files here, so you set this up once, not per example.
-
Docker for the end-to-end demos. They use the mainline
livepeer/go-livepeer:v0.9.0release image, so there is nothing to build. -
Python 3.12+ and
uvfor the client. -
The
livepeer-gatewaySDK from theja/live-runnerbranch (not yet on PyPI):pip install "git+https://github.com/livepeer/livepeer-python-gateway@ja/live-runner"
The orchestrator and signer services are defined once at the repo root and pulled into each example with Docker Compose extends, so examples don't duplicate them:
compose.orchestrator.yml— the offchain orchestrator (-useLiveRunners).compose.onchain.yml— adds a remote signer and re-points the orchestrator on-chain.
On-chain runs add a remote signer that holds the payer wallet and mints probabilistic micropayment tickets; the orchestrator redeems the winning ones. Shared across examples:
- Wallets stay outside the repo —
*_KEYSTORE_DIRpoints at go-livepeer keystores (mounted read-only); only the address + password come from.env. .envis per example and gitignored — copy.env.exampleand fill in RPC, network, keystore paths, accounts, and pricing (it holds the keystore password).- Runner price is a plain USD amount: the app advertises
PRICE(e.g.0.01).currencyandunitdefault tousd/hour, so onlypriceis required. Withhourthe orchestrator converts it to wei via the price feed and meters the session per second. Apps with bounded per-call work can registerunit="fixed"to bill the price once per session (see hello-world, tiles). The signer caps what it pays atMAX_PRICE_PER_UNIT, compared per billing unit: one second of runtime when metered (0.000111USD is about 0.40 USD/hour), the whole session price when fixed. - Payments are probabilistic — on a short run you'll rarely see a redemption; that's expected.
Before running a client, confirm the orchestrator actually advertises your runner with the expected price by calling /discovery directly:
curl -sk https://localhost:8935/discovery | jqEach entry lists its runners with an app, version, capacity, and price_info. The orchestrator republishes your USD price converted to wei — price in wei with currency: wei and unit: seconds (720p-pixel-seconds for per-pixel video, fixed for once-per-session pricing). Check that your app appears and the price is non-zero.
- Apps bind to
127.0.0.1by default (safe for local runs). In a container the compose files pass--host=0.0.0.0so the orchestrator can reach the app. - Orchestrators serve a self-signed TLS cert; the SDK skips verification.
Issues and PRs welcome — see CONTRIBUTING.md.