Teleop guide (beyond-line-of-sight teleoperation)
Teleop guide (beyond-line-of-sight teleoperation)
⚠️ Not yet released: in-development v1.5.0-SNAPSHOT capability; interface and protocol may change. Do not use in production before the official release.
New in v1.5.0. Teleop = beyond-line-of-sight remote takeover: a remote operator drives the robot's voice interaction over SDK, independent of local visibility. Third interaction mode alongside passive / active, using its own event family and the
AgentPolicy.TELEOPpolicy. The Python API mirrors Java 1:1 with PEP 8snake_casenaming.
When to use
- Robot is unattended or deployed in poor visibility / network conditions and a human operator needs to take over specific interactions.
- The application needs to bypass the LLM and let a human voice-drive a short window (soothing complaints, verifying orders, etc.).
- The operator's audio is uploaded via SDK and routed by the gateway to the robot addressed by
agent_id.
Flow
Strict order: enter → any number of start_audio / append_audio_delta / done_audio → exit.
keepalive may be called at any frequency during the session (recommended every 10 seconds, see Robot online detection (keepalive)).
enter exit
┌───────────────────────┐ ┌────────────────────┐
│ TeleopRequest.enter │ │ TeleopRequest.exit │
│ (on_enter_ack) │ │ (on_exit_ack) │
└───────────┬───────────┘ └────────────┬────────┘
│ any number of times │
▼ (fire-and-forget) │
┌───────────────────────┐ │
│ start_audio(param) │ │
│ append_audio_delta(*)│─────────────────────┘
│ done_audio │
└───────────────────────┘
┌─────────────────────────────┐
│ TeleopRequest.keepalive │ ← every 10 s recommended; code != 0 means offline
│ (on_keepalive_ack) │
└─────────────────────────────┘
enter/keepalive/exithave ack callbacks.code == 0= success,code == -1= server rejected (msgcarrieserrorMsg),code == 1000= local connection closed (callback fires synchronously on caller thread).start_audio/append_audio_delta/done_audioare fire-and-forget (no ACK).
Event family & wire format
Python event-type enum lives at linksoul_agentsdk.enums.AgentEventType:
| Enum member | Event type | Meaning |
|---|---|---|
AGENTSDK_TELEOP_ENTER | agentsdk.teleop.enter | Enter |
AGENTSDK_TELEOP_ENTER_ACK | agentsdk.teleop.enter_ack | Enter ack |
AGENTSDK_TELEOP_AUDIO_START | agentsdk.teleop_audio.start | Audio segment start (may carry param) |
AGENTSDK_TELEOP_AUDIO_DELTA | agentsdk.teleop_audio.delta | Audio delta (audio field is base64, optional gain is the audio-gain factor, default 1.0) |
AGENTSDK_TELEOP_AUDIO_DONE | agentsdk.teleop_audio.done | Audio segment end |
AGENTSDK_TELEOP_KEEPALIVE | agentsdk.teleop.keepalive | Robot online probe (SDK → gateway) |
AGENTSDK_TELEOP_KEEPALIVE_ACK | agentsdk.teleop.keepalive_ack | Probe ack (gateway → SDK) |
AGENTSDK_TELEOP_EXIT | agentsdk.teleop.exit | Exit |
AGENTSDK_TELEOP_EXIT_ACK | agentsdk.teleop.exit_ack | Exit ack |
Every teleop envelope carries:
{
"type": "<event type>",
"agentId": "<target robot agentId>",
"teleopId": "teleop_<21 random chars>",
"eventId": "event_<21 random chars>",
"agentMode": "teleop"
}
teleopId is generated by IdGenerator.generate_teleop_id(); agentMode is always "teleop" (matches AgentPolicy.TELEOP).
start_audio parameter
start_audio(param=None) accepts an optional AgentParam per audio segment for voice / VAD tuning:
| Key | Type | Example | Meaning |
|---|---|---|---|
role | str | "male" / "female" | Teleop voice character |
threshold | int | -14 ~ 14 | VAD threshold — larger values make it harder to be interrupted |
Python construction:
start_param = (
AgentParam.create()
.set_string("role", "male")
.set_integer("threshold", 0)
)
event_id = teleop.start_audio(start_param)
On the wire these travel as param.extParam.role / param.extParam.threshold (matching Java).
Robot online detection (keepalive)
A teleop session usually lasts a while, during which the robot may go offline due to network loss, low battery, or on-site power failure. The SDK exposes TeleopRequest.keepalive(...) as a one-shot probe. Applications should call it periodically (recommended every 10 seconds) to check whether the robot is still online.
Signature & callback
class TeleopKeepaliveCallback:
"""Robot online probe callback"""
def on_keepalive_ack(self, teleop_id: str, event_id: str, code: int, msg: str) -> None:
raise NotImplementedError
# TeleopRequest method
def keepalive(self, keepalive_callback: TeleopKeepaliveCallback, timeout: int) -> str: ...
timeoutis a local placeholder (< 50is clamped to 50, same asenter/exit).- Returns the outgoing message's
event_id. - Each call sends one
agentsdk.teleop.keepalivemessage; the gateway routes it to the robot identified byteleop_id; the robot responds withsuccess == truewhen online.
code semantics (same as enter / exit)
code | Meaning |
|---|---|
0 | Robot is online and responding |
-1 | Server returned failure, msg carries errorMsg — treat as robot offline, must be handled |
1000 | Local WebSocket is closed; the probe was not actually sent |
Periodic probe + auto exit on offline
Any code != 0 should be treated as "robot went offline" — stop the timer, exit the session, and unregister_teleop to release resources, so no further append_audio_delta is wasted on a dead session.
import threading
from linksoul_agentsdk import TeleopKeepaliveCallback, TeleopExitCallback
def start_keepalive(teleop_request, agent_sdk, interval_sec: int = 10):
"""Call after enter succeeds; probes every interval_sec seconds"""
state = {"online": True}
def tick():
if not state["online"]:
return
class _Cb(TeleopKeepaliveCallback):
def on_keepalive_ack(self, teleop_id, event_id, code, msg):
if code != 0 and state["online"]:
state["online"] = False
print(f"robot offline => code={code}, msg={msg}")
class _ExitCb(TeleopExitCallback):
def on_exit_ack(self, teleop_id, event_id, code, msg):
agent_sdk.unregister_teleop(teleop_id)
teleop_request.exit(_ExitCb(), 2000)
teleop_request.keepalive(_Cb(), 2000)
if state["online"]:
threading.Timer(interval_sec, tick).start()
threading.Timer(interval_sec, tick).start()
return state # caller may set state["online"] = False to stop probing
Also set state["online"] = False when your business logic invokes exit, to stop further probes.
API cheat-sheet
- Request class:
linksoul_agentsdk.TeleopRequest - Callback bases:
linksoul_agentsdk.TeleopEnterCallback/TeleopKeepaliveCallback/TeleopExitCallback - Register:
AgentSdk.register_teleop(TeleopRequest)/AgentSdk.unregister_teleop(teleop_id) - ID:
IdGenerator.generate_teleop_id()
Full signatures: Teleop API. Runnable example: Teleop example.
Lifecycle & cleanup
AgentSdkTeleopMgrmaintains active requests keyed byteleop_id. Alwaysregister_teleop(...)before callingenter.- Every request method call refreshes an internal
update_ts; requests idle for 7200 s are GC'd by a background sweeper. - After
exit(or when abandoning the session), callagent_sdk.unregister_teleop(teleop_id)to release eagerly. AgentSdk.release()clears all teleop requests owned by this instance (bulk-removed by_linksky_clientreference).
Java ↔ Python mapping
| Java | Python |
|---|---|
TeleopRequest.enter(param, cb, timeout) | TeleopRequest.enter(param, cb, timeout) |
TeleopRequest.startAudio(param) | TeleopRequest.start_audio(param=None) |
TeleopRequest.appendAudioDelta(eventId, base64) | TeleopRequest.append_audio_delta(event_id, audio_delta) |
TeleopRequest.doneAudio(eventId) | TeleopRequest.done_audio(event_id) |
TeleopRequest.keepalive(cb, timeout) | TeleopRequest.keepalive(cb, timeout) |
TeleopRequest.exit(cb, timeout) | TeleopRequest.exit(cb, timeout) |
TeleopEnterCallback.onEnterAck(...) | TeleopEnterCallback.on_enter_ack(...) |
TeleopKeepaliveCallback.onKeepaliveAck(...) | TeleopKeepaliveCallback.on_keepalive_ack(...) |
TeleopExitCallback.onExitAck(...) | TeleopExitCallback.on_exit_ack(...) |
AgentSdk.registerTeleop(...) | AgentSdk.register_teleop(...) |
AgentSdk.unregisterTeleop(...) | AgentSdk.unregister_teleop(...) |
IdGenerator.generateTeleopId() | IdGenerator.generate_teleop_id() |