Add private `enter_netns()` bootstrap validation before actor runtime
integration.
Deats,
- verify the inherited FD against its expected namespace inode
- constrain `setns()` to Linux network namespaces
- verify `/proc/self/ns/net` after the entry syscall
- leave inherited FD ownership to the future spawn caller
- exercise guards with real FDs and unprivileged syscall fakes
Prompt-IO: ai/prompt-io/opencode/20260827T232500Z_d067505a_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Record native tagged encoding for complete `TunnelledAddress` graphs as
a deferred design follow-up.
Deats,
- cover concrete overlay-address and tunnel-spec unions
- preserve optional `BindspaceRef` metadata through decoding
- replace untyped payload inspection with typed roundtrip tests
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Add `TunnelledAddress.with_bindspace_ref()` to annotate frozen tunnel
declarations with stable, serializable namespace metadata.
Deats,
- omit absent refs from the existing msgspec shape
- reject declared and realized namespace-name mismatches
- report the declared key before realization and inode afterward
- keep transport peeling free of live bindspace capability state
- expose realized refs through real listener diagnostics
Prompt-IO: ai/prompt-io/opencode/20260826T030534Z_d130431c_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Replace the unshipped `BindspaceIdentity` and `BindspaceHandle` names
with `BindspaceRef` and `Bindspace` across existing lifecycle APIs.
Deats,
- define refs as wire-safe, host-local and non-owning records
- reserve `Bindspace` for the live FD-backed capability
- rename the capability's realized-resource field to `.ref`
- update lifecycle tests and active design contracts
- omit compatibility aliases for the unshipped model names
Prompt-IO: ai/prompt-io/opencode/20260827T211115Z_d130431c_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Add `open_wg_bindspace()` to enter one declared bindspace and an
ordered WireGuard interface stack as one async lifetime.
Deats,
- snapshot caller layer ordering before the first checkpoint
- enter interfaces outermost-first through `AsyncExitStack`
- unwind interfaces before releasing the namespace capability
- yield the live `BindspaceHandle` for endpoint allocation
- test mutable input and cancellation ordering with lifecycle fakes
Prompt-IO: ai/prompt-io/opencode/20260826T022434Z_2245f094_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Add `open_wg_iface()` to create, configure and remove one WireGuard
interface inside a pinned bindspace through pyroute2.
Deats,
- validate listen/dial bearer policy before kernel side effects
- configure local addresses, private key, listen port and peers
- fill an omitted dial endpoint from the selected tunnel bearer
- clean partial synchronous failures before returning to Trio
- shield owned interface creation and teardown from cancellation
Prompt-IO: ai/prompt-io/opencode/20260826T003430Z_6dd39da0_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Add process-local `WGPeerConfig` entries and make
`WGInterfaceConfig` own a unique peer tuple for listener and dial
provisioning.
Deats,
- carry peer public keys, allowed CIDRs and optional endpoints
- redact per-peer preshared keys while blocking wire encoding
- validate peer routes, endpoint ports and keepalive intervals
- reject duplicate peers before future kernel mutation
- support multi-peer listeners without overloading tunnel identity
Prompt-IO: ai/prompt-io/opencode/20260826T001442Z_dcdf4d82_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Add process-local `WGInterfaceConfig` for key material, interface
addresses and peer-routing policy required by future provisioning.
Deats,
- redact private and preshared keys from representation
- block config from default actor-IPC encoding via `ProcessLocal`
- validate keys, interface CIDRs, allowed CIDRs and bounded integers
- keep public endpoint and peer identity in `WGTunnelSpec`
- move allowed-IP policy out of the serializable tunnel declaration
Prompt-IO: ai/prompt-io/opencode/20260825T234631Z_b973e78c_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Add serialized `BindspaceSpec.lifecycle` policy and dispatch it through
`open_bindspace()` without inferring ownership from transport role.
Deats,
- distinguish borrowed `attach` from owned `open` policy
- validate handle ownership against the declared lifecycle
- share policy-neutral FD pinning between both netns contexts
- reject unsupported lifecycle values before side effects
- exercise both dispatcher branches and owned cancellation cleanup
Prompt-IO: ai/prompt-io/opencode/20260825T191845Z_5b2a064a_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Add `open_netns()` to create a named Linux netns through pyroute2,
pin its identity and yield an owned `BindspaceHandle`.
Deats,
- run synchronous creation and removal in Trio worker threads
- shield both privileged side effects from caller cancellation
- reuse `attach_netns()` to pin identity and manage the FD
- close the FD before removing the owned namespace
- fake privileged operations while testing ordering and cancellation
Prompt-IO: ai/prompt-io/opencode/20260825T190529Z_e1007547_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Add `attach_netns()` to pin a current or named Linux netns in a
borrowed `BindspaceHandle` without creating or entering it.
Deats,
- name the current-namespace default `CURRENT_NETNS`
- derive stable identity from the opened FD with `fstat()`
- open descriptors with `O_CLOEXEC` and close them on context exit
- constrain named lookup beneath the standard iproute2 run directory
- report field-specific validation and missing-resource errors
Prompt-IO: ai/prompt-io/opencode/20260825T045557Z_fdccfd7e_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Separate serializable `BindspaceSpec` and `BindspaceIdentity` values
from a process-local `BindspaceHandle` carrying FD and ownership
authority.
Deats,
- add global `ProcessLocal` wire guards for local handle structs
- derive valid kinds and ownership from their `Literal` aliases
- require a positive inode while keeping the mutable name optional
- pin supplied FDs to identity inodes with `fstat()`
- cover round trips, nested encoding and stale capabilities
Caught-during: review remediation
Found-via: `/run-tests` test_bindspace_handle_pins_local_capability
Prompt-IO: ai/prompt-io/opencode/20260822T042026Z_29141f0b_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Make `TCPAddress` and `UDSAddress` explicitly satisfy
`Address.namespace`, then retain each original listener declaration
beside its peeled, resolved transport address.
Deats,
- remove `TunnelledAddress`'s attribute fallback
- add required `Endpoint.declared_addr` metadata
- report declaration namespaces in endpoint/server formatting
- preserve concrete `Endpoint.addr` for transport reflection
- cover plain and tunneled namespace visibility
Prompt-IO: ai/prompt-io/opencode/20260822T032520Z_d35c802b_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Validate a declared tunnel key against one `pyroute2` snapshot
containing the iface's own key and configured peers.
Deats,
- share worker offload across all WireGuard key readers
- forward `WGTunnelSpec.iface` and `.netns` to the read
- reject malformed declarations before netlink I/O
- export the async helper and cover local, peer and absent keys
- replace multihost's `wg show` subprocess probe
Prompt-IO: ai/prompt-io/opencode/20260822T023226Z_59a8ecfd_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Add a candidate Layer-C architecture where a private, eagerly
spawned manager owns pyroute2 and tunnel provisioning for a simple
WG-enabled actor tree.
Deats,
- overlap manager reconciliation with sibling process startup
- contain `AsyncWireGuard` in an infected-asyncio child
- limit requests and capabilities by bindspace security domain
- define readiness, crash, restart and teardown semantics
- retain pre-provisioned and multi-manager escape hatches
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Add async `read_wg_pubkey()` and `read_wg_peers()` helpers which
offload `WireGuard.info()` calls to a `trio` worker thread.
Deats,
- add the Linux-only `wg` extra and pin `pyroute2`
- pass `flags=0` so a read never creates a named netns
- normalize multipart replies, validate keys and de-dup peers
- always close the netlink client, including error paths
- test thread offload, netns forwarding and client cleanup
Prompt-IO: ai/prompt-io/opencode/20260821T233204Z_5d92595f_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
- Make `TCPAddress.unwrap()` emit `('tcp', host, port)` and
`UDSAddress.unwrap()` emit `('unix', path)` while retaining the
compatibility readers from the preceding change.
- Pass concrete TCP fields to Trio, compose multiaddrs from tagged
values, and let `SpawnSpec` carry protocol-specific tuple shapes
for validation by `wrap_address()`.
- Compare runtime, registry, bind, and tunnel addresses through
canonical serialized forms and cover both TCP and UDS operation.
Prompt-IO: ai/prompt-io/opencode/20260820T033108Z_ba07e09d_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
- Define canonical `tcp` and `unix` tuple shapes while retaining
legacy pair aliases as the emitted `UnwrappedAddress`.
- Dispatch tagged tuple/list payloads explicitly, accept `uds` as a
Unix input alias, and preserve legacy TCP, UDS, and native IPv6
readers.
- Cover tag aliases, msgpack-style lists, legacy payloads, and IPv6
socket addresses before switching writers.
Prompt-IO: ai/prompt-io/opencode/20260820T033107Z_ba07e09d_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Separate serializable bindspace declarations from live namespace
identity, FDs, ownership and teardown resources.
Require child namespace entry during spawn bootstrap, before actor
runtime initialization, then distinguish listen/dial provisioning and
owned/borrowed cleanup without encoding operation role into maddrs.
Prompt-IO: ai/prompt-io/opencode/20260820T021516Z_dfad66a0_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Carry tunnel declarations through listener configuration, then strip
them immediately before constructing transport endpoints.
Also allocate random listener addresses from a contacted registry's
overlay, and prove a real TCP listener never stores the wrapper while
the source declaration retains its bindspace metadata.
Prompt-IO: ai/prompt-io/opencode/20260819T213145Z_f81fc5e5_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Retain tunnel annotations through address declaration, then hand only
the bindable overlay to exact-type transport lookup and dialing.
Broaden `Channel.from_addr()` and `_connect_chan()` inputs accordingly,
and cover plain plus tunnelled TCP dispatch arguments.
Prompt-IO: ai/prompt-io/opencode/20260819T213144Z_f81fc5e5_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Drop the example-local address struct and hand-rolled single-tunnel
parser now that discovery owns the production implementation.
Keep only the explicit `wg(8)` peer probe in the multihost helper,
and update the examples and plan for nested parsing, packaged codec
dependencies and tractor-owned bindspace provisioning.
Prompt-IO: ai/prompt-io/opencode/20260818T075031Z_dd02c7c0_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Teach discovery to preserve WireGuard bearer and identity metadata
around a bindable TCP overlay.
Deats,
- encode `wg(8)` keys as strict 32-byte multibase values
- peel nested stacks with `Multiaddr.decapsulate_code()` and compose
them with `.encapsulate()` instead of splitting strings
- integrate wrappers with `parse_maddr()`, `mk_maddr()`,
`wrap_address()` and `parse_endpoints()`
- pin the unreleased py-multiaddr#108 codec in package metadata
- cover exact round trips, nesting, bad grammar and missing codecs
Prompt-IO: ai/prompt-io/opencode/20260818T075031Z_dd02c7c0_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Introduce the first layer-A address type from the `wg` bindspace
plan without treating a transparent tunnel as a `MsgTransport`.
Deats,
- add frozen `WGTunnelSpec` and `TunnelledAddress` structs which
delegate proto identity, bindspace, validity and wire
serialization to their overlay
- add `strip_tunnels()` and `tunnels_of()` for nested wrappers
- recognize wrappers in `is_wrapped_addr()` while keeping them out
of `_address_types`
- cover delegation, namespace fallback and nested peeling semantics
Also,
- widen `Address.namespace` ids for named netns
- export the new discovery API
- clarify that tractor's layer-C bindspace lifecycle may provision
the kernel-owned bearer without making it a `MsgTransport`
Prompt-IO: ai/prompt-io/opencode/20260818T021729Z_d9a6e2e9_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
§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`))
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`))
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`))
`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`))
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`))
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`))
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`))
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`))
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 #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`))
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`))
Give only the macOS matrix leg two retries so actor/PTY timing
flakes do not strand otherwise-green runs. Linux and Windows remain
strict first-attempt jobs, while persistent macOS failures stay red
after the final visible rerun.
Deats,
- add the pytest-dev-maintained plugin to testing deps
- keep a one-second delay between macOS attempts
- validate the workflow, lock and both observed flaky test areas
Prompt-IO: ai/prompt-io/opencode/20260821T052052Z_3690e43a_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Release 25 concurrent `to_actor.run()` callers through one local
barrier so their implicit child starts race the first remote error.
Exercise one, five and 25 deterministically placed errorers without
overloading actor-nursery internals.
Validate bounded cancel-on-first teardown, only boxed assertion
relays and empty actor-nursery child/reap maps across Trio and
multiprocessing backends. Clarify this successor's distinction from
`test_nested_multierrors()`, diagram flat-pool error propagation and
align the nearby expected-error comment with its handler.
Review: PR #484 (goodboy)
https://github.com/goodboy/tractor/pull/484#discussion_r3858891778
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
PR #481 made target inputs positional and reserved keywords for
actor placement/runtime controls. PR #484 still forwarded target
kwargs, so tests and examples failed local signature binding after
the rebase.
Deats,
- bind named target inputs with `functools.partial()`
- keep placement, naming and runtime controls as direct keywords
- reject invalid target calls locally before actor startup
- require linked one-shots to raise one direct `RemoteActorError`
- doc linked context execution and per-child process reaping
Prompt-IO: ai/prompt-io/opencode/20260819T184640Z_481ba003_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Fold the endeavour's resolution into the plan doc + log the
session per prompt-io policy,
- `ria_nursery_removal_plan.md`: RESOLVED section — migrate
everything, remove the API; the migration-pattern table
(blocking / fire-and-forget / fan-out / collect-don't-cancel
/ mutual-rendezvous), the semantic deltas (cancel-on-first +
`collapse_eg()` chain collapse vs the old teardown-reap BEG),
the excision inventory and the structural dissolution of the
reap-hang class.
- adds the `to_actor.open_one_shot()` follow-up sketch: an
`@acm` + private task-nursery over the existing blocking
`run()` — done-`trio.Event` as a result memo (NOT a
cancel-relay), no `Portal` in the iface, errors always
propagate at scope exit; zero `_supervise` coupling.
- prompt-io entry `20260706T172818Z_ad42871e` (+ raw diff-ref
companion) covering commits `d01a2123..ad42871e`.
(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
Append two sections to the ria-removal plan capturing the
2026-07-02 hang episode + the resulting design pivot.
Regression writeup: the full-suite hang on
`test_tractor_cancels_aio` root-caused to the step-A reaper
hoist (`5cd190c5`), not the B2 handler merge. The happy-path
`_reap_ria_portals()` parks unbounded on `wait_for_result()`
after a user `portal.cancel_actor()`; the old spawn-backend
reaper raced `soft_kill()`'s scope-cancel, the hoist dropped
it. Records the `proc.poll()` death-watch fix + why poll (not
the event `wait_func`) bc `soft_kill` already awaits
`proc.sentinel` (a 2nd `wait_readable` -> `BusyResourceError`).
Pause writeup: user's insight that the hoist landed in the
wrong scope — result-waiting belongs in the `to_actor`
one-shot scope (`_invoke_in_subactor()`), beside `an` + a
local task-nursery + cancel-scope, where bounding the wait is
trivial + the hang dissolves. So the poll fix is likely
SUPERSEDED (flagged do-not-land); the anti-hang guard commit
(`d1fb4a1a`) stays red-first per the failing-test convention.
(this patch was generated in some part by `claude-code` using
`claude-opus-4-8` (`anthropic`))
Split from the step-B2 code commit to keep the runtime diff
free of `ai/` meta noise,
- `ai/conc-anal/ria_nursery_removal_plan.md`: add a "Step-B2
outcome" section — the dead-outer-`raise` trace, why the
merge is behavior-preserving, and the gate results.
- `ai/prompt-io/claude/20260702T222544Z_9201a2ed_*`: NLNet
provenance (log + unedited raw) for the step-B2 work.
(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
Split from the step-B code commit to keep the runtime diff
free of `ai/` meta noise,
- `ai/conc-anal/ria_nursery_removal_plan.md`: add a "Step-B
outcome" section — the empty-nursery collapse, why it's
behavior-preserving, the deliberate handler-merge deferral,
and the targeted-gate result.
- `ai/prompt-io/claude/20260702T172233Z_5cd190c5_*`: NLNet
provenance (log + unedited raw) for the step-B work.
(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
Split from the step-A code commit to keep the runtime diff
free of `ai/` meta noise,
- `ai/conc-anal/ria_nursery_removal_plan.md`: agent-verified
machinery map + 3-step (A/B/C) design + probe history
(reap-relocation deadlock -> sequencing fix -> bound
tighten) + risk register for the `._ria_nursery` excision.
- `ai/prompt-io/claude/20260702T165806Z_a34aaf98_*`: NLNet
provenance (log + unedited raw) for the step-A work.
(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
Partial normalization assigned the optional Python 3.14 placeholder
sentinel separately from its only conditional consumer.
Bind the sentinel with a walrus expression directly in the guard while
retaining the `getattr()` fallback for older Python versions.
Review: PR #481 (goodboy)
https://github.com/goodboy/tractor/pull/481#pullrequestreview-5012942328
Prompt-IO: ai/prompt-io/opencode/20260825T021319Z_ce430fca_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Child monitors register before their IPC handshake so cancellation
owns every started process, but the bare `None` portal arg obscured
that `Portal(chan)` replaces the provisional entry after connection.
Document that transition and name all `_register_child()` args in both
spawn backends. Replace the MP test's positional-only lambda with a
signature-accurate fake which asserts the provisional portal state.
Caught-during: review remediation
Found-via: `/run-tests` test_mp_late_registration_never_starts_process
Review: PR #481 (goodboy)
https://github.com/goodboy/tractor/pull/481#pullrequestreview-5012942328
Prompt-IO: ai/prompt-io/opencode/20260825T015742Z_e42ecb55_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
The fresh reap-coordination maps still used legacy `.uid` tuples even
though process monitors and channels carry complete `Aid` identities.
This extended the legacy key format into new private state.
Key both reap maps by `Aid` and derive `.uid` only when accessing the
existing `_children` map. UUID-based `Aid` hashing lets the subactor
and decoded channel identities resolve the same synchronization state.
Update registration tests to exercise the full identity keys.
Review: PR #481 (goodboy)
https://github.com/goodboy/tractor/pull/481#pullrequestreview-5012942328
Prompt-IO: ai/prompt-io/opencode/20260824T233957Z_5327b25e_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
`_try_cancel_then_kill()` repeated the same child/tree debugger
predicate before and after its cancel-RPC checkpoint. Inline
duplication obscured that lock state must be sampled at both points.
Factor the predicate into a local `child_in_debug()` sampler. Use it
for initial hard-kill protection and re-run it after the await before
debugger waiting, preserving dynamic lock-state behavior. Keep it
local since one input is supervisor-owned nursery configuration.
Review: PR #481 (goodboy)
https://github.com/goodboy/tractor/pull/481#pullrequestreview-5012942328
Prompt-IO: ai/prompt-io/opencode/20260824T225356Z_2f86dd1a_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
`._mark_child_reaped()` previously discarded the reap-request event
without checking that its completion-event peer existed. A one-sided
entry would silently lose process-reap synchronization.
Capture both pops and assert paired presence while allowing the valid
both-absent startup-failure path. Keep an unset request valid because
backend cancellation can reap immediately after registration.
Extend graceful and failed-cancel-ack runtime tests to require all
child and reap mappings empty before `to_actor.run()` returns.
Review: PR #481 (goodboy)
https://github.com/goodboy/tractor/pull/481#pullrequestreview-5012942328
Prompt-IO: ai/prompt-io/opencode/20260824T223614Z_88d538e3_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
A parent-side ctx cancel timeout previously bounded only the
remote `_cancel_task` ack. Complete-frame `send_all()` shielding
could hold request publication forever when a peer stopped reading.
Compute one absolute deadline and pass it through `._run_from_ns()`
so transport publication and the ack wait consume the same timeout
budget. Add a mock-clock regression which stalls the private RPC
under a nested shield and proves the transaction returns on time.
Review: PR #481 (goodboy)
https://github.com/goodboy/tractor/pull/481#pullrequestreview-5012942328
Prompt-IO: ai/prompt-io/opencode/20260824T222033Z_ce38cb6f_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
A child could pass the early `.start_actor()` guard, miss the
`.cancel()` child snapshot and register afterward. Its monitor
inherited a reap request without runtime cancellation and could wait
forever.
- publish child/reap events before sampling `_cancel_called`
- make MP abort before `proc.start()` when cancellation won
- kill a Trio child opened after cancellation won registration
- reject starts begun after nursery cancellation is already visible
- add deterministic registration and MP no-start regressions
- drop the touched Trio backend's stale `get_runtime_vars` import
Prompt-IO: ai/prompt-io/opencode/20260821T040803Z_3c1bbe73_prompt_io.md
(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))