§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`))
`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
7d6e7955.
- 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`))
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`))
`_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`))
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`))
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`))
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`))
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`))
Match `Actor.accept_addr`'s declared `tuple[str, int|str]` contract
instead of narrowing its second item to the UDS-specific string path.
Review: https://github.com/goodboy/tractor/pull/512#discussion_r3888153533
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Capture the human direction, generated changes, review findings and
validation results for the docs-example landing pass.
Point generated-code references at the complete commit range from
the pre-remediation branch head.
Prompt-IO: ai/prompt-io/opencode/20260828T200822Z_0be872ff_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Document `Started` as the eager sender-side payload check and
`Yield` plus `Return` as receiver-side decoding boundaries without
promising a symmetric error relay.
Separate the working task-scoped codec encoder from the private
per-dialog decoder and the incomplete `@context` hook params.
Link the planned typed `Start` contract and sender-side argument
validation follow-up in #514.
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Run the registrar in its own process and prove that a sibling client
discovers the service through registry lookup instead of an existing
peer channel.
Retry ephemeral bind collisions, publish readiness atomically and
validate bounded cross-platform shutdown. Document actual duplicate
name and multi-registrar ordering semantics alongside the example.
Move the demo under the discovery examples and wrap process ownership
in an `@acm`. Record the future public subsystem, Piker service and
pytest isolation follow-ups.
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Finish the Python style, typing and docstring pass across the
debugger examples without changing their intentional breakpoints,
failures, cancellation races or timeout reproducers.
Restore full child command lines in the documented process trees
and keep the examples within the 69-column source limit.
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Finish the Python style, typing and docstring pass across the
ordinary, parallelism, Trio and integration examples.
Preserve each demo's runtime behavior while tightening callable,
portal, stream and nursery annotations. Use the modern `.chan`
portal attr and current actor-lifecycle terminology.
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Finish the examples-typing sweep with the last non-docs-visible
scripts: `-> None` on the two `trio/` behavior-demo mains (plus a
`trio.TaskStatus` on `hold_lock_forever`) and nursery/portal typing
on `integration/mpi4py/inherit_parent_main.py`.
Leaves `concurrent_futures_primes` (a verbatim stdlib baseline) and
`integration/open_context_and_sleep` (its tractor nursery is
commented out) as-is, and the paren-group `trio.open_nursery()`
bindings unannotated (no clean spot for a preceding annotation).
Completes the examples-typing bullet in #472.
(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
Type the runtime objects (`ActorNursery`, `Portal`, `Context`,
`trio.Nursery`) + fn signatures across the 16 highest-visibility,
`literalinclude`-d `examples/` scripts, matching the front-page
`we_are_processes.py` style — so the rendered guides show typed
usage throughout, not just on the landing snippet.
Spans the 3 quickstart-backing scripts + `single_func`,
`remote_error_propagation`, `multiple_streams_one_portal`,
`quick_cluster`, `service_discovery`, `service_daemon_discovery`,
`asynchronous_generators`, `nested_actor_tree`,
`concurrent_actors_primes`, `streaming_broadcast_fanout`,
`rpc_bidir_streaming`, `infected_asyncio_echo_server`,
`typed_payloads`.
Annotation-only (no renames/logic changes); each runs green and the
docs build stays warning-free. Part of the examples-typing bullet
in #472.
(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
Sweep the `examples/debugging/` set for basic typing: add `-> None`
to all 16 bare `async def main()`s and annotate the clean
single-line `open_nursery()` bindings as `tractor.ActorNursery`.
Kept to the unambiguous, runtime-safe cases (these breakpoint/crash
demos can't be run headless); the heterogeneous
multi-line/paren-group nursery bindings + `current_actor()` returns
are left for a later pass. Continues the examples-typing bullet in
(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
Add a runnable `examples/dedicated_registrar.py` + a "A dedicated
registrar" subsection in `guide/discovery.rst` demoing the
registrar decoupled from any app tree's root: boot a bare
`tractor.run_daemon([], registry_addrs=[...])` as its own process
(a root actor that does nothing but hold the registry), point the
app tree at the same `registry_addrs`, and discover a service
*through* that external registrar.
This is the buildable-today form of the #472
"Registrar-as-subsystem (not the root actor)" bullet. Two
constraints are called out inline as follow-ups:
`enable_transports` is single-proto per runtime (no multi-backend
registrar yet), and a registrar can only be a root (no `actor_cls`
hook on `start_actor()` to spawn one as a subactor).
(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
The "Toward capability-based msging" section only pointed at the
`#196`/`#36` epics. Fold in the concrete recent state,
- `#365` as the most recent step: driving the whole `pld_spec` off
plain type-annotations (e.g. annotating a context's
`open_stream()` with `msgspec.Struct` subtypes) rather than
explicit `pld_spec=` kwargs.
- clarify that the decorator-level `@tractor.context(pld_spec=...)`
is already the higher-level path (vs the lower-level
`tractor.msg._ops.limit_plds()` escape hatch), pointing at
`tests/msg/test_pldrx_limiting.py` + `test_ext_types_msgspec.py`
which exercise both.
- `#376` (from @guilledk, `auto_codecs` branch) as the drafted
public factory API for the `enc_hook`/`dec_hook` pair (today only
reachable via `tractor.msg._ops`).
Addresses the caps-based-msging bullet in #472.
(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
Two tasks could receive through one `BroadcastReceiver` and race its
single `_wait_scope`. A later call could replace the scope, leaving
the first blocked through `.aclose()`.
Track the active `trio.lowlevel.Task` per receiver and reject an
overlapping call with `trio.BusyResourceError`. Include the owner's
task name and repr in the error, then clear ownership in `finally`.
Define each receiver as one logical subscription cursor. Concurrent
consumers must use separate `.subscribe()` receivers so each retains
an independent cursor and lag policy. Strict mode reads every retained
value or raises `Lagged`; `raise_on_lag=False` explicitly permits
drops.
Cover the regression with a checkpoint loop synchronized on
`recv_ready`; explain the interleaving inline, assert the error names
the active consumer, then release the source and prove the original
receive remains usable.
Prompt-IO: ai/prompt-io/opencode/20260828T014247Z_d06ee34b_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Bound `BroadcastState.cancelled` entries to receiver progress,
terminal state and resource lifetime instead of retaining completed
`Task`s indefinitely.
Deats,
- make EOC durable so awakened peers never re-enter a closed source.
- close root broadcasters during explicit `MsgStream` and
`LinkedTaskChannel` teardown without re-entrant EOC closure or
breaking `MsgStream.aclose()` overrides.
- reject non-positive fan-out retention capacity before constructing
an unusable zero-length queue.
- cover child/root cancellation cleanup, terminal peer wakeups,
wrapper teardown, subclass compatibility and zero-buffer rejection.
Prompt-IO: ai/prompt-io/opencode/20260813T181901Z_a2e0df4b_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
`MsgStream.subscribe()` and `LinkedTaskChannel.subscribe()` omitted
`BroadcastReceiver.raise_on_lag`, forcing downstream consumers to
mutate a private receiver attribute when overruns were acceptable.
Add `raise_on_lag` to both public wrappers. The first subscription
sets the irreversible root broadcaster's policy, while every child
selects its own strict or warn/drop/resume behavior independently.
Document both fan-out APIs. Cover policy forwarding plus real IPC
and infected-asyncio paths.
Prompt-IO: ai/prompt-io/opencode/20260812T213117Z_51185487_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Closing any subscriber set the shared `recv_ready` event, even when
another receiver owned the source read. Waiting peers then looped
until an idle source produced another value.
Give each receiver private source-read and peer-wait cancellation
scopes. Closing a waiting peer interrupts only that peer. Closing
the source owner discards post-close source outcomes and then wakes
peers for a clean ownership handoff.
Keep outer task cancellation as `trio.Cancelled`; only explicit
receiver close maps either private scope's cancellation to
`ClosedResourceError`. Assert that scope cancellation implies the
receiver is closed and document the source-owner key check.
Prompt-IO: ai/prompt-io/opencode/20260812T150027Z_c2a6ccef_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Only `EndOfChannel` and direct cancellation woke tasks waiting
behind the subscriber which owned the underlying receive. Any other
failure cleared `BroadcastState.recv_ready` while peers remained
blocked on its unreachable event.
Publish ordinary receive exceptions as terminal broadcast state.
The owner keeps the original failure while peers drain retained
values and then raise `BroadcastReceiveError` from that cause. Also
wake peers on process-control exits without retaining them as state.
Document the public owner/peer contract and cover current, late and
control-flow subscribers with deterministic bounded regressions.
Prompt-IO: ai/prompt-io/opencode/20260812T030608Z_1095e7f7_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
`BroadcastState.subs` stores each receiver's next unread deque
index, but `.statistics()` exposed that index as a queue length. A
caught-up receiver looked correct by accident while every queued
count was one short.
Convert cursors to retained, receivable counts and clamp lagged
receivers to the current queue length. Also avoid deprecated
`trio.Event` truthiness when reporting waiter counts.
Cover caught-up, queued, lagged and real-event states using actual
broadcast sends and receives.
Prompt-IO: ai/prompt-io/opencode/20260812T012324Z_06c4af17_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
`BroadcastReceiver.receive_nowait()` treated `seq` as a deque index
but subtracted `BroadcastState.maxlen` without counting the first
invalid index. A one-slot queue thus claimed it dropped zero values
after its subscriber missed one.
Include that first displaced value in the count. Preserve the
existing Tokio-style reset to the oldest retained item.
Also, cover exact loss reporting and recovery for one- and
three-slot retention windows.
Prompt-IO: ai/prompt-io/opencode/20260811T233833Z_7cbd64ee_prompt_io.md
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Link Tractor's project reference to the immutable canonical skill
and its repository, then show the exact hybrid deployment command.
Also clarify the Prompt-IO record's commit boundaries and PR #510
review direction.
Review: PR #510 (copilot-pull-request-reviewer)
https://github.com/goodboy/tractor/pull/510#pullrequestreview-5046306236
Prompt-IO: ai/prompt-io/opencode/20260822T022526Z_5562fd9a_prompt_io.md
(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
Prevent unsafe harness guidance from sending SIGUSR1 before
`stackscope` installs its handler or using shared-memory scans on
unsupported platforms.
Deats,
- verify `stackscope` in the pytest env before signaling.
- target pytest by default and require a separately verified handler
before signaling subactors.
- keep `tractor-reap --shm*` guidance to Linux and FreeBSD.
- pin staged Prompt-IO provenance to `eb3c99c9` and record the
PR #510 review direction.
Prompt-IO: ai/prompt-io/opencode/20260822T022526Z_5562fd9a_prompt_io.md
(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
Record generated ignore blocks for deployed skills, commands, and
runtime artifacts so direct symlink deployments remain status-clean.
(this commit msg was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
The migration reference overstated cleanup safety and omitted
current environment, platform, debugger and timeout behavior.
- document runtime env overrides and the actual CI matrix
- distinguish interactive debugger capture from hang diagnosis
- describe Trio-aware and SIGALRM timeout guards separately
- expose reaper platform limits and dry-run descendant expansion
- restore exact change-to-test mappings and randomized-port wording
- drop unrelated Taken and broad command ignores from the migration
Prompt-IO: ai/prompt-io/opencode/20260822T022526Z_5562fd9a_prompt_io.md
(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
Move Tractor's project-specific test knowledge into the local harness
reference and let `ai.skillz` own shared execution and safety policy.
Deats,
- document current Python envs, pytest opts, backends, and transports.
- replace broad registry and process cleanup rules with scoped checks.
- record capture, diagnostics, reaper, and expected-outcome caveats.
- ignore canonical Claude and OpenCode links plus Taken runtime state.
(this patch was generated in some part by `opencode` using
`gpt-5.6-sol` (`openai`))
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`))
Handle the child RPC leaf as an early return, then leave the root-only
runtime and recursive one-shot flow unindented. Rename the helper for
that behavior and document why RPC namespace lookup requires it to
remain import-addressable at module scope.
Review: PR #484 (goodboy)
https://github.com/goodboy/tractor/pull/484#discussion_r3859956375
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Point `test_tractor_cancels_aio()`'s anti-hang guard at the original
fix commit and the detailed ria-removal analysis plan.
Review: PR #484 (goodboy)
https://github.com/goodboy/tractor/pull/484#discussion_r3859956368
(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`))
Fix five spelling errors in comments and failure text touched by the
one-shot migration: one `propagate`, one `Daemon` and three `directly`
corrections.
Review: PR #484 (GitHub Copilot)
https://github.com/goodboy/tractor/pull/484#pullrequestreview-5025348921
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))
Add the PR #484 towncrier fragment for removing
`ActorNursery.run_in_actor()`, `Portal.wait_for_result()` and
`Portal.result()`.
Point callers to `to_actor.run()`, `Portal.run()` or
`Portal.open_context()` according to task ownership and dialog shape.
Review: PR #484 (OpenCode)
https://github.com/goodboy/tractor/pull/484#pullrequestreview-5025383596
(this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))