Shape-matching in `wrap_address()` doesn't survive 4 backends and
the plans were papering over it: TIPC's natural unwrapped form is
a `(str, int)`, indistinguishable from `TCPAddress`, and iroh's
is a `(str, str)`, which the *existing* UDS case
(`case (_, filename) if type(filename) is str`) already swallows.
So the contract doc (§1.1) now carries the conclusion as a
**recommended prerequisite for all three backends**: make the
unwrapped form carry an explicit proto-key spelled with the
`multiaddr` protocol name — `('tcp', host, port)`,
`('unix', path)`, `('tipc', stype, inst, scope)`. `wrap_address()`
then collapses from an order-sensitive `match` to
`_address_types[addr[0]]` and the whole collision class stops
existing, while the on-wire form finally agrees w/
`mk_maddr()`/`parse_maddr()` instead of being an independent
invention.
Two consequences spelled out: it's a wire-format change
(`SpawnSpec`, `_root_mailbox`, `_registry_addrs`) + every fixture
+ downstream config, so it wants its own migration commit landed
*before* any new backend; and it's the moment to stop handing raw
tuples to users at all — `Address` becomes the public currency
and `UnwrappedAddress` an internal serialization detail, the same
discipline `ipaddress` uses (you pass `IPv4Address`, never a
4-tuple).
Plan 01 §2.2 is rewritten to match and to explicitly **retract**
its own earlier `('tipc:<stype>:<scope>', instance)` self-tagging
prefix hack — it keeps `wrap_address()` order-sensitive and does
nothing for the iroh/UDS collision, so the doc says don't
resurrect it. Registration checklist item 4 likewise becomes "do
the migration first, then this is a one-line `_address_types`
entry".
Also seeds a `/tipc` multiaddr-spec submission as a follow-up,
mirroring the `wg` track (multiformats/py-multiaddr#107/#108 + gh
(this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
|
||
|---|---|---|
| .. | ||
| 00_shared_backend_contract.md | ||
| 01_tipc_backend.md | ||
| 02_quic_iroh_backend.md | ||
| 03_wg_tunnel_bindspace.md | ||
| README.md | ||
README.md
next-gen tractor.ipc transport backend plans
Implementation specs for three prospective .ipc transport backends, written so each can be worked independently (by a different model/provider) without design or lib-selection drift.
Read 00_shared_backend_contract.md first — it is the normative description of what a tractor transport backend is as of main@83b34884 (the backend duck-type, the 10-item registration checklist, the test-harness plumbing, the code-style rules). The three plans assume it and document only their own deltas.
| plan | issue | dep | size | lands |
|---|---|---|---|---|
| 01 — TIPC | #378 | none (stdlib) | small | first |
02 — QUIC/iroh |
#353 | iroh (uniffi FFI) |
large | needs a prep PR |
03 — wg bindspace |
#482, #443 | pyroute2 |
medium, 3 layers | layer A now |
Headline conclusions:
- TIPC is the cheap win. Verified:
trio.SocketStreamandtrio.SocketListenerare address-family agnostic (onlySOCK_STREAM+ a trio socket), and CPython shipsAF_TIPC+ 23TIPC_*constants. So the backend is ~one module of contract boilerplate, zero new deps, and it buys kernel-native service discovery:bind()publishes,connect()-by-name resolves — no registrar in the loop. (modprobe tipcis required; hard-gate everything.) - QUIC’s cost is entirely in two adapters, not in QUIC. The
irohpython bindings areuniffi-generated asyncio, but the asyncio dependency is confined to one future-poll callback — a ~40-linetriobridge (TrioToken.run_sync_soon) replaces it. The second cost is that an iroh listener isn’t a socket, which needs a small, independently-reviewable prep PR to_server.py/_types.py. - WireGuard is not a transport. It’s an iface-layer tunnel, so it belongs as a nested bindspace (
TunnelledAddress+open_bindspace()@acms) wrapping whatever L4 tpt is in use — which is also what finally implements the long-spec’dAddress.namespace, and what generalizes toveth/vxlan/gre.
Ordering rationale: plan 01 first as the cheap proof the table-registration story generalizes to a genuinely new proto; plan 03 layer A is already deployable-today doc/example work; plan 02 last (and gated on its prep PR). Plans 01 and 02 both want the same Address.rebind_from_sockname gate — whichever lands first ships it.