Skip to content

Python — Client API

The client page starts with the host-facing workflow: create a connection, open or use a session, submit work, read results, then close. Data structures are linked from the parameter tables instead of repeated as interface-style code blocks.

Imports

Use the high-level client/session APIs for applications. Low-level packet builders remain public for tests, diagnostics, and custom transports.

python
from nnrp import NativeTransportBinding, NativeTransportClientSecurity, TransportPolicy
from nnrp.client import (
    ClientProfile,
    ClientSession,
    NativeClientOptions,
    NativeClientProviderRoute,
    NativeClientSessionOptions,
    NativeSessionRecoveryTicket,
    SubmitRequest,
    connect_client_control,
    connect_client_control_with_probe,
    connect_native_client_connection,
)

Client Workflow

Production host code uses the Rust-backed native runtime:

  1. Provide an nnrp:// or nnrps:// application endpoint.
  2. Call connect_native_client_connection; the SDK selects an installed provider and opens its carrier.
  3. Open a session with NativeClientConnection.open_session.
  4. Use coarse native methods for submit, polling, and runtime-control frames.
  5. Call close() to release the connection and sessions.

Packet transport helpers remain public, but they are mainly for smoke tests, diagnostics, and custom transports:

  1. Build a ClientProfile.
  2. Choose TCP, QUIC, or probe-based transport bootstrap.
  3. Call connect_client_control or connect_client_control_with_probe.
  4. Use the returned bootstrap session's ClientSession for request/response flows, or send_submit plus receive_result when multiple frames are in flight.
  5. Keep the async context manager open for the lifetime of the client control session.

connect_native_client_connection

Selects an installed Preview4 provider for an application endpoint, opens the provider carrier, transfers that carrier to the Rust role runtime, completes the NNRP handshake, and returns a NativeClientConnection context manager. A provider-local locator never replaces the application endpoint in normal host configuration.

ParameterTypeRequiredDescription
optionsNativeClientOptionsYesApplication endpoint, provider routes, transport policy, and session defaults.
python
async def run() -> None:
    options = NativeClientOptions(
        endpoint="nnrps://runtime.example/session/default",
        provider_routes={
            "tcp": NativeClientProviderRoute(
                security=NativeTransportClientSecurity(
                    server_name="runtime.example",
                    trusted_certificate_der=trusted_certificate_der,
                )
            )
        },
        transport_policy=TransportPolicy.FORCE_TCP,
    )
    with connect_native_client_connection(options) as connection:
        print(connection.active_transport_name)
        await connection.open_session(NativeClientSessionOptions(requested_session_id=42))

NativeClientConnection.transport_selection retains the complete immutable NativeTransportSelection, including the selected provider and every accepted or rejected candidate. active_transport_name is the canonical name of the selected provider transport. Installed provider packages are discovered by the SDK; each package owns probing, carrier creation, and role adoption for its provider rather than acting as a configuration-only feature switch.

TCP and QUIC resolve the application authority and default to port 4433 when the authority omits a port. IPC requires a matching unix:// or npipe:// route locator; WebSocket requires a matching ws:// or wss:// route locator. The SDK rejects a provider-local locator that does not belong to its route transport. Carrier ownership moves into Rust only after successful role adoption; failure leaves the carrier wrapper closable by Python.

NativeClientConnection

NativeClientConnection is the primary preview4 Python host API. It preserves coarse native calls through session, operation, event, and owned-buffer objects instead of crossing the ABI for every small field.

NativeClientConnection.open_session

NativeClientSessionOptions has these frozen defaults:

FieldDefaultDescription
requested_session_id0Preferred wire session id; zero lets the server assign it.
profile_id2 (STANDARD_PROFILE_TOKEN)Standard token profile.
schema_id0x00001001 (TOKEN_DELTA_SCHEMA_ID)Token-delta schema.
schema_version3 (TOKEN_DELTA_SCHEMA_VERSION)Token-delta schema version.
priority_classbalancedSession scheduling class.
default_deadline_ms500Default operation deadline.
max_in_flight_operations4Requested concurrency ceiling.
lease_ttl_hint_ms30000Requested cache lease lifetime.
allow_resumeFalseEnable resumable-session negotiation.
resume_token_bytes0Local recovery-token capacity; zero selects the runtime default.
cache_hints()Cache object kinds folded into automatic CLIENT_HELLO.

The no-argument open_session() path uses exactly these values, matching the Rust runtime default. Applications override them only when selecting another installed profile/schema pair.

