Skip to content

feat: define the Robot/Operator role split in Rust, consume it from Python#58

Open
pham-tuan-binh wants to merge 3 commits into
mainfrom
feat/ffi-role-split
Open

feat: define the Robot/Operator role split in Rust, consume it from Python#58
pham-tuan-binh wants to merge 3 commits into
mainfrom
feat/ffi-role-split

Conversation

@pham-tuan-binh

Copy link
Copy Markdown
Collaborator

What

Move the Robot/Operator role split out of hand-written Python and into the Rust FFI layer, then point the Python facades at it. The split is now defined once in Rust and every binding (Python today, C#/Swift/Kotlin later) inherits it from UniFFI.

Why

The role split (which methods each role exposes, role pinning) was reimplemented in Python's __init__.py. Any second binding would have to hand-reimplement it again. Defining it once as UniFFI objects removes that per-language duplication. The core crate stays unified — both roles still drive the same core::Portal.

Commits

  1. feat(ffi) — add RobotConfig/OperatorConfig and Robot/Operator as thin UniFFI objects over the unified PortalConfig/Portal. Each exposes only role-appropriate methods; wrong-role methods are absent by construction rather than runtime-erroring.
  2. feat(python) — rewire the Python role facades onto _ffi.RobotConfig/_ffi.OperatorConfig and _ffi.Robot/_ffi.Operator via a new Portal._over constructor. The shared ergonomic layer (typed values, numpy, asyncio callback hopping) is unchanged.
  3. test — end-to-end coverage of the raw FFI role objects.

The public Python API is unchanged. The unified Portal/PortalConfig remain for v0.1 compatibility.

Verification

Mirrors the CI pipeline locally:

  • cargo clippy --workspace -- -D warnings clean
  • cargo test --workspace --locked — 96 passed
  • Release FFI build + pytest unit suite — 47 passed
  • Full integration suite against a local LiveKit server — 97 passed, 2 failures

The 2 integration failures (test_large_frame_above_data_packet_cap, test_4k_frame_roundtrip[raw]) are pre-existing large raw-frame transport issues, verified by reproducing them identically on unmodified main. CI does not run integration (no server), so they don't affect this PR's checks.

Notes

  • Generated bindings (livekit_portal_ffi.py, the cdylib) are gitignored and built in CI, so they're not in the diff.
  • lerobot packages use only the public API, which is unchanged.

Add RobotConfig/OperatorConfig and Robot/Operator as thin UniFFI objects
wrapping the unified PortalConfig/Portal. Each pins its role internally
and exposes only role-appropriate methods (no send_action on Robot, no
send_state/send_video_frame on Operator), so every foreign binding
inherits the role split straight from UniFFI instead of reimplementing
it per host language.

The core crate stays unified: both roles drive the same core::Portal.
Configs are distinct objects purely for type safety at the boundary.
Robot/Operator also forward the introspection getters (state_fields,
action_fields, video_tracks, frame_video_tracks, action_chunks) so any
inner object is interchangeable for binding-side ergonomic layers.
Rewire the Python role facades onto the new bindings so the role split
is defined once in Rust rather than hand-reconstructed in Python:

  * RobotConfig/OperatorConfig now wrap _ffi.RobotConfig/_ffi.OperatorConfig.
    The role is pinned in Rust; schemas are read back from the FFI config
    instead of mirrored Python-side.
  * Robot/Operator back their Portal with _ffi.Robot/_ffi.Operator via a
    new Portal._over constructor.
  * Extract Portal._init from __init__ so the unified Portal and the role
    facades share one ergonomic layer (typed values, numpy, asyncio
    callback hopping). A factory handles that callbacks must be passed at
    FFI construction time.

The unified Portal/PortalConfig still wrap _ffi.Portal/_ffi.PortalConfig
for v0.1 compatibility. The public Python API is unchanged.
Add an integration test that drives the raw _ffi.Robot/_ffi.Operator
objects directly (the facade tests exercise them indirectly). Covers
role-appropriate method presence/absence, control plane, state and
observation flow, action flow, RPC roundtrip (handler registered
pre-connect), and metrics. Registered in conftest's collect_ignore so a
serverless `pytest` still skips integration cleanly.
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.

1 participant