Skip to content

Subsystem SDK Reference

This page is the table-oriented catalog for public subsystem-author SDK surfaces. For usage guidance, start with Subsystem SDK API Guide.

Role Classes

Role Python class C++ class Purpose
gateway r2kernel.wrapper.GatewaySubsystem r2kernel::wrapper::GatewaySubsystem Hardware or simulator-backed device boundary
component r2kernel.wrapper.ComponentSubsystem r2kernel::wrapper::ComponentSubsystem Reusable robotics control function
compound r2kernel.wrapper.CompoundSubsystem r2kernel::wrapper::CompoundSubsystem Robot physical and spatial composition
app r2kernel.wrapper.AppSubsystem r2kernel::wrapper::AppSubsystem Robot task, scenario, SI logic, or product application

All role classes inherit the common Subsystem behavior.

Construction And Manifest

Surface Python C++ Purpose
Construct subsystem GatewaySubsystem(...), ComponentSubsystem(...), CompoundSubsystem(...), AppSubsystem(...) role-specific class constructed from SubsystemOptions Create one runtime subsystem
Name name constructor argument SubsystemOptions::name Runtime subsystem name
Manifest path manifest_path SubsystemOptions::manifest_path Load subsystem.yaml declaration
Deployment manifest path deployment_manifest_path use launch bindings or manifest-driven startup Optional source-level debug binding data
Launch bindings environment / launcher managed SubsystemOptions::launch_bindings Runtime binding hints outside deployment
Declared APIs manifest-derived declared_apis declared_apis() APIs declared by public interfaces
Declared components manifest-derived declared_components declared_components() component contracts declared by public interfaces
Declared capabilities manifest-derived declared_capabilities available in manifest status capability contracts declared by public interfaces

Code-level declare_api_contract and declare_component are disabled for ordinary Python subsystem authors. Public declarations should come from subsystem.yaml.

Binding And Configuration Methods

Purpose Python C++
Resolve required interface resolve_binding(requirement) use launch bindings and control resolution through wrapper/runtime
Resolve target subsystem name binding_target(requirement) use resolved binding target
Require legacy app-facing capability require_capability(binding, capability=None) use resolved binding plus contract-specific wrapper code
Read effective runtime bindings runtime_bindings() reported in heartbeat status
Read launch bindings launch_bindings property SubsystemOptions::launch_bindings
Read config data config_data package/runtime config through app code
Read config value config_get(...) package/runtime config through app code
Set config value config_set(..., save=False) package/runtime config through app code
Reload config config_reload() package/runtime config through app code
Save config config_save() package/runtime config through app code

Compound Export Support

Export support differs by language and surface.

Surface Supported behavior
Python SDK static deployment resolver resolves an app binding through a compound export to the actual component provider and validates the export path
Python SDK runtime resolver accepts only a bound runtime export response with both resolved fields; failure states are callable errors, not compound-alias fallbacks
C++ wrapper validates compound export declarations, including component conformance, in the manifest loader and reports exports in heartbeat status
C++ tutorial support helper statically checks export-chain contract equality, component role, public visibility, and cycles for tutorial bindings; it does not load the component conformance_apis dictionary and is not a runtime resolver API
Rust CLI backend loads and validates manifest export declarations for r2kernel deploy validate; this is a CLI/runtime capability, not a Rust SDK resolver

The public subsystem-author SDKs are Python and C++. Runtime and CLI validation capabilities do not imply a public Rust subsystem SDK.

Lifecycle Methods

Purpose Python C++
Initialize initialize() initialize()
Start start() start()
Pause request pause() pause()
Resume request resume() resume()
Stop request stop() stop()
Fault request fault(reason=None) fault(reason)
Degrade request degrade(reason=None) degrade(reason)
Reset reset() reset()
Terminate terminate() terminate()
Generic transition request_lifecycle_transition(...) request_lifecycle_transition(...)

Lifecycle Hooks

Hook Python C++ Required
Initialize implementation on_initialize() on_initialize() yes
Start implementation on_start() on_start() yes
Terminate implementation on_terminate() on_terminate() yes
Reset implementation on_reset() on_reset() yes
Pause implementation on_pause() on_pause() no
Resume implementation on_resume() on_resume() no
Stop implementation on_stop() on_stop() no
Fault implementation on_fault(reason=None) on_fault(reason) no
Degraded implementation on_degraded(reason=None) on_degraded(reason) no
State change callback on_state_changed(state) on_state_changed(state) no

Register all declared endpoints before on_initialize() returns. The SDK validates declarations after on_initialize() and before the subsystem reaches Ready; default strict validation errors fail initialization and are not automatically downgraded to warnings. In C++, install a raw Control handler with start_control_server(handler) during on_initialize() as well. An endpoint added in on_start() cannot satisfy the initialization gate.

Ready is not Operating. A registered handler can receive a command before the subsystem enters Operating, and the SDK does not gate handler dispatch by lifecycle state. Component or Gateway handler code must check its lifecycle state and safely reject commands that are not yet valid. Starting is accepted only from Ready or Stopped after initialization succeeds; stop or reset cannot replace a failed initialization.

Event Methods

Purpose Python C++
Register publisher register_event_publish(topic=..., type=..., size=..., src_attr=..., priority=..., queue_depth=...) register_event_publish(EventPublishSpec)
Register subscriber register_event_subscribe(topic=..., type=..., size=..., dest_attr=..., callback=...) register_event_subscribe(EventSubscribeSpec)
Publish sample publish_event(topic=..., value=..., timestamp=...) publish_event(topic, EventPayload, timestamp_ns, priority)
Start transport start_event_transport() start_event_transport()
Stop transport stop_event_transport() stop_event_transport()

Event endpoints must come from public spec contracts or visibility: internal manifest endpoint lists. Do not create endpoint names dynamically at runtime.

Control Methods

Purpose Python C++
Register control endpoint register_control_endpoint(topic, handler, request_type=..., response_type=..., timing_support=...) register_control_endpoint(topic, handler, ControlEndpointOptions)
List endpoint names get_control_info() get_control_info()
List endpoint metadata get_control_contract_info() get_control_contract_info()
Bind control server bind_control(port=None, host="0.0.0.0") bind_control(port, host)
Start control server start_control_server(handler=None) start_control_server(...)
Stop control server stop_control_server() stop_control_server()
Resolve target resolve_control_target(target) resolve_control_target(target, host, port)
Check target route (not provider-ready preparation) handshake_control(target, timeout_ms=1000) handshake_control(target, timeout_ms)
Send without waiting send_control(...) send_control(...)
Receive response recv_control(target, type, timeout_ms=0) recv_control(target, timeout_ms)
Request/response exchange transact_control(...) transact_control(...)
Bound request/response exchange transact_bound_control(binding=..., ...) resolve binding then transact_control(...)
Prepare provider-ready bounded topic lane prepare_control(target, topic, request_capacity, response_capacity, timeout_ms=1000, result_ready=False) prepare_control(target, topic, request_capacity, response_capacity, timeout_ms=1000, result_ready=false)
Configure actual provider data-worker profile configure_control_worker(policy, priority, cpus); policy OTHER / FIFO / RR configure_control_worker(policy, priority, cpus); numeric OS policy
Read cold worker profile diagnostics control_worker_status(); tid, policy, priority, cpus, ready, error, phase control_worker_status(); tid, policy, priority, cpus, ok, error
Prepare typed Procedure lane prepare_procedure(target, procedure, request_capacity, response_capacity, timeout_ms=1000, result_ready=False, typed=True) use prepare_control with the existing typed Procedure wire topic
Prepare typed Operation-start lane prepare_operation(target, operation, request_capacity, response_capacity, timeout_ms=1000, result_ready=False, typed=True) use prepare_control with the existing typed Operation wire topic

In user-facing terms, the topic argument is the Control endpoint key.

Preparation requires production Control v5 and a live provider/binding. Capacities are encoded payload-byte limits, not page-rounded region sizes. Success means the provider has authenticated, mapped the request and locked its owned response, and the caller has mapped that response. No application callback runs. Select result_ready during preparation to precreate its reclaimer; the same topic lane is reused without fresh proof, mappings, growth or workers. Ordinary unprepared exchange is still supported. Explicit re-preparation uses a fresh idle lane and does not discard pending reclamation or retained errors. Binding/route invalidation fails explicitly. A deadline miss that expires before anything reaches Control fails only that exchange and keeps the prepared lane. A late exchange after publication, or a connection Control closed or reset (end of stream before a frame, or a send that fails with EPIPE or a receive that fails with ECONNRESET), fails that exchange without replay and re-prepares the same lane within its preparation timeout; route, codec, binding, protocol and capacity failures, and a transaction failure that Control reports in its reply (it then closes the connection), are not re-prepared automatically. Worker status is profile readiness, not provider readiness. Production caller deadlines include route/codec work and propagate remaining time; successful decoding after the absolute deadline is a deadline failure. Preparation/production route resolution makes one request under the caller's absolute remaining budget, including proof and response parsing; no independent one-second wait or retry sleep is added. Standalone resolution without a strict invocation deadline retains its existing behavior.

Python BoundCapability exposes prepare_control, prepare_procedure and prepare_operation with the bound target omitted. Procedure/Operation preparation uses existing runtime transport topics, not a Control-role bypass for arbitrary endpoints. Set typed=False to prepare the existing untyped Procedure call or Operation start envelope lane. Nonreserved Control endpoints remain restricted to Gateway/Component callers.

Python resolve_runtime_binding(requirement_name) returns a BoundInterface only for a currently bound runtime requirement; missing or still-pending bindings return None without using launch or deployment manifest targets. A runtime transport, protocol, or binding error raises RuntimeError.

Procedure Methods

Purpose Python C++
Register Procedure register_procedure(name, handler, request_type=..., response_type=..., timing_support=...) register_procedure(name, handler, ProcedureOptions)
Register typed Procedure register_typed_procedure(name, handler, request_type=..., response_type=..., timing_support=...) register_typed_procedure(name, handler, request_type, response_type, timing_support)
Start Procedure server start_procedure_server() start_procedure_server()
Stop Procedure server stop_procedure_server() stop_procedure_server()
List Procedure names get_procedure_info() get_procedure_info()
List Procedure metadata get_procedure_contract_info() get_procedure_contract_info()
Call Procedure call_procedure(target=..., procedure=..., ...) call_procedure(target, procedure, params_json, ...)
Call bound Procedure call_bound_procedure(binding=..., procedure=..., ...) resolve binding then call_procedure(...)
Call typed Procedure call_typed_procedure(target=..., procedure=..., request_type=..., response_type=..., params=...) call_typed_procedure(target, procedure, request_type, response_type, params_json, timeout_ms)

Operation Methods

Purpose Python C++
Register Operation register_operation(name, handler, request_type=..., feedback_type=..., result_type=..., state_type=..., timing_support=...) register_operation(name, handler, OperationOptions)
Start Operation server start_operation_server() start_operation_server()
Stop Operation server stop_operation_server() stop_operation_server()
List Operation names get_operation_info() get_operation_info()
List Operation metadata get_operation_contract_info() get_operation_contract_info()
Start Operation start_operation(target=..., operation=..., ...) start_operation(target, operation, params_json, ...)
Start bound Operation start_bound_operation(binding=..., operation=..., ...) resolve binding then start_operation(...)
Cancel Operation cancel_operation(target=..., operation_id=...) cancel_operation(target, operation_id, timeout_ms)
Query Operation query_operation(target=..., operation_id=...) query_operation(target, operation_id, timeout_ms)
Subscribe feedback subscribe_operation_feedback(...) subscribe_operation_feedback(...)
Subscribe result subscribe_operation_result(...) subscribe_operation_result(...)
Subscribe state subscribe_operation_state(...) subscribe_operation_state(...)
Check cancellation is_operation_cancel_requested(operation_id) ActionContext::is_cancel_requested()
Publish feedback publish_operation_feedback(operation_id=..., payload=...) ActionContext::publish_feedback(...)
Publish result publish_operation_result(operation_id=..., payload=...) ActionContext::publish_result(...)
Publish state publish_operation_state(operation_id=..., state=..., payload=...) ActionContext::publish_state(...)
Complete operation complete_operation(...) ActionContext::succeed(...), fail(...), or canceled(...)

Spatial Methods

Purpose Python C++
report or replace an owned edge set_frame_transform(...) set_frame_transform(FrameTransform)
remove an owned edge clear_frame_transform(child_frame) clear_frame_transform(child_frame)
inspect locally owned edges frame_transforms() frame_transforms()
query composed runtime graph lookup_frame_transform(source, target) use Host Control frame lookup
synchronized observation time now_ns() now_ns()

Frame names passed to reporting methods are subsystem-local. Runtime lookup accepts qualified <subsystem>/<frame> names. Unqualified names work only when they are unique in the current graph.

Timing And Option Types

Concept Python shape C++ type
Timing request dict with execute_after_ns, execute_at_sync_ns, priority, late_policy, max_lateness_ns, correlation_id, request_id TimedExchangeTimingOptions
Control transaction options keyword args on transact_control(...) ControlTransactOptions
Control endpoint options keyword args on register_control_endpoint(...) ControlEndpointOptions
Procedure call options keyword args on call_procedure(...) ProcedureCallOptions
Procedure endpoint options keyword args on register_procedure(...) ProcedureOptions
Operation start options keyword args on start_operation(...) OperationStartOptions
Operation endpoint options keyword args on register_operation(...) OperationOptions

late_policy is exactly drop, reject or execute_immediately (omitted or None: execute_immediately); the timed request carries them as codes 0, 1 and 2. Any other value, including an empty string, a different case or surrounding spaces, is refused before the request is sent: Python raises ValueError and C++ throws std::invalid_argument. A received timed request whose late-policy code is not 0, 1 or 2 fails to decode and is refused before the endpoint handler runs; it is never treated as execute_immediately. The C ABI functions r2kernel_codec_encode_timed_exchange_request, r2kernel_codec_decode_timed_exchange_request and their _v2 forms return InvalidArgument (-1) for any other code. The SDKs pass the validated value to the endpoint handler (Python context.late_policy, C++ request.timing.late_policy); applying it to a late request is the handler's decision.

Endpoint timing_support values are none, optional, and required; a contract with any other value is rejected.

Resource Broker Client

Python managed packages can request brokered file descriptors through r2kernel.core.ResourceBrokerClient.

Method Purpose Typical use
open_udp_socket(...) Open an approved UDP socket on a claimed interface UDP device protocols
open_tcp_socket(...) Open an approved TCP socket on a claimed interface TCP device protocols or managed listeners
open_frontend_tcp_listener(kind="api" | "ws") Open a package frontend loopback listener grant package-owned REST or WebSocket backend
open_can_socket(...) Open an approved CAN socket CAN or CAN-FD devices
open_device(path, ...) Open an approved device file serial, USB, or udev-governed devices
inherited_fd(kind, expect) Validate and return a brokered socket inherited from the package leader through R2KERNEL_INHERITED_FDS child processes of a package leader

Resource broker access requires R2KERNEL_RESOURCE_LEASE_ID, normally set by r2kernel launch run or r2kernel deploy up.

A brokered UDP or CAN socket is opened on its claimed interface, but its holder is not confined to that interface: it can name another interface for each send (UDP IP_PKTINFO; CAN also by binding again). See Resource Control.

Runtime Notes

  • JSON produced by the C++ SDK escapes every control character (U+0000–U+001F).
  • C++ frame-transform notifications, and heartbeat status sent through a CoreClient other than the native R2KernelClient, are bounded at 100 ms in total per notification (each socket call gets only the time left); a failure is logged as a warning. With the native R2KernelClient, each heartbeat status waits up to 2 s for Control's acknowledgement; a status Control does not accept starts the re-registration described below. The Python SDK's heartbeat status waits up to 1 s for Control's acknowledgement.
  • A running subsystem stays registered across a Control restart. Every heartbeat status (Python, and C++ with the native R2KernelClient; a heartbeat follows 1 s after the previous one finished) must be acknowledged by Control. When it is not, because Control is unreachable, has restarted, or no longer holds the registration, the subsystem registers again with its unchanged session, process ID, resource lease and notify endpoint, publishes its full status (manifest, provides, requires, endpoints), and requests its Event publications, Event subscriptions and Control handler again. When Control kept a C++ publication's shared-memory slots (the same slot index, queue depth and entry slot count), the publisher continues its Event generation, so subscribers receive exactly the Events published after the re-registration. On a changed allocation the generation starts again at 0. The first attempt runs in the heartbeat that found the failure. After a failed attempt, the next one is due 1 s, 2 s, and then every 4 s later, and it runs in the first heartbeat after that time. Each failed attempt is logged at error level, and the subsystem keeps trying while it runs. Control accepts the lease only when the resource broker proves that the process is a member of it. Self-bindings are not requested again (call bind_requirement again, for example from on_start). If Control serves a different Event shared-memory pool than the one the process mapped (Control recreated it, for example with resource_service.mode: disabled), the subsystem registers again but logs that its Event publications and subscriptions cannot be restored until the process restarts.
  • A lifecycle change (fault(), degrade(), pause(), a transition that Control requests, ...) sends one status update. It never waits for a re-registration and never starts one. This holds in the Python SDK, and in the C++ SDK with the native R2KernelClient. The update is left to the next heartbeat, which carries the new state when it runs, in two cases, and an info line is logged: a heartbeat is running (it may be registering again), or the registration is being restored. If Control does not accept the update, an error line is logged and the next heartbeat registers again. In C++ the update is also left to the next heartbeat while an ordinary heartbeat is being sent. In C++, terminate() and reset() stop the heartbeat after their state change. If that lifecycle status was left to the heartbeat, it is not sent: Control keeps showing the previous lifecycle state, and reports the session as inactive after control.subsystem_timeout, until initialize() starts the heartbeat again, or until the native client reports the subsystem down when it is destroyed. The info line about the deferred status is still logged. With the native client, the single update waits up to 2 s for Control's acknowledgement.
  • R2KernelClient::keep_registration, registration_lost, reregister_subsystem and restore_event_registrations, and the exception r2kernel::core::EventPoolReplacedError, are used by the C++ subsystem wrapper's heartbeat and lifecycle status; applications do not need to call them.
  • The Python SDK removes its Control notification socket (r2kernel-notify-<pid>-<id>.sock) when the process exits normally (including an unhandled KeyboardInterrupt) and when it is terminated by SIGTERM while SIGTERM has its default action: when the process's first client is created in the main thread, the SDK installs a SIGTERM handler that removes the socket and terminates the process by SIGTERM again. A SIGTERM handler the application installed is left in place. Only the process that created a notification socket removes it. A child forked from that process (os.fork, or multiprocessing with the fork start method, including Pool workers) removes none of its parent's sockets when it exits, is terminated or closes an inherited client. Such a child also leaves its parent's Control connections open: when it exits or closes an inherited client it closes only its own copies of their descriptors, and it never waits for a lock that another thread of the parent held at the fork. The child cannot use the parent's prepared Control lanes; a call on one in the child fails, and the child needs its own client. The child also gets back the default SIGTERM action in place of the SDK's handler. Python runs that handler only in the main thread between bytecode instructions. A new Python client removes the sockets in its notification directory whose process is no longer running and that accept no connection.
  • C++: a child created with fork() must not use or destroy the parent's R2KernelClient or Subsystem (return through their scope, or exit() with them as static objects). Their destructors shut down the parent's Control connections and join threads that do not exist in the child. Call an exec function or _exit() in the child.
  • An SDK client that is killed leaves its notification socket next to the Control socket. At start, Control removes those of its own user, and the resource broker (r2kernel-resourced) removes those of every client identity it serves (the allowed users, the root launch user, Control's user and root); each removes a socket only when its process no longer exists, a connection to it is refused and it is still the same socket just before removal, and neither follows symbolic links.
  • The C++ SDK library does not load yaml-cpp dynamically; release verification rejects such a dependency.
  • EventTransport.apply_state rejects a non-empty remote_subscriptions; a peer's subscriptions arrive only as that peer's own requests. should_connect: false means accept-only. When an output buffer is too small, the call reports the required size. Send-only users keep calling recv_event(peer, timeout_ms=0) for every connected peer; timeout_ms=0 does not wait on the network, but in the Python EventTransport it takes the transport lock, so it can wait behind a concurrent flush_n (at most about twice io_timeout_ms) or a concurrent blocking recv_event (up to that call's wait). One such call applies at most 64 setup frames and returns None (C: NotFound, -2) when no Event is available; the next call continues.
  • EventTransport.accept_peer(timeout_ms=None|0|n) and apply_state wait on the network without the transport lock; recv_event(timeout_ms=0), enqueue_event and flush_n in other threads never wait behind a silent client or a slow connect.
  • A signal handled while recv_event, recv_event(timeout_ms>0), accept_peer or apply_state waits on the network (for example a Python signal.signal handler, SIGCHLD or SIGALRM) does not fail the call or close the session: the wait continues with the time left. In Python this applies to Ctrl+C as well: a KeyboardInterrupt during such a wait is raised only after the wait returns, at the latest when its timeout (io_timeout_ms for timeout_ms=None) has passed. Earlier releases ended such a wait at once with an error and closed the session.
  • A null or too-small buffer on recv_event, accept_peer, apply_state or a local Event receive never consumes the item: BufferTooSmall reports every required length, and kept items are kept per peer. When an apply_state report does not fit, the apply is repeated once; the returned report describes the repeated apply and also lists, ahead of its own, the connected/disconnected transitions of the apply whose report did not fit.
  • When two hosts dial each other at once, accept_peer returns None for the losing connection (C: R2KERNEL_EVENT_TRANSPORT_ALREADY_CONNECTED).
  • A local Event whose topic, publisher or payload exceeds the receive buffer is received again once with the lengths Core reports; with a Core that does not report them, the drop is logged as an explicit error.
  • The C++ SDK keeps the larger payload buffer for later Events; an oversized topic or publisher name is received again each time into a reused buffer.
  • An Event inline endpoint longer than the Unix socket path limit disables inline delivery at registration with an explicit error.
  • If the C++ SDK cannot verify the Control peer of the Event inline connection during publish(), it disables inline delivery for that registration and logs once Event inline delivery disabled for endpoint <endpoint> until the subsystem registers again: publish on topic <topic> failed: <reason>; Events are delivered through the lock notification instead. That publish() and every later one still succeed through the lock notification and do not retry the peer verification.
  • Python SDK: a native Event subscription that cannot be established (refused, no notification lock, or a native error) is logged at error level and the client stays in its native mode. If the native Event client cannot be created at registration, the SDK logs an error and the client publishes and receives Events in its Python mode from then on; this fallback is kept in RC9, so check the log for Native Event client creation failed. After stop_event_transport() (or the client's stop()), starting the Event transport again raises RuntimeError; create a new subsystem client to receive Events again.

The versioned public SDK packages, generated API reference, and installed C++ headers are the source of truth for exact signatures. This page records the supported surface and its intended use.