ParameterTypeRequiredDescription
optionsNativeClientSessionOptions | NoneNoSession negotiation options; defaults to NativeClientOptions.session_defaults.
Returns
NativeRuntimeSession

The method is asynchronous because session negotiation may wait for provider and peer I/O. One connection remains open and may own many concurrently opened sessions.

NativeClientConnection.resume_session

await connection.resume_session(ticket, options=None) resumes a session with one runtime-issued NativeSessionRecoveryTicket. Applications may persist a ticket with ticket.to_bytes() and restore it with NativeSessionRecoveryTicket.from_bytes(encoded), but cannot construct or alter its opaque resume token. session.recovery_ticket() returns the current ticket when resumability was negotiated.

NativeClientConnection.submit_and_poll_result

ParameterTypeRequiredDescription
sessionNativeRuntimeSessionYesOpen native session.
requestSubmitRequestYesTyped tensor, token, or typed-payload submit request.
parent_operation_idint | NoneNoParent operation.
operation_group_idint | NoneNoOperation group.
max_eventsint | NoneNoMaximum events processed by this poll.
timeout_msintNoMaximum native event-poll wait; 0 performs a non-blocking poll.

Build SubmitRequest with SubmitRequest.tensor(...), SubmitRequest.token(...), or SubmitRequest.typed_payload(...). The SDK validates and packs the typed request, then crosses the FFI once for submit and once for the bounded result poll.

Returns
NativeRuntimeResult

NativeRuntimeResult preserves every terminal outcome:

FieldTypeDescription
operation_idintNon-zero submitted operation identity.
terminal_stateResultTerminalStateSUCCESS, CANCELLED, DROPPED, or ERROR.
eventNativeTerminalEventClosed NativeRuntimeEvent | OperationLifecycleEvent terminal-evidence union.

Successful results preserve RESULT_PUSH; non-success results preserve the exact wire or local lifecycle event that established their state. The union never uses nullable parallel fields and the application-facing result does not expose serialized FFI payloads.

OperationLifecycleEvent

FieldTypeDescription
operation_idintNon-zero operation identity.
stateOperationStateExact local lifecycle state.

This is a local role notification. It never fabricates a RuntimeFrameHeader; a native event whose header is absent is projected here instead of becoming a wire NativeRuntimeEvent.

Runtime control helpers

MethodMessage type
cancel_runtime_operationCANCEL
abort_runtime_operationABORT
update_runtime_priorityPRIORITY_UPDATE
update_runtime_deadlineDEADLINE
expire_runtime_operation_atEXPIRE_AT
supersede_runtime_operationSUPERSEDE
update_runtime_budgetBUDGET_UPDATE
send_runtime_route_hintROUTE_HINT
send_runtime_execution_hintEXECUTION_HINT
negotiate_runtime_capabilitiesCAPABILITY_NEGOTIATION
degrade_runtime_profileDEGRADE_PROFILE

NativeRuntimeSession Preview4 Frames

The session returned by NativeClientConnection.open_session() owns the high-level Preview4 send surface. Applications use these methods instead of constructing frames with the codec functions:

MethodMessage
cancel_operation(metadata, diagnostic=b""), abort_operation(...)CANCEL, ABORT
update_priority(metadata), update_deadline(metadata), expire_at(metadata)scheduling messages
supersede(metadata, diagnostic=b""), update_budget(metadata)SUPERSEDE, BUDGET_UPDATE
negotiate_capabilities(metadata, body=b""), degrade_profile(...)capability messages
send_route_hint(metadata, body=b""), send_execution_hint(...)routing messages
send_trace_context(metadata, body=b"", *, operation_id=None)TRACE_CONTEXT; None is session scope, otherwise resolves the active operation frame
declare_object(metadata, body=b""), reference_object(...)OBJECT_DECLARE, OBJECT_REF
release_object(metadata, diagnostic=b"")OBJECT_RELEASE
patch_object(metadata, delta, metadata_body=b"")OBJECT_PATCH
send_object_delta(metadata, delta, metadata_body=b"")OBJECT_DELTA
reference_cache(metadata, body=b""), report_cache_miss(...)cache reference/miss
invalidate_cache(metadata)CACHE_INVALIDATE

Every method returns None, validates declared lengths, and performs one coarse call to the Rust-owned runtime. The underlying role-neutral frame-send primitive is internal to the SDK and is not exposed on NativeRuntimeSession.

Session-scoped event pump

Rust role events are owned by one session. The Python SDK therefore exposes receive and dispatch methods on NativeRuntimeSession, never as a connection-wide queue:

MethodResult
next_event(timeout=None)NativeClientEvent, the closed `NativeRuntimeEvent
poll_event() / poll_events(max_events=None, event_kind=None)Raw owned NativeRuntimeEvent snapshots.
poll_credit_updates(max_events=None)Decoded credit and backpressure updates.
poll_result_hints(max_events=None)Decoded result hints.
poll_payload_family_events(...)Decoded payload-family events.
poll_runtime_frames(max_events=None)Decoded Preview4 runtime frames.
dispatch_events(...) and typed dispatch_* variantsSynchronous callback dispatch for the same session.
async_poll_event() and typed iter_* variantsAsync wrappers over the same session event source.

NativeClientEvent is the public type alias for the two closed variants. Callers discriminate the value by type; a headerless lifecycle notification never becomes a fabricated runtime frame. The event pump uses the session handle in one bounded native poll. It copies and releases native-owned buffers before returning. Applications with several sessions poll each session explicitly; events are never reassigned by a connection-level router.

connect_client_control

Opens the selected transport, completes the control handshake, and yields a ClientControlBootstrapSession.

ParameterTypeRequiredValues / RangeDescription
hoststrYesHostname or IPRemote NNRP endpoint.
quic_portint | NoneNoQUIC portEnables QUIC when provided.
tcp_portint | NoneNoTCP portEnables TCP when provided.
quic_configurationQuicConfiguration | NoneNoaioquic configQUIC client configuration.
tcp_configurationNnrpTcpClientConfiguration | NoneNoTCP configTCP client configuration.
client_profileClientProfileNoDefaults to SDK profileClient capabilities and cache limits sent during handshake.
selected_transport_idTransportIdNoUNSPECIFIED, QUIC, TCPPreferred selected transport when no probe result is supplied.
forced_transport_idTransportIdNoUNSPECIFIED, QUIC, TCPHard transport selection for controlled deployments.
auth_blockbytesNoDefaults to b""Application-defined authentication payload.
timeoutfloatNoSeconds, default 10.0Connect and handshake timeout.
ReturnsRaises
AsyncIterator[ClientControlBootstrapSession]Transport errors, malformed handshake errors, or capability rejection errors.
python
profile = ClientProfile(max_views=1, enable_cache=True)
async with connect_client_control(
    "render.example.com",
    quic_port=4433,
    client_profile=profile,
) as bootstrap:
    result = await bootstrap.session.submit(request)

connect_client_control_with_probe

Probes QUIC and TCP, selects a transport, and uses the selected transport in the handshake.

ParameterTypeRequiredValues / RangeDescription
hoststrYesHostname or IPRemote NNRP endpoint.
quic_portintYesQUIC portQUIC probe/connect port.
tcp_portintYesTCP portTCP probe/connect port.
quic_configurationQuicConfiguration | NoneNoaioquic configQUIC client configuration.
tcp_configurationNnrpTcpClientConfiguration | NoneNoTCP configTCP client configuration.
client_profileClientProfileNoDefaults to SDK profileCapability and cache preferences.
probe_payload_bytesintNoDefault 32768Payload size used by each probe sample.
probe_sample_countintNoDefault 3Number of scored probe samples.
include_warmup_probeboolNoDefault FalseAdds a warmup sample before scoring.
auth_blockbytesNoDefaults to b""Application auth payload.
timeoutfloatNoSeconds, default 10.0Probe, connect, and handshake timeout.
ReturnsRaises
AsyncIterator[ClientControlBootstrapSession]Probe, transport, or handshake failures.
python
async with connect_client_control_with_probe(
    "render.example.com",
    quic_port=4433,
    tcp_port=4434,
    client_profile=ClientProfile(),
) as bootstrap:
    result = await bootstrap.session.submit(request)

ClientSession

An established client-side session. Prefer these methods over building FRAME_SUBMIT packets by hand.

ClientSession.submit

Submits one frame and waits for the matching result.

ParameterTypeRequiredValues / RangeDescription
requestSubmitRequestYesframe_id must be unique while in flightStructured frame submit request.
timeoutfloat | NoneNoSeconds; None disables timeoutMaximum wait for the result.
ReturnsRaises
Resultasyncio.TimeoutError, transport errors, protocol correlation errors.
python
result = await session.submit(
    SubmitRequest(
        frame_id=1,
        sections=(tensor_section,),
        input_profile=InputProfile.CHANGED_TILES_LUMA,
        submit_mode=SubmitMode.INLINE,
    ),
    timeout=0.05,
)

ClientSession.send_submit

Sends a frame without waiting for its result. Use it with receive_result or ResultRouter.

ParameterTypeRequiredValues / RangeDescription
requestSubmitRequestYesframe_id must be unique while in flightRequest to serialize and send as FRAME_SUBMIT.
ReturnsRaises
intTransport errors or local serialization errors.
python
await session.send_submit(request)

ClientSession.receive_result

Receives the next server-pushed result on the result channel.

ParameterTypeRequiredValues / RangeDescription
timeoutfloat | NoneNoSeconds; None disables timeoutMaximum wait for the next result.
ReturnsRaises
Resultasyncio.TimeoutError, malformed result, or correlation errors.
python
result = await session.receive_result(timeout=0.05)

ClientSession.patch_session

Updates negotiated runtime parameters without reopening the session.

ParameterTypeRequiredValues / RangeDescription
patch_fieldsSessionPatchFieldYesBitmaskSelects which fields the server should apply.
target_cadenceintNo0 leaves unchangedTarget FPS or cadence value.
quality_tierintNo0..255Application-defined quality tier.
active_lane_maskintNoBitmaskActive lane/view mask.
preferred_codecintNoCodec idPreferred codec.
preferred_compressionintNoCompression idPreferred compression mode.
ReturnsRaises
SessionPatchAckMetadataRejection, timeout, or malformed ack errors.
python
ack = await session.patch_session(
    SessionPatchField.TARGET_CADENCE | SessionPatchField.QUALITY_TIER,
    target_cadence=60,
    quality_tier=2,
)

ClientSession.close

Closes the session and underlying connection gracefully.

ParameterTypeRequiredValues / RangeDescription
None---No parameters.
ReturnsRaises
NoneTransport close errors.
python
try:
    await session.submit(request)
finally:
    await session.close()

Core Types

ClientProfile

Client capabilities and cache preferences used during handshake.

FieldTypeDefaultDescription
max_viewsint1Maximum concurrent views.
enable_cacheboolTrueWhether to negotiate server-side cache support.
max_cache_entriesint256Maximum cache entries requested from the server.
max_cache_bytesint8388608Maximum cache bytes requested from the server.

ClientDialPolicy

Transport policy included in the client handshake.

FieldTypeDescription
selected_transport_idTransportIdTransport selected by probing or policy.
forced_transport_idTransportIdForced transport; UNSPECIFIED means no force.

SubmitRequest

Frame submission request.

FieldTypeRequiredDescription
operation_idintYesNon-zero u64 lifecycle id, independent from frame_id.
frame_idintYesUnique frame id while in flight.
tile_idstuple[int, ...]NoTile ids included in the request.
sectionstuple[TensorSectionData, ...]NoTensor payload sections. See packet types.
typed_payloadstuple[TypedPayload, ...]NoNon-tensor payload frames.
input_profileInputProfileYesInput data profile.
submit_modeSubmitModeYesInline or reference mode.
budget_policyBudgetPolicyNoAllowed degradation behavior.
inference_budget_msintNoRelative inference budget in milliseconds; 0 means unlimited.
deadline_msintNoAbsolute Unix timestamp in milliseconds.

TypedPayload

Non-tensor payload container.

FieldTypeRequiredDescription
payload_kindPayloadKindYesPayload family.
databytesYesRaw payload bytes.

Result

Decoded packet helper used by the pure packet/router API. It is not the Preview4 native role result projection; role sessions return NativeRuntimeResult above.

FieldTypeDescription
packetNnrpPacketRaw result packet.
metadataResultPushMetadataParsed result metadata.
sectionstuple[TensorSectionData, ...]Tensor result sections.
typed_payloadstuple[TypedPayload, ...]Non-tensor result payloads.

ResultRouter

Use ResultRouter when multiple frame ids are in flight and consumers need per-frame awaiters.

MethodParameterReturnsDescription
send_submitSubmitRequestintSends a request through the wrapped session.
receiveframe_id, optional view_id, optional timeoutResultWaits for a specific frame result.
closeNoneNoneStops the router task.

Common Pitfalls

WARNING

  1. Always close ClientSession; unclosed sessions keep server-side resources alive until timeout.
  2. Do not send from multiple coroutines through the same session without an application-level queue.
  3. deadline_ms is absolute Unix time in milliseconds. inference_budget_ms is relative.
  4. FORCE_QUIC hard-fails in TCP-only networks; prefer probing or fallback policies in production.

NNRP Documentation