Skip to content

Make the SDK legible: usage docs, shipped types, and discovery ergonomics #58

Description

@rickstaa

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_selectorcall_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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions