spoke-connect (Python)
PyPI package spoke-connect: SPOKE Connect session-core bindings (generated uniffi Python + native spoke_connect / libspoke_connect).
Packaging contract: connect-binding-channels.md §3.3.
Install
At spoke lockstep tag vX.Y.Z:
pip install spoke-connect==X.Y.Z
Platform wheels (py3-none-<platform>) ship for manylinux_2_35_x86_64, macosx_11_0_arm64, and win_amd64. Each wheel bundles that platform's shared library beside the spoke_connect module (uniffi ctypes loader contract). Version locksteps with the spoke monorepo SemVer / git tag vX.Y.Z.
Release publishes via Trusted Publishing on the top-level release.yml workflow (publish-pypi job, OIDC). The PyPI Pending publisher for spoke-connect binds repository 42ch-dev/spoke to that workflow filename; the first successful stable-tag publish activates the project on PyPI.
Usage
import spoke_connect
peer_id = spoke_connect.derive_peer_id_from_ed25519_pubkey(pubkey)
protocol = spoke_connect.protocol_version() # 1
Transport / WebSocket stays in the host product.
Build features
| Artifact | Cargo features | Notes |
|---|---|---|
Committed spoke_connect/ |
ffi,remote-adapter |
Production surface: RemoteAdapterFFI, MultiPeerRouterFFI, Transport, loopback_transport_pair, the tool faces (invoke_tool, register_tool_handler, ToolHandler, connect_responder_ffi / ConnectResponderFFI), the optional-port dialer ops (project / compute / list_fork_timeline_events on RemoteAdapterFFI), and the responder ports face (optional ports: on connect_responder_ffi + the PortsHandler callback) — no start_loopback_smoke_host |
| Local loopback smoke cdylib + bindings | ffi,remote-adapter,ffi-smoke-host |
Adds loopback smoke host FFI for the RemoteAdapter section |
ffi-smoke-host is non-default and is not implied by remote-adapter or ffi.
Full loopback smoke procedure: Smoke/README.md.
RemoteAdapter FFI surface
With remote-adapter enabled, the binding ships the additive remote-adapter surface: RemoteAdapterFFI (single peer), MultiPeerRouterFFI (multi-peer routing), the callback Transport interface, the in-memory loopback helpers, and the tool faces — invoke_tool on the adapter, router, and responder, register_tool_handler on the adapter and responder, the ToolHandler callback, and connect_responder_ffi / ConnectResponderFFI for the accept side. The optional-port dialer ops (project / compute / list_fork_timeline_events on RemoteAdapterFFI) and the responder ports face (PortsHandler + the optional ports: constructor parameter) ride the same session invoke path.
Transport contract
The callback Transport is a message-oriented interface: one envelope per call, blocking receive, idempotent close.
| Method | Behavior |
|---|---|
send(envelope) |
Accepts exactly one connect envelope's bytes per call |
recv() |
Blocks until the next inbound envelope arrives or the connection closes; returns exactly one envelope per call |
close() |
Releases transport resources; idempotent — closing either end of a connection fails the peer's pending recv like a real connection drop |
The surface bounds messages at one envelope per call; byte-stream carriers apply length-prefix (or equivalent) delimiting before handing envelopes to the adapter.
MultiPeerRouterFFI
new_multi_peer_router_ffi() returns the router as a synchronous object over the same runtime: a peer registry (register_peer(adapter) accepts an established RemoteAdapterFFI and returns its peer_id; unregister_peer(peer_id); list_peers()), the BaselinePorts six families routed per call to exactly one capable peer, and the two HostManifestPort aggregation views — the composed get_host_capability_manifest() and the per-peer list_peer_host_capability_manifests(). Selection matches each registered peer's cached HostCapabilityManifest: required capability, exact namespace, soft role preference, and a deterministic lowest-peer_id tie-break.
Tool faces
invoke_tool(capability_id, arguments_json)— invoke a tool on the peer (dialer → responder, responder → dialer, or router → capable peer); returns the tool'sresultas a JSON string. A non-tools.id or malformed arguments rejectsINVALID_INPUTwith zero wire traffic; a dispatch deny rejectsCAPABILITY_PORT_MISSINGwith the peer's preservedwire_code.register_tool_handler(capability_id, handler)— serve reverse invokes through a foreignToolHandler; last-wins per id, never mutates the manifest. The callback'shandle(arguments_json)returns the result JSON; a raisedFfiError.Rejectedpasses through verbatim as an application reject, any other outcome is contained toINTERNAL_ERRORand the session survives.connect_responder_ffi(...)/ConnectResponderFFI— the accept side: wrap a connected (host-accepted) callbackTransport. The constructor returns immediately inHandshaking— pollstate()(bounded) toEstablishedbefore invoking; a handshake failure surfaces asstate() → "Closed"(never a thrown constructor error), config-validation failures returnDial { kind: "config" }.- Handlers run on the FFI blocking pool — do not synchronously call back into the FFI faces from
handle; hand off asynchronously in the host instead.
Responder ports serving face (PortsHandler)
The optional ports: constructor parameter (between peer_keys and invoke_timeout_ms) serves every declared port.* family through a foreign PortsHandler callback: the nine baseline serve ops (get_knowledge_entry / put_knowledge_entry / get_relation / put_relation / list_knowledge_entries / list_timeline_events / put_findings / list_rules / list_peer_host_capability_manifests) plus the three optional ops (project / compute / list_fork_timeline_events) — every method returns the op's result as a JSON string. Passing nothing keeps the documented absent-ports behavior: the constructor is still valid and every port.* op answers the default deny branch (CAPABILITY_PORT_MISSING with the peer's preserved op_unsupported wire code). Optional ops are capability-gated like the baseline rows: a session whose negotiated capabilities lack l2-computable / l5-fork denies at the responder's dispatch gate with the same deny row.
Callback outcomes map strictly: FfiError.Rejected passes through verbatim as an application reject (kind re-hung onto details); a foreign exception, Dial, or panic is contained to INTERNAL_ERROR with details: None and the session survives. The ports callbacks behave like tool handlers — demand-driven, one blocking-pool thread per in-flight callback — so the same rules apply: never call back into the FFI surface from inside a ports callback (hand off asynchronously in the host instead), and size the host accordingly (one blocking-pool thread per transport end; ~256 full-duplex sessions at the tokio default cap). The dialer optional ops (project / compute / list_fork_timeline_events on RemoteAdapterFFI) reject malformed JSON locally with INVALID_INPUT and zero wire traffic.
Layout
| Path | Contents |
|---|---|
pyproject.toml |
Packable project (name = spoke-connect; lockstep SemVer) |
setup.py |
Platform wheel tag hook (py3-none-<platform>; no sdist) |
spoke_connect/ |
Import package — committed generated __init__.py + host native for smoke |
native/ |
Staging contract for non-host RIDs (see native/README.md) |
Smoke/ |
Golden-parity + RemoteAdapter loopback smoke — see Smoke/README.md |
Maintainer: regenerate → stage native → wheel → smoke
Commands from the repository root (local nightly convention for cargo: cargo +nightly …).
Production regenerate uses ffi,remote-adapter only (no ffi-smoke-host). Full loopback smoke
recipe: Smoke/README.md.
# 1. Build the production ffi cdylib (must run before generate — default build replaces the stub)
cargo +nightly build -p spoke-connect --features ffi,remote-adapter --release
# 2. Generate bindings (first-party uniffi 0.32)
cargo +nightly run -p spoke-connect --features ffi,bindgen-cli,remote-adapter --bin uniffi-bindgen -- \
generate --library target/release/libspoke_connect.dylib \
--language python --out-dir crates/spoke-connect/bindings/python/spoke_connect
mv crates/spoke-connect/bindings/python/spoke_connect/spoke_connect.py \
crates/spoke-connect/bindings/python/spoke_connect/__init__.py
# 3. Stage host native beside the module (darwin arm64 example)
cp target/release/libspoke_connect.dylib \
crates/spoke-connect/bindings/python/spoke_connect/
install_name_tool -id @rpath/libspoke_connect.dylib \
crates/spoke-connect/bindings/python/spoke_connect/libspoke_connect.dylib
# 4. Golden-parity smoke (no wheel required)
PYTHONPATH=crates/spoke-connect/bindings/python \
python3 -m unittest discover -s crates/spoke-connect/bindings/python/Smoke -v
# 5. Build platform wheel (host RID or CI artifact — recipe: tooling/connect/build-python-wheel.sh)
./tooling/connect/build-python-wheel.sh
# Release: all three wheels from build-connect-ffi artifacts
./tooling/connect/build-python-wheels-from-ffi.sh ffi-assembled
v1 publishes platform wheels only (no sdist): a source install cannot produce the native library without a Rust toolchain.
Committed natives (in-tree smoke)
| Wheel platform (PEP 425) | Native | Status |
|---|---|---|
macosx_11_0_arm64 |
spoke_connect/libspoke_connect.dylib |
Committed — maintainer-built (release) |
manylinux_2_35_x86_64 |
libspoke_connect.so |
CI — stage from release.yml build-connect-ffi (linux-x64, ubuntu-22.04) before wheel build |
win_amd64 |
spoke_connect.dll |
CI — stage from build-connect-ffi (win-x64) before wheel build |
Reference
- Wheel recipe:
tooling/connect/build-python-wheel.sh - Release wheels (all RIDs):
tooling/connect/build-python-wheels-from-ffi.sh - Pre-publish inventory gate:
tooling/connect/verify-python-wheels.sh - FFI surface:
../../src/ffi.rs - Publish strategy: connect-publish-strategy.md
Metadata
Release files for spoke-connect 0.14.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| spoke_connect-0.14.1-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details for spoke_connect-0.14.1-py3-none-win_amd64.whl |
| spoke_connect-0.14.1-py3-none-manylinux_2_35_x86_64.whl | Python 3 | none | Linux glibc 2.35+ x86-64 | Details for spoke_connect-0.14.1-py3-none-manylinux_2_35_x86_64.whl |
| spoke_connect-0.14.1-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details for spoke_connect-0.14.1-py3-none-macosx_11_0_arm64.whl |
Total release size: 11.7 MB
Release files / spoke_connect-0.14.1-py3-none-win_amd64.whl
| Download URL | spoke_connect-0.14.1-py3-none-win_amd64.whl |
|---|---|
| Size | 3.9 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
7318cb31cc48872d2155f0883d02dc4d2d4b587ba2e40d15a676b7cc2f319b15
|
|
BLAKE2b-256 checksum How to use checksums |
530d0fe6fb971aceb4d385ae2d7200cb9ea6872386b4763ab90c26481594fe20
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / spoke_connect-0.14.1-py3-none-manylinux_2_35_x86_64.whl
| Download URL | spoke_connect-0.14.1-py3-none-manylinux_2_35_x86_64.whl |
|---|---|
| Size | 2.1 MB |
| Tags | Linux glibc 2.35+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
75c3ad71b5b0eaf1e3e3165710a4963a4aa484bb1d31395afa020fcd524ecea8
|
|
BLAKE2b-256 checksum How to use checksums |
f65baebb731ec9822a716b157e5160dcb9ad4d594cda1dd847d1e021dc79ff75
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / spoke_connect-0.14.1-py3-none-macosx_11_0_arm64.whl
| Download URL | spoke_connect-0.14.1-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 5.7 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
4036e9a76a741367186334119ef79e1d0ca9cdf6a5bdc6e8c03c3d42daf7c99d
|
|
BLAKE2b-256 checksum How to use checksums |
16b25079714f3403dc25b14b2787737d3caf7c27c75372092539ad2f1a50d0b6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logSecurity for spoke-connect 0.14.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 3 files in this release have been uploaded with provenance metadata.
Attestation for spoke_connect-0.14.1-py3-none-win_amd64.whl
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.