Publish and subscribe live media and data over QUIC — audio, video, robot/game frames — fanned out by the relay.
Beyond the request/response RPC surface, ClutchCall exposes a realtime
track API: a publisher streams a named track into the relay, and any number
of subscribers receive it live over QUIC. Tracks ride
MoQT (Media-over-QUIC Transport)
and are fanned out by the ClutchCall relay mesh, so a robot, an agent, a
browser, and a recorder can all consume the same stream without the publisher
knowing they exist.Every SDK ships a MoqtClient with method names in that language’s idiomatic
case. The C++, Python, and Go SDKs share one engine via the
clutchcall_moqt_ffi native library; the TypeScript SDK is a standalone
WebTransport implementation with its own queueing, late-join, and reconnect
behaviour. Treat the two families as separate conformance surfaces — a result on
one does not automatically carry to the other until they’re held to a common
behaviour contract (publish/subscribe APIs match; queue depth, filter handling,
and unsubscribe lifecycle can differ).
Robot telemetry, LIDAR, game state — opaque binary with a per-frame priority.
per group
Text
Reliable ordered messages (chat, control).
per 64 msgs
A track is addressed by a namespace + name (e.g. robot/turtlebot4-001
odom) and carries a capability string — an open-ended routing intent
("asr", "tts", "ros.telemetry", "media.passthrough") that the relay /
gateway uses to route the track to whatever registered that capability. You
route on intent, not on media kind.
connect returns immediately and dials in the background. The client
auto-reconnects with capped exponential backoff if the link drops, and
re-establishes every publication and subscription on each reconnect — your
code does nothing. on_state reports the lifecycle:
State
Meaning
Connecting
dialling the relay
Connected
session up; tracks (re)attached
Reconnecting
link dropped; retrying with backoff
Closed
you called close()
Failed
unrecoverable (e.g. bad URL)
You may publish or subscribe before the session is up — calls are queued
and replayed once connected (and the relay holds a subscribe for a namespace
that has not been announced yet, so a robot can subscribe to its command track
before any controller has connected).
Frame tracks carry opaque binary objects with a per-frame priority — the right
fit for robot telemetry and game state. Below: a publisher streaming a track
and a subscriber receiving it, fanned out through the relay.
Keep the subscription handle alive for as long as you want frames. In
garbage-collected languages (Python/Java/C#) it owns the native callback; if it
is collected, the engine calls into freed memory. The publication handle owns
the track — drop it (or call close) to stop publishing.
Audio tracks are the same shape with codec metadata instead of a priority —
publish_audio(ns, name, capability, sample_rate, channels, frame_ms) and
subscribe_audio(ns, name, on_frame), where on_frame(ts_us, bytes) delivers
one decoded object. Use them for live voice between an SDK client and an agent;
the capability (“asr”, “tts”, …) routes the track to the right module.
Telemetry vs commands: give command (inbound) tracks a distinct
namespace from telemetry — e.g. publish telemetry under robot/<id> and
subscribe commands under robot/<id>/ctl. If both share robot/<id>, the
relay can route a command subscription to the robot’s own telemetry announce.
Capability is the routing key. Two publishers may use different
capabilities on the same namespace; subscribers and modules select by intent.
Fan-out is free. The relay copies each object to every subscriber; the
publisher opens one stream per group regardless of subscriber count.
See the per-language SDK reference for the full method list, and
Architecture for how the relay mesh fans tracks out.