Repository navigation
feat(transport): WebRTC DataChannel transport (cloudflare SFU) - #2048
Merged
Merged
Conversation
spomichter
force-pushed
the
feat/webrtc-transport
branch
from
May 11, 2026 22:44
e56fd1f to
212450b
Compare
…e SFU Implements a new pubsub transport backed by WebRTC DataChannels over Cloudflare's Realtime SFU. Two new classes in dimos/protocol/pubsub/impl/webrtcpubsub.py: - CloudflareSession: manages the WebRTC PeerConnection lifecycle. Opens two CF sessions (publisher + subscriber) so a single process can do loopback pubsub. Runs aiortc on a dedicated background asyncio thread with its own ThreadPoolExecutor (so we don't leak asyncio_N worker threads). Uses negotiated=True placeholder DCs with id=100 during transport establishment to avoid stream-id collisions with CF-assigned ids. - WebRTCPubSub: bytes-on-the-wire pubsub facade matching the LCMPubSubBase / BytesSharedMemory interface (string topics, bytes payloads). Lazily creates pub/sub DataChannel pairs on first publish/subscribe per topic. Also adds: - WebRTCTransport in dimos/core/transport.py (mirrors LCMTransport pattern, no encoding - bytes only). - WebRTC benchmark testcase in dimos/protocol/pubsub/benchmark/testdata.py, gated on aiortc + CF_TELEOP_APP_ID / CF_TELEOP_APP_SECRET env vars. - Integration test in dimos/protocol/pubsub/impl/test_webrtcpubsub.py covering basic pub/sub, latency, and throughput (all live tests skip without CF credentials). - aiortc + httpx as new 'webrtc' optional extra in pyproject.toml. Live benchmark (us-east-2 -> CF edge): - 64-256B: ~10K msgs/s, 0% loss - 1KiB: ~7K msgs/s, 0% loss - >= 64KiB: dropped (above SCTP message size) - Median single-RTT: ~2.5 ms
…sport - Add BrokerProvider: DataChannelProvider that works through the hosted teleop broker (dimensional-teleop) instead of directly with CF credentials. Handles session registration, heartbeat loop, and DataChannel creation when an operator joins via the broker's bridge-datachannel API. - Extend WebRTCTransport with optional msg_type parameter for typed LCM encode/decode with fingerprint-based filtering. Multiple transports can share a single multiplexed DataChannel and each receives only its type. - Add hosted teleop blueprints (dimos/teleop/hosted/) demonstrating the module-free architecture: make_teleop_hosted_go2() uses pure transport (zero modules), make_teleop_hosted_go2_scaled() adds a thin TeleopScalerModule for speed scaling only. - Add unit tests for typed mode, fingerprint filtering, multiplexed dispatch, and BrokerProvider credential validation.
- Rebase on main and regenerate uv.lock (resolve conflict) - Add _LoopbackProvider (in-process, no network) to benchmark testdata - Enables local WebRTC transport benchmarking without CF credentials - All 12 message sizes pass locally (2.78s total)
spomichter
force-pushed
the
feat/webrtc-transport
branch
from
May 19, 2026 23:30
212450b to
2c263eb
Compare
Codecov Report❌ Patch coverage is @@ Coverage Diff @@
## main #2048 +/- ##
==========================================
- Coverage 72.53% 72.07% -0.47%
==========================================
Files 905 941 +36
Lines 82233 84147 +1914
Branches 7432 7703 +271
==========================================
+ Hits 59648 60645 +997
- Misses 20646 21464 +818
- Partials 1939 2038 +99
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 61 files with indirect coverage changes 🚀 New features to boost your workflow:
|
WebRTCPubSub over an in-memory loopback provider — measures the webrtc pubsub/dispatch stack with no network, SCTP, or credentials. Gives every contributor a webrtc row in the matrix (~2.4M msg/s stack ceiling) and an upper bound to read the credential-gated WebrtcCloudflare row against.
Dreamsorcerer
previously approved these changes
Jul 7, 2026
paul-nechifor
previously approved these changes
Jul 7, 2026
If createDataChannel raised inside the heartbeat, the id was already recorded, so every later heartbeat skipped the open and the channel stayed dead until the operator id changed. Extract _reconcile_channels and store the id only after a successful open so the next beat retries. Also drop needless forward refs in Blueprint.transports (review nit).
spomichter
dismissed stale reviews from paul-nechifor and Dreamsorcerer
via
July 7, 2026 22:59
12173ae
…pping Per Paul's review: a dev or CI env without the webrtc deps used to skip the entire suite invisibly. The guard asserts WEBRTC_AVAILABLE when CI is set; downstream/no-network installs (Sam's case) keep skipping.
Per review: the repo convention moved dev-only/live tests to tool_*.py filenames (never collected; run by explicit path) and removed the tool marker codebase-wide. Cred-gating skipifs stay for keyless path runs.
paul-nechifor
previously approved these changes
Jul 7, 2026
tool_*.py dev-only test files (renamed from test_*.py in ea4db73) fell out of the test_ filename exclusion, leaking TwistSource/TwistSink from the webrtc e2e tool into all_modules and failing the CI currency check.
mustafab0
approved these changes
Jul 7, 2026
spomichter
added a commit
that referenced
this pull request
Jul 8, 2026
…issing root cause of the arm failures: the tests job only apt-installed portaudio, x64 runners had libturbojpeg by accident from the runner image. install it explicitly and turn the silent skip into a CI assert (same treatment as the webrtc aiortc guard from #2048), skip stays for local no-syslib installs
jeff-hykin
pushed a commit
that referenced
this pull request
Oct 18, 2026
…her backends supported(#2048) Co-authored-by: ruthwikdasyam <ruthwikdasyam@gmail.com> Co-authored-by: Sam Bull <git@sambull.org> Co-authored-by: Paul Nechifor <paul@nechifor.net>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
DimOS has LCM, SHM, DDS, and ROS transports for local/LAN communication but no transport for internet-scale real-time data (NAT traversal, global edge routing). Hosted teleop required a dedicated
HostedTeleopModulewrapper to bridge WebRTC DataChannels to DimOS streams.Solution
A WebRTC DataChannel transport backed by Cloudflare Realtime, following the same pubsub abstraction as
LCMTransport/SHMTransport. Hosted teleop becomes a one-line transport swap on a normal blueprint — no wrapper module:How to run it against ruthwik's hosted stack
dtk_live_…key.dimos run teleop-hosted-go2-transport --helplists alltransports.broker.*fields):dimos run teleop-hosted-go2-transport -o transports.broker.api_key=dtk_live_... # or via env: TRANSPORTS__BROKER__API_KEY=dtk_live_... dimos run teleop-hosted-go2-transportTELEOP_API_KEYenv fallback was removed when transport config moved into the blueprint config flow.)TwistStampedoncmd_unreliable; the transport decodes it straight onto the go2cmd_velstream. NoHostedTeleopModuleanywhere in the path.Verify without credentials (no key, no robot)
The full pubsub benchmark (
uv run pytest -s dimos/protocol/pubsub/benchmark/tool_benchmark.py) also runs keyless for all local transports. The liveWebrtcCloudflarerow additionally self-registers whenCF_TELEOP_APP_ID/CF_TELEOP_APP_SECRETare set (the app secret must never leave server-side environments) — for that row, run withDIMOS_BENCH_RECEIVE_TIMEOUT_S=5so the CF round-trip can drain; sizes >16K skip (CF DataChannel message cap).Architecture (
dimos/protocol/pubsub/impl/webrtc/)providers/spec.py—Providerprotocol,AsyncProviderBase(shared loop-thread lifecycle), andProviderConfig: a picklable, hashable factory resolving to a per-process singleton provider. Transports survive pickling into module worker processes; all transports in a process share one PeerConnection.providers/broker.py— bidirectional broker-mediated CF Realtime (hosted teleop). Topic == DataChannel name:cmd_unreliablestate_reliablestate_reliable_backpublish()(reliable; drops while no operator is connected — normal pubsub)Heartbeat acks carry all three SCTP ids; channels open/close/re-open per operator join/leave/rejoin.
providers/cloudflare.py— direct CF access (pub+sub session loopback pair); integration tests + benchmark only.webrtcpubsub.py—WebRTCPubSub(AllPubSub[str, bytes]), passes the standard grid tests inpubsub/test_spec.py.WebRTCTransport[M: DimosMsg](core/transport.py) — typed LCM encode/decode + wire-fingerprint demux on a multiplexed channel;CloudflareTransportbindsBrokerConfig(→BrokerProvider) for blueprints. Fingerprints derive from the wire format, not_get_packed_fingerprint()—TwistStampedinheritsTwist's fingerprint but encodes as LCMTwistStamped; class fingerprints would drop every real message (regression-tested).HostedTeleopModuledeprecationThe module now emits a
DeprecationWarning. Commands and telemetry are covered byCloudflareTransport; video-track publishing is the one remaining capability and stays on the module untilBrokerProvidergrows media support, after which it gets deleted. (Web-UI note: the browser currently parsesstate_reliable_backas its JSON control plane, so consuming LCM telemetry in the web app is a small front-end follow-up; the robot side is done.)End-to-end verification (live, teleop.dimensionalos.com)
test_broker_e2e.py— aiortc stands in for the browser operator, speaking the web client's exact protocol (join → wait connected → bridge-datachannel → negotiated channels); the robot side is the blueprint's exactCloudflareTransportobjects. Env-gated (TELEOP_API_KEY,TELEOP_ROBOT_ID,TELEOP_OPERATOR_TOKEN),-m tool:cmd_unreliable→CloudflareTransport→ typedTwistStamped: 40/40 delivered, values verifiedCloudflareTransport("state_reliable_back").broadcast()→ operator: delivered + LCM-decodedBenchmark (standard harness, sustained 1s windows, this machine → CF edge)
CF paces DataChannel forwarding at ~1k msg/s per channel — plenty for teleop command planes (50–100 Hz). Same-host loopback RTT through the CF edge: median ~12–16 ms. Benchmark knobs are env-overridable (
DIMOS_BENCH_DURATION_S,DIMOS_BENCH_MAX_MESSAGES,DIMOS_BENCH_RECEIVE_TIMEOUT_S).Breaking changes
None — new optional transport.
HostedTeleopModulekeeps working (with aDeprecationWarning);teleop-hosted-go2(video path) is unchanged.Future work
Video track moves into
BrokerProvider(media-track support), the web app learns LCM telemetry onstate_reliable_back, thenHostedTeleopModuleand theteleop-hosted-go2wrapper blueprint are deleted.Contributor License Agreement