Skip to content

Live Runner + Scope support#20

Draft
j0sh wants to merge 40 commits into
mainfrom
ja/live-runner
Draft

Live Runner + Scope support#20
j0sh wants to merge 40 commits into
mainfrom
ja/live-runner

Conversation

@j0sh

@j0sh j0sh commented May 21, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

j0sh added 30 commits May 15, 2026 16:43
The new runner uses `address` as an opaque blob
Registers the app as a runner with an orchestrator.
None of these have anything to do with orchestrators.
Keep only small shims for backwards compatibility.
@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cb4eb06d-3d45-44d0-b413-24abe2ae3133

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ja/live-runner

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread src/livepeer_gateway/channel_reader.py Fixed
Comment thread src/livepeer_gateway/live_runner.py Fixed
Comment thread src/livepeer_gateway/media_output.py Fixed
Comment thread src/livepeer_gateway/channel_reader.py Fixed
Comment thread src/livepeer_gateway/live_runner.py Fixed
Comment thread src/livepeer_gateway/live_runner.py Fixed
Comment thread src/livepeer_gateway/media_output.py Fixed
@j0sh
j0sh force-pushed the ja/live-runner branch from d0d3453 to 2bc02a7 Compare May 27, 2026 16:29
Comment thread src/livepeer_gateway/live_runner.py Fixed
Comment thread src/livepeer_gateway/live_runner.py Fixed
Comment thread src/livepeer_gateway/live_runner.py Fixed
Comment thread src/livepeer_gateway/live_runner.py Outdated
runner_url: str,
app: str,
price_per_unit: int = 0,
pixels_per_unit: int = 1,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@j0sh can we use something like unit_scale since this runner also allows different pricing schemes. Also see my pull request in go-liveper livepeer/go-livepeer#3942.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll look into that idea (and the general move from away from pixels to purely timing) but don't want to block this merge for that

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@j0sh yea no blocker from my side I see this as a nice follow up improvement for consistency. I am tracking this in #27.

Comment thread src/livepeer_gateway/live_runner.py Outdated
secret: str,
runner_url: str,
app: str,
price_per_unit: int = 0,

@rickstaa rickstaa Jun 29, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@j0sh How do you intend pricing to work for dynamic runners? Right now the app self-asserts price_info via register_runner(price_per_unit=…) and go-livepeer trusts it (only > 0 is checked in normalizeHeartbeat). That's fine for operator-deployed/trusted containers — the operator sets the price via env and the app forwards it — but an untrusted image could ignore that and under-report its price. Static sidesteps this (operator sets price_info in runners.json); only dynamic trusts the app.

You can see how I'm currently using this in the hello_world example, which feels a bit strange since it relies on the app to create the argument and forward it.

Two thoughts:

  • If the orchestrator is still meant to set the price in dynamic mode (to make gaming harder), the SDK could auto-read it from env (e.g. PRICE_PER_UNIT) instead of the app passing it to register_runner, keeping pricing a deployment/operator concern, out of app code.
  • I think we'll eventually move to GPU-based pricing (orchestrator states a price per GPU type, workloads auto-run at that rate), so go-livepeer would override the reported price at registration anyway.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct, the runner itself reports the price because the orchestrator is intended to control the runner.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This way runner provisioning (including pricing) can be configured separately from go-livepeer without having to introduce a mutual dependency on one other. Setting the price on go-livepeer itself introduces a tension with the orchestrator needing to be configured separately with details of the runner / workload, hardware, etc, as opposed to runners being able to simply connect and go.

If you want to keep the configuration / pricing within go-livepeer then static configuration is the way to go.

if wait_callback and (timeout is None or timeout > 0):
try:
await self.wait_callback(timeout=timeout)
except asyncio.TimeoutError:
task.cancel()
try:
await task
except asyncio.CancelledError:
def _release_session_id(self, session_id: str) -> None:
try:
self._active_session_ids.remove(session_id)
except ValueError:
if wait_callbacks and (timeout is None or timeout > 0):
try:
await self.wait_callbacks(timeout=timeout)
except asyncio.TimeoutError:

async def _maybe_await(value: object) -> None:
if inspect.isawaitable(value):
await value


class LiveRunnerSessionHeaders(Protocol):
def get(self, key: str, default: str = "") -> str: ...

async def _maybe_await(value: None | Awaitable[None]) -> None:
if inspect.isawaitable(value):
await value
seanhanca and others added 2 commits July 17, 2026 23:06
…ge into ja/live-runner (#46)

## Why
The production SDK (`sdk-service:byoc-dual-path-1bf13cd`) carries a
**load-bearing byoc-payment fix that exists in no branch** — only in the
running container. That's an operational liability and it blocks a
unified gateway. This ports it onto `ja/live-runner` (the Live Runner +
Scope branch, PR #20 → main) so the consolidated gateway keeps BYOC
payment working while gaining LR.

## What
- `_payment_type_for_signer(signer_url)` — **legacy Daydream signer**
(`signer.daydream.live`) → `type:"lv2v"` + string `capability`; **modern
signers** (pymthouse DMZ, …) → `type:"byoc"` + BYOC capabilities
protobuf.
- `_create_byoc_payment` — assemble the payment payload + orch-discovery
capabilities per the resolved type.
- `capabilities.py` — `CapabilityId.BYOC` +
`byoc_capabilities_from_app()`.

Two files, +52/−9. Byte-identical to what runs in prod today.

## Relationship to #41
PR #41 sends `type:"byoc"` **unconditionally** and depends on an
undeployed go-livepeer signer+orch change. Against
`signer.daydream.live` (which only accepts `lv2v` today) that reproduces
the **2026-07-13 “invalid job type” outage**. This PR is the
**superset**: per-signer switching keeps the legacy signer working *and*
enables modern signers. Recommend closing #41 in favor of this.

## Follow-on
The same per-signer type logic generalizes to the **live-runner**
payment path, which will let us drop the `lr-gateway` `lv2v` workaround
once this lands.

## Test
- [ ] `python -m py_compile` (passes locally)
- [ ] `submit_byoc_job` against `signer.daydream.live` → `type:lv2v` →
200 (no regression)
- [ ] `submit_byoc_job` against a modern signer → `type:byoc` + caps
proto → 200

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ibling to #46) (#47)

Sibling to #46. #46 gives the **BYOC** payment path a per-signer
dual-path; this gives the **Live Runner** payment path the same, so
selecting an LR runner doesn't break payment on the default (Daydream)
signer.

## The bug
`_get_runner_payment` (live_runner.py) hardcoded `type="live"` for every
non-scope runner. `signer.daydream.live` only accepts `lv2v` → **`400
invalid job type`** → LR payment fails. The `lr-gateway` `lv2v` patch
only fixed the isolated sidecar; the gateway itself was still broken for
LR + Daydream. Without this, the transparent-routing plan's **Scenario 1
(Daydream + LR) breaks**.

## The fix
- Canonical `_payment_type_for_signer` moved to **`remote_signer.py`**
(the module that owns `LivePaymentSession`) so **both** payment paths
share it and LR does **not** import the soon-deprecated `byoc.py`.
Legacy Daydream → `lv2v`; modern signers → `byoc`.
- `_get_runner_payment`: Scope/LV2V stays `lv2v`; every other runner
uses the per-signer switch.

Two files, +28/−2. Both compile.

## Verified
Generalizes the exact fix proven on-chain via the lr-gateway `lv2v`
patch: LR single-shot + Daydream signer → `Payment tickets processed,
totalTickets=1` on Arbitrum.

## Follow-up
Once #46 merges, `byoc.py`'s local `_payment_type_for_signer` copy
should import this canonical one (trivial dedup) — the two PRs touch
disjoint files so they merge without conflict.

## Test
- [ ] `py_compile` (passes locally)
- [ ] LR + Daydream signer → `type:lv2v` → ticket redeems (the
Scenario-1 gate)
- [ ] LR + modern signer → `type:byoc` (gated on the pymthouse upstream
fix + orch byoc-single-shot verification)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants