Built seven examples against ja/live-runner in runner-app-examples and repeatedly had to read go-livepeer's Go structs, or the SDK source, to answer questions the SDK could have answered itself. Collecting the gaps.
Docs and discoverability
README has no .md extension, so GitHub renders it as plain text. Its 142 lines are maintainer instructions (protobuf generation, pytest, coverage) — there is no usage documentation. Someone landing here learns how to rebuild the protos, not how to reserve a session. Suggest: rename to README.md, lead with a quickstart for both shapes (runner_selector → call_runner, and reserve_session → call → stop_runner_session), move maintainer material to CONTRIBUTING.md.
- No
py.typed. The package is thoroughly annotated, but without the marker every consumer's type checker treats it as untyped. One empty file plus a packaging entry turns all existing annotations into working documentation.
- Module-level docstrings. The first question is "which module do I import from" —
selection vs live_runner vs discovery is not self-evident, and per-function prose does not answer it.
examples/ mixes two eras. write_control.py, subscribe_events.py, start_job.py, and in_out_composite.py are lv2v; echo/, ping-pong/, and text/ are live runner. Pattern-matching from that directory can produce start_lv2v(StartJobRequest(...)) in the belief it is the live-runner API. Worth labelling or separating.
- Linking runner-app-examples from the README would help — it is already the de-facto usage documentation, verified end to end.
Discovery ergonomics
LiveRunnerInstance types a minority of what discovery publishes. url, app, mode, and price_info are typed; gpu, metadata, version, capacity, capacity_used, and capacity_available are reachable only through raw, despite go-livepeer publishing all of them (ai/runner/live_runner.go:189-199). Suggest typing every field the discovery schema defines, keeping raw for anything newer than the installed SDK. A metadata_json() helper would also stop every caller reimplementing the same parse — but the field should stay str, since the protocol guarantees an opaque string.
- Filtering the candidate list costs you the failover.
cursor.candidates is a tuple copy, so narrowing it means abandoning cursor.next() and hand-rolling the retry loop, losing cursor.rejections with it. Something like cursor.filter(pred) / cursor.order_by(key) that narrows in place would keep one code path. Discovery filters are exact set membership (discovery.py:303), so any selection on capacity, GPU, or metadata is client-side today.
- Candidates are unordered. They arrive in discovery order, so
candidates[0] — which every example uses — is arbitrary rather than cheapest or least loaded. Worth deciding whether a default ordering belongs in the SDK, since it affects how work spreads across orchestrators.
Runtime state
- No public setter for
status or capacity, and no way to force an immediate heartbeat. Both exist privately (registration._status, registration._send_heartbeat()). They are needed for the one workaround available to an app whose registrations share a GPU (see go-livepeer#4015): flip every registration out of ready together when the hardware is saturated, and beat immediately rather than waiting out the interval. An example should not have to reach into private attributes to do that.
Happy to take any of these as PRs.
Built seven examples against
ja/live-runnerin runner-app-examples and repeatedly had to read go-livepeer's Go structs, or the SDK source, to answer questions the SDK could have answered itself. Collecting the gaps.Docs and discoverability
READMEhas no.mdextension, so GitHub renders it as plain text. Its 142 lines are maintainer instructions (protobuf generation, pytest, coverage) — there is no usage documentation. Someone landing here learns how to rebuild the protos, not how to reserve a session. Suggest: rename toREADME.md, lead with a quickstart for both shapes (runner_selector→call_runner, andreserve_session→ call →stop_runner_session), move maintainer material toCONTRIBUTING.md.py.typed. The package is thoroughly annotated, but without the marker every consumer's type checker treats it as untyped. One empty file plus a packaging entry turns all existing annotations into working documentation.selectionvslive_runnervsdiscoveryis not self-evident, and per-function prose does not answer it.examples/mixes two eras.write_control.py,subscribe_events.py,start_job.py, andin_out_composite.pyarelv2v;echo/,ping-pong/, andtext/are live runner. Pattern-matching from that directory can producestart_lv2v(StartJobRequest(...))in the belief it is the live-runner API. Worth labelling or separating.Discovery ergonomics
LiveRunnerInstancetypes a minority of what discovery publishes.url,app,mode, andprice_infoare typed;gpu,metadata,version,capacity,capacity_used, andcapacity_availableare reachable only throughraw, despite go-livepeer publishing all of them (ai/runner/live_runner.go:189-199). Suggest typing every field the discovery schema defines, keepingrawfor anything newer than the installed SDK. Ametadata_json()helper would also stop every caller reimplementing the same parse — but the field should staystr, since the protocol guarantees an opaque string.cursor.candidatesis a tuple copy, so narrowing it means abandoningcursor.next()and hand-rolling the retry loop, losingcursor.rejectionswith it. Something likecursor.filter(pred)/cursor.order_by(key)that narrows in place would keep one code path. Discovery filters are exact set membership (discovery.py:303), so any selection on capacity, GPU, or metadata is client-side today.candidates[0]— which every example uses — is arbitrary rather than cheapest or least loaded. Worth deciding whether a default ordering belongs in the SDK, since it affects how work spreads across orchestrators.Runtime state
statusorcapacity, and no way to force an immediate heartbeat. Both exist privately (registration._status,registration._send_heartbeat()). They are needed for the one workaround available to an app whose registrations share a GPU (see go-livepeer#4015): flip every registration out ofreadytogether when the hardware is saturated, and beat immediately rather than waiting out the interval. An example should not have to reach into private attributes to do that.Happy to take any of these as PRs.