Add impl plans for TIPC/QUIC/wg tpt backends - #492
Conversation
There was a problem hiding this comment.
Pull request overview
Adds planning documentation for “next-gen” tractor.ipc transport backends (TIPC, QUIC/iroh, WireGuard-as-bindspace), plus a two-host WireGuard multiaddr example under examples/multihost/.
Changes:
- Add a set of detailed backend plan/spec markdown docs under
ai/tpt-backends/. - Add WireGuard tunnelled-multiaddr parsing + verification helper and two multihost example scripts.
- Add prompt I/O artifacts capturing the doc-generation session output.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| examples/multihost/wg_lan/wg_maddr.py | Example-local WG tunnelled-maddr struct + parse/render helpers + optional peer verification |
| examples/multihost/wg_lan/README.md | Setup and usage guide for the multihost WireGuard example and the maddr grammar |
| examples/multihost/wg_lan/host_a_srv.py | Host A server-side example binding tractor on the WG overlay endpoint |
| examples/multihost/wg_lan/host_b_client.py | Host B client-side example dialing Host A via the WG overlay endpoint |
| ai/tpt-backends/README.md | Index/overview of the backend plan docs and their sequencing |
| ai/tpt-backends/00_shared_backend_contract.md | Shared “backend contract” doc: conventions, registration checklist, dependency policy, test harness notes |
| ai/tpt-backends/01_tipc_backend.md | Plan/spec for a TIPC backend using kernel discovery semantics |
| ai/tpt-backends/02_quic_iroh_backend.md | Plan/spec for QUIC via iroh, including an approach to a trio-native uniffi bridge |
| ai/tpt-backends/03_wg_tunnel_bindspace.md | Plan/spec for WG as a nested bindspace and tunnelled multiaddr grammar |
| ai/prompt-io/claude/20260813T001102Z_27c34aeb_prompt_io.raw.md | Raw captured output for the doc-generation session |
| ai/prompt-io/claude/20260813T001102Z_27c34aeb_prompt_io.md | Curated summary of the doc-generation session output |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
TIPC/QUIC/wg tpt backends
Record #493's current draft head, #492's advanced planning tip and the exact restack sequence before final landing. Also, - keep the unrelated `pformat` red-test/fix pair ordered for its standalone `main` PR - distinguish the 17 substantive arc commits from the local-cache ignore - make the in-repo handoff authoritative over agent memory - preserve digest/drift checks for already-authorized forge writes (this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
85a4458 to
4730b4c
Compare
First doc of a new `ai/tpt-backends/` set: the normative description of what a `tractor` tpt backend *is* as of `main`, written so the 3 sibling plans (TIPC, QUIC, `wg`) can be worked independently (by another model/provider) w/o design drift. Deats, - the backend duck-type as empirically derived from `_tcp.py`/`_uds.py`: the `Address` protocol surface, the mod-level `start_listener()`/`close_listener()` pair and `Msgpack<Proto>Stream(MsgpackTransport)`. - the ONE reflection you can't break: `Endpoint.start_listener()` resolves the tpt mod via `inspect.getmodule(self.addr)`, so an `Address` type and its listener fns MUST live in the same mod. - a 10-item registration checklist (`_address_types`, `_key_to_transport`, `_addr_to_transport`, `wrap_address()` match-cases, `TransportProtocolKey`, maddr tables, ..) incl. the import-time `_default_lo_addrs` trap. - where the `trio.SocketListener` assumption is *actually* load-bearing (just the `getsockname()` reconcile) vs. merely annotated. - the handshake/discovery invariants a new backend inherits, dep policy (extras + import-laziness per the #470 boot-latency budget), `--tpt-proto` harness plumbing and code style. Also, records a verified finding the plans lean on hard: `trio.SocketStream`/`SocketListener` are addr-*family* agnostic — the only ctor checks are "is a trio sock" + `SOCK_STREAM` (+ an `OSError`-suppressed `SO_ACCEPTCONN`) — so any `SOCK_STREAM` family CPython can make drops into the existing `trio.serve_listeners()` path unmodified. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
Plan doc for gh #378, the cheapest new backend we can add: it's stdlib-only (CPython ships `AF_TIPC` + 23 `TIPC_*` consts) and per the contract doc `trio`'s stream/listener wrappers don't care about the addr family, so `MsgpackTransport` framing and `trio.serve_listeners()` are reused verbatim. Deats, - `TIPCAddress` as a *service name* `(type, instance)` w/ scope as the `.bindspace`; `bind()` publishes the singleton name-range, peers `connect()` by name and the kernel resolves + load-balances. I.e. registration/lookup for free, no registrar in the loop. - the self-tagging `('tipc:<stype>:<scope>', instance)` unwrapped form + why it must be match-ordered before `TCPAddress`'s. - `get_random()` via a blake2b digest of the actor id (there's no `port=0` analogue) and the silent-crosstalk risk that follows: TIPC *allows* dup binders and round-robins, so a collision doesn't `EADDRINUSE`, it cross-talks. - an `Address.rebind_from_sockname` ClassVar to opt out of `Endpoint.start_listener()`'s `getsockname()` reconcile, which for TIPC always returns a port-id, never the bound name. - the `TIPC_TOP_SRV` topology-service subscription as an `@acm` yielding a chan of typed name-table events — push-based register/dereg, the real "end game cluster proto" bit. - commit sequencing, hard capability gating (`modprobe tipc`; bare `AF_TIPC` is `EAFNOSUPPORT` on a stock box), CI matrix notes, risks + follow-up seeds. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
Plan doc for gh #353. Picks `iroh` (the `uniffi` FFI pkg) over `aioquic`/`quiche` bc node-id addressing + hole-punching + relay fallback is the whole point; `aioquic` stays documented as the fallback since ~90% of the adapters here are reusable against a sans-io core. Deats, - the layering: iroh `Endpoint` per actor, `Connection` per peer (pooled via `trionics.maybe_open_context()`, not a hand-rolled cache), one bi-stream per `Channel`. 4-byte prefix framing stays so `MsgpackTransport` is untouched. - `_uniffi_trio.py`: uniffi only uses `asyncio` as the executor for its rust-future poll loop, so a ~40-line `TrioToken.run_sync_soon()` bridge replaces it. Spells out the real hazards — strong ref on the `ctypes` trampoline, poll-code propagation, and a *bounded* shielded cancel-drain so a wedged rust future can't make an actor un-cancellable. - `IrohAddress` w/ ALPN as the `.bindspace`, the `(str, str)` unwrapped form's collision w/ the UDS match-case, and why `get_root()` needs a persisted secret key -> a lazy `default_lo_addrs()` + a pure-getter/explicit-setter split. - `QuicMsgStream(trio.abc.HalfCloseableStream)` + `QuicListener(trio.abc.Listener)`, incl. the exact EOF/reset/use-after-close semantics `_transport.py` already match-cases on, and hanging the acceptor tasks off the existing `Endpoint.listen_tn`. - a prep-PR boundary: annotation widening, the shared `rebind_from_sockname` gate and a `tpt_key`-based `transport_from_stream()` dispatch, all landable w/ tcp/uds as the only backends. Further, notes this is our first tpt w/ real transport security + peer auth, so an inbound node-id allowlist hook belongs here — and that it says nothing about the other backends. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
Plan doc for gh #482 + the tunnelled-maddr item of #443. Pushes back on the framing that `wg` is a tpt: it's transparent to `socket(2)`, so it belongs as a *bindspace* — a scoped `@acm`-managed net ctx that an existing L4 tpt binds *inside* — and it's what finally implements the long-spec'd (never implemented) `Address.namespace`. Deats, 3 independently-shippable layers, - A) declarative: commit #482's examples, teach `parse_maddr()` the `/…/wg/u<key>` suffix -> a `TunnelledAddress` wrapper whose `.proto_key`/`.unwrap()` delegate to `.inner` so nothing new crosses the wire and every existing table lookup keeps working. - B) swap the `subprocess.run(['sudo', 'wg', 'show'])` shelling for `pyroute2`. Default to `trio.to_thread` around the sync API (these are one-shot ops at bind/teardown, never hot-path), w/ sans-io codecs + a trio `AF_NETLINK` sock as the follow-up for the read paths. Explicitly forbids dragging `trio-asyncio` in. - C) `open_bindspace()`/`open_netns()`/`open_wg_iface()` `@acm`s folded w/ an `AsyncExitStack`, + filling in the `# !TODO, always be ns aware!` placeholder already sitting in `Endpoint.pformat()`. Also flags the subtlest bug in the whole thing: `setns(2)` is *per-thread*, so a `pyroute2` query issued via `trio.to_thread` lands in the *original* netns. Test-first, per usual. Further, designs for the generalization (`TunnelSpec` union + `match` dispatch) while only implementing `wg`+netns, and calls out `veth`-in-netns as the better *first* one bc it makes a fully self-contained two-"host" integration test possible w/o `wg` at all. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
Landing page for `ai/tpt-backends/`: points at the contract spec as required first reading, tables the 3 plans against their issues/deps/size, and states the landing order + why. Deats, - TIPC first as the cheap proof the table-registration story generalizes to a genuinely new proto (stdlib-only, and `trio`'s sock wrappers are family-agnostic). - `wg` layer-A next since it's deployable-today doc/example work. - QUIC last, gated on its own prep PR. - notes that plans 01 and 02 both want the same `Address.rebind_from_sockname` gate, so whichever lands first ships it. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
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`))
The prior revision (and gh #482's examples) had it as a suffix, `/ip4/10.0.11.1/tcp/1616/wg/u<key>`. Wrong: verified against `baudco/py-multiaddr@wg_support` (py-multiaddr#108) installed in a throwaway venv, the canonical form is /ip4/192.168.1.50/udp/51820/wg/u<A_pub>/ip4/10.0.11.1/tcp/1616 where segs *before* `/wg/` are the **bearer** — the underlay `(ip, udp-port)` `wg(8)` itself listens on (`ListenPort`), per the codec docstring's own example — and segs *after* are the **overlay** ep, the only part we ever bind. The suffix form does parse, which is why it slipped through, but it's semantically inverted: overlay addr where the bearer belongs, `tcp` where wg's `udp` goes, and no overlay ep declared at all. Records the observed `[p.name for p in m.protocols()]` lists so the `match` can be written against fact, and replaces the "composed vs not" framing w/ what's actually the design axis: three parts, three **owners** — bearer bound by the kernel via `wg-quick`/`pyroute2`, `/wg/u<key>` bound by nothing (it's an identity, verified out-of-band), overlay bound by our `IPCServer` as `.inner`. `_peel_tunnel_segs()` correspondingly grows a 3rd return, splitting *at* the tunnel seg so nested tunnels fall out for free. Also hoists the netns conclusion to the top of §5.3 where it can't be missed: netns is a **runtime-level config API, not an actor-app-code one**. It's a spawn/boot-time input alongside `enable_transports`/`tpt_bind_addrs`, deliberately w/ no `await actor.enter_netns(...)`, because `setns(2)` neither moves already-created sockets nor applies beyond the calling thread — so a mid-life API would silently leave the IPC server bound in the old ns. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
Re-renders gh #482's examples w/ the corrected (infix) maddr grammar, as the "layer A" slice of the wg plan: declarative maddrs only, tunnel pre-provisioned out-of-band, zero runtime changes. - `wg_maddr.py`: a `frozen=True` `msgspec.Struct` addr carrying `bearer`/`peer_pubkey`/`inner` (+ `inner_proto`), a `.maddr` property that re-renders the canonical form, and pure `mb_pubkey()`/`wg8_pubkey()`/`parse_wg_maddr()`. The parser rejects #482's inverted suffix form w/ an actionable error and stays **side-effect free** — `verify_wg_peer()` is a separate, explicitly impure step the caller composes, never something a parse path shells out to. - `host_a_srv.py`/`host_b_client.py`: the two-host runs, passing only `addr.inner` into `open_nursery()`/`open_root_actor()`, which is the whole point — the bearer + key layers are already established before any bind happens. - `README.md`: the grammar + the 3-owners table, the `#108` branch install line, tunnel setup, and a "what changed vs #482" section enumerating the corrections. Runnable-shaped but **not yet run against a live tunnel**; that's next, and the reason these sit on the planning branch rather than in `examples/` proper. `_segments()` marks its stopgap for when the `wg` codec isn't installed. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
`tests/test_docs_examples.py` walks `examples/` **recursively** and subproc-runs every collected file asserting `rc == 0`. Ran its exact filter against the tree: all 4 of our files were being collected — including `README.md`, since the filter never checks the extension, so CI would have literally tried `python README.md`. These need a real second host + a live `wg` tunnel, so they can't ever satisfy that gate. `'multihost' not in p[0]` is already in the test's exclusion list w/ no dir yet using it, so this is a pure `git mv` — zero test changes — and it's what the exclusion was plainly there for. Collection drops 24 -> 20 files, 0 of them ours. Also records *why* in the two places someone would look before adding the next one: a callout at the top of the example README and a note on plan 03's §3.4 deliverables. Anything needing a second host or live tunnel goes under `examples/multihost/`. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
One record covering all 9 commits on this branch, per the NLNet generative-AI policy and the existing `ai/prompt-io/claude/` convention. Uses diff-ref mode for both the plan docs and the example code (`git diff main..ng_tpts_planning -- <path>`) rather than duplicating content already in `git log -p`. Kept verbatim in the `.raw.md`: the four verified findings (trio's family-agnostic `SocketStream`/`SocketListener`, the round-trip table proving `/wg/` is infix, the proto-key `UnwrappedAddress` rationale, and `setns(2)`'s per-thread reality), since those are reasoning rather than diffable output. `## Human edits` records that the steering here was substantial and mid-session rather than post-hoc: two model claims about wg maddr semantics were challenged and retracted (incl. in an already-posted issue comment), and the proto-key + netns-as-runtime-config framings were human-directed. Also notes the one model-initiated correction — a pre-publication self-review that downgraded the `uniffi`/asyncio thesis and the TIPC duplicate-binder claim to explicitly-flagged assumptions. Prompt-IO: ai/prompt-io/claude/20260813T001102Z_27c34aeb_prompt_io.md (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
py-multiaddr#108 (the `/wg/u<key>` maddr proto) merged upstream on 2026-07-28 as `f86519da`, but ships in no release yet — the latest `0.2.0` predates it by ~4 months and carries no `wg` codec at all. So `examples/multihost/wg_lan/` can't parse its own maddrs off PyPI. Pinned by `rev` and not `branch` so CI stays reproducible. Note the lock now records the git source *instead of* the `>=0.2.0` specifier, i.e. the dep floor above is fully overridden for as long as this pin lives. TODO, drop the pin (and bump that floor) the moment a release carries the codec; the only consumer is the `wg_lan` example set. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
`_segments()` called `Multiaddr(maddr)` purely to validate, then swallowed every failure under `except Exception: pass`. That was harmless pre-#108 — w/o a `wg` codec there was nothing to validate — but now that the codec is pinned in, the swallow is load-bearing and disabled: a malformed key sails past validation into `wg8_pubkey()`, which happily emits a corrupt b64 str, and the returned struct then fails its own `.maddr` round-trip. No raise, just quietly wrong output. Deats, - add `_have_wg_maddr_proto()`, the gate plan-03 already referenced but which never actually existed. Impl'd as `protocols.protocol_with_name('wg')` under `except ProtocolNotFoundError` and cached in a mod global, same shape as the TIPC plan's `is_tipc_available()`. - only validate when that gate is `True`, and let `StringParseError` propagate — a maddr which doesn't parse must NOT reach `wg8_pubkey()`. - keep the degraded split for a pre-#108 install, now w/ an explicit `XXX` naming the validation you give up. So parsing stays pure but becomes total-or-raises. Our own `ValueError`s (missing `/wg/` seg, bare tunnel w/o an overlay ep) are unaffected, as is the `wg(8)` b64 round-trip. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
it lands" framing in plan-03 and the example README was stale in both directions: the branch pin is obsolete, yet you still can't just `pip install multiaddr`. Deats, - §3.2's grammar table is now re-verified against the upstream merge (`f86519da`) rather than only `baudco@wg_support` in a throwaway venv. Also notes the codec enforces a 32-byte key, so a truncated one is a `StringParseError` and not a silently mangled parse. - §1 says merged-but-unreleased; the still-open work is spec registration (py-multiaddr#107 + gh #483). - §3.4 swaps "pin the branch" for the `[tool.uv.sources]` `rev` pin, and fixes the `_have_wg_maddr_proto()` recipe it suggested — probing w/ `Multiaddr('/wg/uAAAA')` now ALWAYS raises bc the codec wants 32B, i.e. that feature-detect would report `False` even w/ the proto perfectly well known. - risk table row goes "#108 not merged" -> "merged but unreleased". - example README: `uv sync` alone now suffices bc of the pin; documents the 32B check and points at `_have_wg_maddr_proto()` as the gate. The one surviving `baudco` mention is deliberate, it records where the grammar was *first* verified. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
`py-multiaddr` already ships the entire tunnel compose/peel
surface and this module was reimplementing it — a raw
`maddr.split('/')` plus index arithmetic, sitting directly under
a comment congratulating itself for not hand-rolling a parser.
Same NIH trap gh #429 existed to close, just one layer up. The
API was linked from gh #443's own 2nd bullet the whole time.
So every cut now goes through the real thing,
| need | API |
| --- | --- |
| isolate the bearer | `.decapsulate_code(P_WG)` |
| per-seg maddrs | `.split()` |
| rejoin a seg tail | `Multiaddr.join()` |
| read the key | `.value_for_protocol('wg')` |
| recompose | `.encapsulate()` |
`.decapsulate_code()` turns out to handle the infix `/wg/` seg
cleanly *because* it cuts on proto-code and never tries to match
an addr value — the key seg has no addr of its own, which was
the exact thing I'd assumed would need bespoke handling.
Deats,
- rename the role fields `inner`/`inner_proto` ->
`overlay`/`overlay_proto`, matching `py-multiaddr`'s
encapsulation model (earlier segs wrap later ones) and #443's
owner table. `inner` collided head-on w/ call-stack `inner`,
where it reads as higher-up + later-called, while here the
encapsulated addr is bound *first* and sits deeper.
- drop `_segments()` and its degraded hand-split path entirely.
W/o the codec there's now one actionable `RuntimeError`
instead of a silent downgrade, superseding the swallow fix in
7d6e795.
- add `.as_multiaddr()` so callers can stay in `Multiaddr` land;
`.maddr` is now just `str()` of it.
- accept `str|Multiaddr` on the way in.
- carry `bearer_ip`/`overlay_ip` so a v6 stack re-renders as v6
— the old `.maddr` hardcoded `/ip4/` and would silently
mangle it.
- both host scripts follow the rename to `.overlay`.
⚠️ `value_for_protocol('ip4')` on a *full* tunnelled maddr
silently returns the **first** match, i.e. the bearer's host, so
it's only ever called here on an already-peeled sub-maddr.
(this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
§3.2 specced a pure fn `_peel_tunnel_segs(proto_names) -> (bearer_names, tunnel_specs, overlay_names)` to split a maddr at its tunnel seg. It should never be written: `py-multiaddr` ships that whole surface already and the plan simply missed it, even though gh #443's 2nd bullet links the README sections in question. Replaced w/ a⚠️ CORRECTION carrying the verified API table (`.decapsulate_code(P_WG)` for the bearer, `.split()`/`.join()` for a seg tail, `.value_for_protocol()` to read a value, `.encapsulate()` to recompose) plus *why* it works on an infix `/wg/` seg: the cut is by proto-code, never by matching an addr value, and the key seg has no addr of its own. Also, - adopt `bearer`/`overlay` as the role names throughout, and say plainly why not `inner`/`outer` — the call-stack reading of "inner" is the exact opposite of the encapsulation one. - warn that `value_for_protocol('ip4')` on a full tunnelled maddr silently yields the *bearer's* host; only call it on a peeled sub-maddr. - note nesting (wg-in-wg) falls out of `.decapsulate_code()` cutting at the *last* occurrence, so peel repeatedly rather than recursing through a bespoke splitter. - `mk_maddr()` for `TunnelledAddress` is `.encapsulate()` composition, not `str` building. - README: drop the "degrades to a plain segment split" para, since that path is gone — no codec now means one actionable raise. (this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
4730b4c to
768b531
Compare
Replace mutable branch pointers with the exact source commit and historical base-to-commit range. Normalize the substantive scope while leaving the recorded raw response unchanged. Prompt-IO: ai/prompt-io/claude/20260813T001102Z_27c34aeb_prompt_io.md (this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Bring the shared backend contract and TIPC plan in line with current runtime behavior and downstream implementation evidence. Rework the QUIC plan around actor-owned endpoint, bootstrap, stream, listener and cleanup lifecycles, with explicit validation gates for the still-unverified UniFFI details. (this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Validate WireGuard keys and tunnel descriptors strictly. Inspect iface keys asynchronously by local/peer role and reject unsupported nested tunnels. Correct both host binds and service publication, document an unprivileged two-host setup and pin the merged `py-multiaddr` codec revision in the lock. Prompt-IO: ai/prompt-io/opencode/20260831T022317Z_768b5316_prompt_io.md (this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Record #493's current draft head, #492's advanced planning tip and the exact restack sequence before final landing. Also, - keep the unrelated `pformat` red-test/fix pair ordered for its standalone `main` PR - distinguish the 17 substantive arc commits from the local-cache ignore - make the in-repo handoff authoritative over agent memory - preserve digest/drift checks for already-authorized forge writes (this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
There was a problem hiding this comment.
🟢 Approval recommended
The changes are predominantly planning docs plus an opt-in example and a scoped uv pin/lockfile update, with only minor usability polish suggested for the example’s error reporting.
Review details
- Files reviewed: 14/15 changed files
- Comments generated: 1
- Review effort level: Lite
| if inspection is None: | ||
| with trio.fail_after(timeout): | ||
| proc = await trio.run_process( | ||
| ['wg', 'show', iface, field], | ||
| capture_stdout=True, | ||
| check=True, | ||
| ) | ||
| inspection = proc.stdout.decode() | ||
|
|
There was a problem hiding this comment.
response authored by
opencode
The usability point is valid, but we are keeping this planning PR's
manual example unchanged. PR #511 replaces the example-local wg
subprocess with pyroute2 inspection and carries the runtime,
namespace, lifecycle, and failure-path coverage. Hardening the
superseded tinkering path here would duplicate that stacked work.
Add impl plans for
TIPC/QUIC/wgtpt backendsMotivation
TIPC (#378), QUIC-via-
iroh(#353), and WireGuard (#482,#443) each challenge assumptions hidden by the current TCP/UDS-only
transport model. Implementing them independently would otherwise mean
re-deriving address dispatch, listener, registration, and lifecycle
contracts differently for every backend.
This is planning-led work rather than a runtime implementation. It
does not change
tractor/, but it is not literally docs-only: itadds an executable, opt-in two-host WireGuard example and temporarily
pins
multiaddrto the exact upstream revision containing itsunreleased
wgcodec.The plans define the shared contract first, then isolate
backend-specific decisions and unresolved work so each implementation
can proceed without silently changing wire or resource-lifecycle
semantics.
Src of research
The plans prefer implementation and kernel evidence over recalled API
behavior:
wg(WireGuard) protocol support multiformats/py-multiaddr#108wgcodec and composed multiaddr behavior.AF_TIPCtransport backend #493include/uapi/linux/tipc.handnet/tipc/.Summary of changes
explicit protocol-key dispatch, listener reflection and rebinding,
capability checks, stream framing, registration, and test plumbing.
AF_TIPCtransport backend #493. Separate implementedtransport and topology primitives from registrar election,
collision, topology-consumer, and registrar-less discovery
follow-ups.
launch-time transport bootstrap, listener-owned feeder tasks,
connection leases, supervised UniFFI operations, and teardown that
spans parent dial through final deregistration.
transport. Keep the kernel-owned bearer and tunnel identity outside
Tractor while binding and dialing only the overlay endpoint.
examples/multihost/wg_lan/with pure multiaddr parsing,strict 32-byte key validation, explicit local/peer verification,
host-local binds, stable RPC exposure, and rejection of unsupported
nested tunnels.
multiaddrto the immutable merge revision for upstream Multiple issues on the code examples present in the README file. #108and regenerate
uv.lock; remove the pin once a release includesthe codec.
the contract, lifecycle, and executable-example repair pass.
The branch passes staged-diff checks,
uv lock --check, Ruff, Pythoncompilation, WireGuard address round trips, role checks,
malformed-key rejection, nested-tunnel rejection, and
Taken-independent review. No live TIPC, iroh/UniFFI, or two-host
WireGuard network test was run.
Scopes changed
ai.tpt-backendsimplementation plans.
examples.multihost.wg_lanclient example.
multiaddrsource pin and regeneratedlockfile.
ai.prompt-ioTODOs before landing
These remain design-review decisions rather than mechanical test
failures:
UnwrappedAddress. It is awire-format change (
SpawnSpec,_root_mailbox,_registry_addrs) plus every fixture and downstream config. Decidewhether it lands before the first new backend.
wgis a bindspace rather than atransport. A
WGAddressin_address_typeswould break thattable's one-to-one transport mapping.
irohselection given its UniFFI bridge.aioquicremains the fallback and the adapters should remainreusable with a sans-I/O core.
Channeloreach
tractor.Contextfor independent flow control andcancellation.
Future follow up
UnwrappedAddressmigration on its ownbranch as the natural base for new transport backends.
Addressthepublic currency and keep
UnwrappedAddressas an internal wireform.
examples/multihost/wg_lan/against a live two-hosttunnel; until then, treat it as executable but network-unproven
example code.
/tipcin the multiaddr specification alongside theexisting
wgregistration effort.Address.namespace, beginning with a self-containedbindspace rather than privileged WireGuard lifecycle management.
vethin a network namespace as the first managedbindspace and as a self-contained two-host integration-test
substrate.
Multiaddr.decapsulate()documentation,substring-matching, and unreachable-error-path fixes reported in
py-multiaddr#109.
in Tractor proper rather than introducing bespoke segment slicing.
and errors for the exact
wgcodec revision pinned here.wgmultiaddr protocol: upstream spec submission plan #483 so thetwo
wgspecification-registration plans do not diverge.Links
TIPC, the "end game" cluster proto maybe? #378 - TIPC backend.QUICtransport research #353 - QUIC backend.wgmultiaddr: LAN deployment examples #482 - Originalwgmultiaddr examples.wgmultiaddr protocol: upstream spec submission plan #483 -wgmultiaddr specification submission plan.AF_TIPCtransport backend #493 - Downstream TIPC implementation evidence.TIPC_TOP_SRV-driven push registry indiscovery._registry#496 - TIPC topology-feed consumer follow-up.wg(WireGuard) protocol support multiformats/py-multiaddr#108(this pr content was generated in some part by
opencodeusinggpt-5.6-sol(openai))