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
CoreClientother than the nativeR2KernelClient, 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 nativeR2KernelClient, 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 (callbind_requirementagain, for example fromon_start). If Control serves a different Event shared-memory pool than the one the process mapped (Control recreated it, for example withresource_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 nativeR2KernelClient. 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()andreset()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 aftercontrol.subsystem_timeout, untilinitialize()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_subsystemandrestore_event_registrations, and the exceptionr2kernel::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 unhandledKeyboardInterrupt) and when it is terminated bySIGTERMwhileSIGTERMhas its default action: when the process's first client is created in the main thread, the SDK installs aSIGTERMhandler that removes the socket and terminates the process bySIGTERMagain. ASIGTERMhandler 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, ormultiprocessingwith theforkstart method, includingPoolworkers) 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 defaultSIGTERMaction 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'sR2KernelClientorSubsystem(return through their scope, orexit()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 anexecfunction 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_staterejects a non-emptyremote_subscriptions; a peer's subscriptions arrive only as that peer's own requests.should_connect: falsemeans accept-only. When an output buffer is too small, the call reports the required size. Send-only users keep callingrecv_event(peer, timeout_ms=0)for every connected peer;timeout_ms=0does not wait on the network, but in the PythonEventTransportit takes the transport lock, so it can wait behind a concurrentflush_n(at most about twiceio_timeout_ms) or a concurrent blockingrecv_event(up to that call's wait). One such call applies at most 64 setup frames and returnsNone(C: NotFound, -2) when no Event is available; the next call continues.EventTransport.accept_peer(timeout_ms=None|0|n)andapply_statewait on the network without the transport lock;recv_event(timeout_ms=0),enqueue_eventandflush_nin other threads never wait behind a silent client or a slow connect.- A signal handled while
recv_event,recv_event(timeout_ms>0),accept_peerorapply_statewaits on the network (for example a Pythonsignal.signalhandler, 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: aKeyboardInterruptduring such a wait is raised only after the wait returns, at the latest when its timeout (io_timeout_msfortimeout_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_stateor a local Event receive never consumes the item:BufferTooSmallreports every required length, and kept items are kept per peer. When anapply_statereport 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_peerreturnsNonefor 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 onceEvent 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. Thatpublish()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. Afterstop_event_transport()(or the client'sstop()), starting the Event transport again raisesRuntimeError; 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.