feat(examples): switch hello-world and tiles to single-shot - #39
Conversation
go-livepeer v0.9.0 ships single-shot payments (go-livepeer#4000) on top of fixed pricing (go-livepeer#3999), so the request/response examples no longer need the persistent-mode stopgap. hello-world and tiles now register mode="single-shot" with unit="fixed": the orchestrator reserves a session per call and releases it on return, and the client pays the 402 challenge inline via runner_selector + call_runner — no reserve/stop, no payment loop. The tiles capacity showcase survives unchanged: in-flight calls hold capacity slots and a full runner refuses with 503, which the client waits out with backoff. Refs #5 (vllm intentionally stays persistent: per-token billing is brokerage for the gateway/signer layer). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TNptCFhk6SHnioSgpSPVMu
There was a problem hiding this comment.
Pull request overview
This PR updates the hello-world and tiles example apps to use Live Runner single-shot mode with fixed pricing, removing the prior persistent-mode stopgap and simplifying client call flows to no longer manage runner sessions.
Changes:
- Switch both runners to
mode="single-shot"andunit="fixed"; update pricing language to “USD per call / per tile”. - Update both clients from
reserve_session → call_runner → stop_runner_sessiontorunner_selector → call_runner, with capacity/backoff handled at the call level (tiles). - Refresh example and root documentation (
READMEs,.env.examples, compose comments) to match the single-shot + fixed-pricing behavior.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| tiles/runner.py | Registers tiles runner as single-shot and updates fixed-price semantics to “per tile”. |
| tiles/README.md | Updates tiles example docs to describe single-shot calling and capacity behavior via 503 retries. |
| tiles/compose.onchain.yml | Updates on-chain compose comments to reflect per-tile single-shot fixed pricing. |
| tiles/client.py | Replaces session lifecycle with runner_selector → call_runner and adds retry behavior around 503 capacity refusals. |
| tiles/.env.example | Updates pricing documentation to “billed once per tile/call” for fixed pricing. |
| README.md | Updates the repo-wide examples table, runner mode explanations, and calling flows for single-shot vs persistent. |
| hello-world/runner.py | Registers hello-world runner as single-shot with fixed pricing billed once per call. |
| hello-world/README.md | Updates hello-world docs to describe the single-shot discovery+call flow and inline payment challenge handling. |
| hello-world/compose.onchain.yml | Adds fixed-pricing guidance comments for on-chain runs. |
| hello-world/client.py | Simplifies the client to runner_selector → call_runner (no reserve/stop session). |
| hello-world/.env.example | Updates on-chain pricing guidance for fixed pricing billed once per call. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The module docstrings already explain the paid path; keep the grep marker bare. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TNptCFhk6SHnioSgpSPVMu
Compose files point at .env.example, which now attributes the cap to its actual sources: the signer's 100-tickets-per-payment limit and the demo orchestrator's -ticketEV=1e9. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TNptCFhk6SHnioSgpSPVMu
Drop the go-livepeer#4000 reference and the redundant billing-model prose; say what each knob does in one line. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TNptCFhk6SHnioSgpSPVMu
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
hello-world/client.py:51
cursor.candidates[0]can raiseIndexErrorif discovery returns a cursor with no candidates, which would bypass theLivepeerGatewayErrorhandling and produce an unhelpful crash. Guard for the empty-candidates case and raise a user-facingLivepeerGatewayErrorinstead.
cursor = await runner_selector( # Livepeer: 1
discovery_url=args.discovery, app=APP_ID
)
runner = cursor.candidates[0]
log.info("app_url=%s", runner.url)
tiles/client.py:114
runner = cursor.candidates[0]can raiseIndexErrorif discovery returns an empty candidate list. Treat this as a retryable "no runner available" condition so tiles don't crash with an uncaught exception.
cursor = await runner_selector( # Livepeer: 1
discovery_url=discovery_url, app=APP_ID
)
runner = cursor.candidates[0]
return await call_runner( # Livepeer: 2
Single-shot session mechanics live in the READMEs; keep only the price-unit one-liners the code cannot show. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TNptCFhk6SHnioSgpSPVMu
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (3)
tiles/client.py:127
- This comment says anything but HTTP 503 is a real error, but the code also retries
NoRunnerAvailableError/NoOrchestratorAvailableError(which are not 503). Either narrow the retry set or adjust the comment so it matches the actual behavior.
# Anything but "insufficient capacity" (503) is a real error, not a
# full runner.
tiles/client.py:135
- The warning message implies this is always a capacity-slot issue, but this loop also retries on discovery/selection failures. Consider wording that doesn’t mislead when the root cause is e.g. an unreachable discovery URL.
"tile (%d,%d) no slot yet, retrying until --slot-timeout: %s",
tiles/client.py:105
- The retry-loop comment is too narrow: this handler retries not only when the runner is at capacity (503) but also when discovery/selection fails (e.g. no orchestrator/runner available). Updating the comment helps future readers understand why these exceptions are treated as retryable.
This issue also appears in the following locations of the same file:
- line 126
- line 135
# A full runner refuses the call with 503; wait for a slot instead of failing the
# tile. Re-discover each attempt so a restarted runner is picked up.
tiles docstring said one session per tile; the root README still claimed live runners were not on go-livepeer main and drew only the persistent client flow; run-without-docker sections pointed at the ja/live-runner branch instead of the v0.9.0 release. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TNptCFhk6SHnioSgpSPVMu
What
Switches the two request/response examples to their natural mode:
hello-worldandtilesnow registermode="single-shot"withunit="fixed". go-livepeer v0.9.0 (already pinned by the examples) ships fixed pricing (livepeer/go-livepeer#3999) and single-shot payments (livepeer/go-livepeer#4000), so the "stays persistent until single-shot payment lands" stopgap is no longer needed.Changes
mode="single-shot"plusunit="fixed";--priceis USD per call (hello-world) or per tile (tiles). The orchestrator reserves a session per call and releases it when the response returns.reserve_session/call_runner/stop_runner_sessioncollapses torunner_selectorpluscall_runner, which answers the 402 payment challenge inline: one fixed payment, no payment loop, no leaked sessions if a client crashes mid-call.--reserve-timeoutrenamed to--slot-timeout)..env.examples, compose comments, and the root README (examples table, runner modes, mode-specific calling flows) updated. The price cap note now attributes the ~0.00019 USD ceiling to the signer's 100-tickets-per-payment limit and the demo orchestrator's-ticketEV=1e9. Also fixes stale claims that live runners are not in mainline go-livepeer (they ship in v0.9.0; only the Python SDK still comes fromja/live-runner).Verification (offchain,
livepeer/go-livepeer:v0.9.0)hello-world: discovery advertises"mode": "single-shot"with the proxied/apps/{runner_id}/appURL; the client prints{'message': 'Hello, livepeer!'}.tiles:CAPACITY=1serializes behind 503 retries (9 tiles in 15.1s);CAPACITY=9runs them in parallel (4.4s); byte-identicaltiles-out.pngboth ways.reservePaidLiveRunnerSessionflow validated for fixed pricing in feat(tiles): showcase fixed pricing on go-livepeer v0.9.0 #36.Refs #5.
vllmintentionally stays persistent: per-token billing belongs to the gateway/signer layer.