feat(live-runner): session-owned payment lifecycle for streamed sessions#31
feat(live-runner): session-owned payment lifecycle for streamed sessions#31rickstaa wants to merge 5 commits into
Conversation
reserve_session now returns the LivePaymentSession built during the reserve payment challenge (previously discarded). Add run_session_payments(session), a timer-driven loop that calls LivePaymentSession.send_payment on an interval to keep a long-lived session funded — the orchestrator meters open sessions by wall-clock time and releases them when the balance runs dry. No-op offchain. Reusable across any held-open transport (trickle today, websockets next). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a session-owned payment lifecycle to LiveRunnerSession so streamed (trickle/websocket) sessions stay funded without the caller hand-managing a background task. Mirrors the lv2v Lv2vJob.start_payment_sender/close shape, but uses the transport-agnostic interval driver (run_session_payments) since the general live-runner path has no single output stream to meter and must also cover websocket. - LiveRunnerSession (still frozen) gains start_payments(), aclose(), and async context manager support; _payment_task stored via object.__setattr__. start_payments is idempotent, a no-op offchain, and warns instead of raising when called without a running loop. - run_session_payments now pays immediately before the first sleep (a cold start can leave a long gap after the reservation payment) and logs+retries per-cycle failures instead of dying. - reserve_session threads payment_interval through to the session. Callers can now do `async with await reserve_session(...) as session:` and get automatic start/stop, or use start_payments()/aclose() manually. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
…o server tick Refinements from reviewing the go-livepeer orchestrator design (ai_http.go ReserveLiveRunnerSession): after the reservation 402, the orchestrator holds the session as a prepaid balance debited by a server-side ticker (-livePaymentInterval, 5s default) and silently releases it when underfunded. The client just keeps crediting out-of-band, which is what run_session_payments already does. Two corrections: - Treat HTTP 482 / SkipPaymentCycle as a healthy "balance current" gate (debug, keep looping) instead of logging it as a payment failure. The orchestrator uses it to prevent overpayment; only genuine errors warn. - Document that payment_interval must stay at or below the orchestrator's livePaymentInterval (5s), which is why the 3s default carries margin. Add a test asserting a skip cycle does not kill the loop. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks for this @rickstaa . I spent some time reviewing this, comparing to how Scope does it and filling in some gaps (in the runner implementation, not your code). From Claude: 1. Session liveness in the payment loopThe loop currently has no notion of whether the session it's funding still exists, and only stops if See livepeer/go-livepeer@e545b98 for a workaround on the go-livepeer-side: it adds a payment endpoint scoped to the session that returns a 404 if the session doesn't exist. The loop should point to that endpoint and, on a 404, stop sending payments (and surface it as "session released"). To more easily construct the payment URL on the client, a base control URL was added in livepeer/go-livepeer@81f24cf ... take the control_url with a 2. send_payment doesn't disable cert verification like the rest of the SDKLivePaymentSession.send_payment POSTs via a default aiohttp.ClientSession (normal TLS verification), while every async JSON helper forces TCPConnector(ssl=False) and the sync lv2v sender uses an unverified context. For consistency it might be best to match the existing behavior. The SDK already has the right primitive: _post_empty in live_runner.py does exactly this (POST empty body, raise on >= 400) with TCPConnector(ssl=False). Suggest hoisting _post_empty into http.py (next to post_json, and to avoid the live_runner ← remote_signer import cycle) and routing the payment POST through it. This also gives a single place to distinguish the 404/403 status codes needed. We can have the SDK verify TLS by default if TOFU is disabled (which it should be for the live runner) but that's a larger change outside the scope of this PR. 3. Symmetrical stop_payments() (optional)Soft suggestion: it might be nice to have a small stop_payments() that cancels only the payments loop without stopping the session, as a symmetrical complement to start_payment. This might be handy for tests and any "drain / hand off funding" flow. Session tear-down via close already stops payments so this is purely optional. |
bd8e780 to
81ee7be
Compare
9a9f821 to
81ee7be
Compare
…g sessions
Address review feedback on the payment lifecycle:
- Pay via the session-scoped {control_url}/payment endpoint from the reserve
response. A 404 there means the orchestrator released the session: the loop
stops and marks the session released (session.released / wait_released());
409 (fixed-price) stops follow-up payments; aclose() skips the stop call for
released sessions. Falls back to the generic /payment endpoint when the
orchestrator predates control_url.
- Hoist _post_empty into http.py as post_empty (ssl=False like every other
helper, typed status errors) and route LivePaymentSession.send_payment
through it.
- Add LiveRunnerSession.stop_payments() as the symmetric complement to
start_payments().
- Make on-chain sessions self-funding: reserve_session() auto-starts the
payment loop (auto_pay=True) so any transport against app_url stays paid
for, and derives the cadence from the challenge's payment_interval_ms when
the orchestrator reports it (60% of the server tick; 3s fallback).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CBkCesUhvPkNquGK48V84c
…ner-session-payments # Conflicts: # src/livepeer_gateway/live_runner.py
|
Update 2026-07-26: merged the latest ja/live-runner (89ac0f7, decimal USD per hour pricing) into this branch (6ad5e2f); one conflict in call_runner resolved keeping both the fixed-price payment_session handling and server_payment_interval. All 31 tests pass. Validated end to end on-chain (Arbitrum mainnet) via the flux-klein example (livepeer/app-examples#35): session reserved and funded through the remote signer, tickets signed at the new price scale, clean release. Once this lands, flux-klein and the streamdiffusion examples can re-pin from rs/live-runner-session-payments to ja/live-runner. 🤖 Generated with Claude Code |
Draft for review and comparison against Josh's
ja/live-runnerbranch.What this adds
Streamed live-runner sessions (trickle / websocket / SSE / plain HTTP against
app_url) must keep paying for as long as the session is open. After the reservation 402, go-livepeer holds the session as a prepaid balance: a server-side ticker (-livePaymentInterval, 5s default) debits the balance and releases the session once it runs dry. Unlikecall_runner(request/response, where the orchestrator pulls payment via a 402 inline), a held-open transport has no request to attach payment to, so the client pushes on a cadence below the server tick.This makes
LiveRunnerSessionown that lifecycle so callers do not hand-manage a background task. On-chain sessions are self-funding by default:reserve_session()starts the payment loop automatically, and the loop stops itself once the orchestrator reports the session gone.Commits
3b60852surface payment session and addrun_session_payments(the interval payment loop).37ee769make the session own start/stop of that loop.81ee7behandle the 482 skip-payment gate and anchor the interval to the server tick.b817c09review follow-up: session-scoped payments + liveness, TLS-consistent sender,stop_payments(), auto-started self-funding sessions, cadence frompayment_interval_ms.Changes
{control_url}/payment(from the reserve response, go-livepeer #3938) instead of the blind/paymentcredit endpoint. A 404 means the orchestrator released the session: the loop stops and surfaces it viasession.released/await session.wait_released();aclose()then skips the redundant stop call. 409 (fixed-price session) stops follow-up payments quietly. Orchestrators that predatecontrol_urlfall back to the generic endpoint._post_emptyis hoisted intohttp.pyaspost_empty(TCPConnector(ssl=False)like every other helper, typed status errors via the shared error mapper) andLivePaymentSession.send_paymentroutes through it — one place to distinguish 404/409/403.stop_payments()(review pt. 3): symmetric complement tostart_payments(); cancels only the loop, session stays reserved, restartable for drain/hand-off flows.reserve_session(auto_pay=True)starts payments at reserve time (matching Scope'sconnect()and this SDK'sregister_runner()heartbeat pattern), so a baresession = await reserve_session(...)is no longer silently released ~5s later.auto_pay=Falserestores the manual lifecycle.payment_intervalnow defaults to 60% of the orchestrator's advertisedpayment_interval_ms(5s tick → 3s cadence), falling back to 3s when absent. Reporting the interval is go-livepeer #4001 (open); the SDK side is forward/backward compatible either way. Explicitpayment_intervalstill wins.Usage
Design notes vs lv2v and scope
LivePaymentProcessoronlivePaymentInterval), so interval push aligns more directly with the server than per-segment does.wait_released().Tests
tests/test_live_runner_payments.py(24 cases): offchain no-op, immediate first payment, session-scoped vs generic endpoint routing, 404 → released, 409 fixed-price stop, transient/HTTP-5xx retry, skip-cycle gate, idempotency,stop_payments(incl. restart),aclose(incl. released skip), async context manager,payment_interval_mschallenge parsing,reserve_sessionauto-start / opt-out / derived and explicit intervals. Full suite: 31 passed.Relates to ENG-130.
🤖 Generated with Claude Code