livepeer / livepeer/livepeer-python-gateway

Encapsulate live-runner session payments in a stateful session/client object

Aperta
#30 2 commenti 0 reazioni 1 assegnatario Vedi su GitHub

@rickstaa ci sta già lavorando.

Dal 17/6/2026.

  • #31 di @rickstaa — chiusa senza merge
enhancement
Lingua principale
Python
Stelle
1
Fork
7
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Descrizione

## Problem

Streamed live-runner sessions (trickle / websocket) must keep paying for as long as the session is open, because the orchestrator meters the open session by wall-clock/segments and drops it when the balance runs dry. Today the SDK exposes this as a free function, `run_session_payments(session, interval=...)`, which the caller must drive manually:

```python
payment_task = asyncio.create_task(run_session_payments(session, interval=...))
...
finally:
payment_task.cancel()
await payment_task
```

`reserve_session` already mints and attaches the `payment_session` credential to the returned `LiveRunnerSession`, but `LiveRunnerSession` is a frozen dataclass with no lifecycle, so it cannot own the background task. That pushes task creation and (critically) cancellation onto every consumer. Forget the `finally` and you silently stop paying or leak a task. The echo example (`example-apps/echo/client.py`) shows the boilerplate this requires.

## Prior art (already in our codebase)

Two better-encapsulated patterns already exist and prove the approach:

- `lv2v.py`: `Lv2vJob.start_payment_sender()` runs a per-output-segment payment sender, auto-started by `start_lv2v(start_payments=True)` and cancelled by `job.aclose()`. The consumer cannot forget it.
- daydreamlive/scope `ja/runner` (`server/livepeer_client.py`): `LivepeerClient` starts an interval `_payment_loop` right after connect, gated on `payment_session is not None`, and cancels it on teardown. This is effectively `run_session_payments` hand-rolled inside a client object.

## Proposal

Make `reserve_session` return a stateful session/client that owns the payment lifecycle, so payments are automatic for on-chain sessions:

```python
async with await reserve_session(app=..., signer_url=signer) as session:
# publish/consume trickle; payments start and stop automatically
```

- Auto-start the payment loop when `payment_session is not None` (on-chain only; no-op offchain).
- Auto-cancel on context exit / `aclose()`, alongside `stop_runner_session`.
- Keep `run_session_payments` as the internal primitive.
- This mirrors `start_lv2v(start_payments=True)` + `job.aclose()`.

Payments still need a scope anchor (the session must know when the stream ended); the context manager / `aclose()` provides it. The object cannot infer that media stopped on its own.

## Open questions

1. One unified `LiveRunnerSession` object that subsumes `Lv2vJob`, or keep lv2v separate and only wrap the general live-runner path? The former removes the parallel implementations; the latter is the minimal change.
2. Cadence semantics to standardize on: lv2v pays per output segment (only while media flows); `run_session_payments` pays on a fixed interval (pays even when paused-but-open). These differ in over-payment behavior and should be decided before standardizing the interface.

## Context

Follow-up from the echo example migration and the `run_session_payments` work (branch `rs/live-runner-session-payments`). Related: gateway PR #25 (SSE streaming).

Guida per i contributori

Nessuna guida per i contributori indicizzata per questo repository

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Valutazione

Questa issue non è ancora stata valutata.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.