Back to list
Skywire Development Update — September 6, 2026

Skywire Development Update — September 6, 2026

The day’s main thread was the deployment’s periodic chatter moving off dmsg handshakes and onto CXO: service-discovery registrations gained a publisher and an aggregator, the address resolver gained a readable feed of its own, and a CXO facility implemented end to end and never called anywhere in skywire finally got a caller. The second thread was a correction — a fix retaining hypervisors across a config regeneration was merged and reverted the same day, for making one setting out of forty-five authoritative in the wrong file. And a 39% discrepancy in the transport discovery turned out to be two endpoints read at different moments rather than an index losing edges.

Skywire: Registrations That Stop Re-Handshaking

Every visor re-POSTs its service-discovery entries every ninety seconds, and each POST is a fresh dmsg stream with a full Noise handshake — the responder that dominates the discovery service’s CPU. 4585 adds the publishing half of the alternative: the visor mirrors its live entry set onto a CXO feed and announces every thirty seconds so the discovery subscribes back. Entries pass verbatim through a new sink each discovery client hands its accepted entry to, so the feed holds the visor’s whole set rather than one app’s, batched into a single leaf — which keeps the Root at two objects however many apps register. Deregistration is absence from the next batch.

4588 completes the path on the server side, making the service discovery the fourth user of the shared aggregator core. Its ingest is no-clobber: an existing entry — the common case, since the authoritative HTTP POST registered it first — has its TTL refreshed off the stored record rather than overwritten, and a fresh insert is taken only for non-visor types, since a visor registration is gated on a remote address the CXO path cannot see. Whether any of this runs was itself hard to answer: 4583 found the address resolver logging no aggregator activity at all while holding 464 live CXO connections, because the successful-subscribe line sat at Debug while the AR runs at info. It is the one line separating working from silently doing nothing.

Skywire: A Feed You Can Look One Key Up In

The address resolver aggregates visor bind feeds but published nothing back, so the roughly 1,500 warm connections it holds carried traffic one way only. 4586 adds the read side: a feed carrying every binding the AR holds, keyed so one peer can be looked up without pulling the rest. Its shape follows from measurement — children are fetched one object per request, and the reference container has degree sixteen, so a wider level makes an indexed fetch walk the branch nodes ahead of it. Two sixteen-wide levels, every bucket always published so both stay dense, keep a lookup at a fixed seven fetches at any network size.

One detail is easy to get wrong and was got wrong first: the bucket is named from the second byte of the hex key, not the first. A skywire public key is a compressed secp256k1 point, so its leading byte is always 0x02 or 0x03 — measured against the 1,711 peers the production AR held that day, the first byte gives two buckets of 886 and 825, while the second gives 255 occupied buckets with a maximum occupancy of 17. Freshness and correctness come from different sources: a decorator marks a binding dirty on every bind and unbind, while a five-minute resync rebuilds from the per-type index, the only thing that drops a binding expired by TTL.

Skywire: Reading a Feed Without Holding It

CXO’s preview mechanism fetches a peer’s latest Root over an already-open connection, serves its objects on demand, and subscribes only if the callback says so; it had no caller anywhere in skywire. 4594 adds one — a bounded point read that always declines the subscription — and a consumer resolving a single peer’s transport addresses out of the bindings feed. Measured live over forty lookups, preview ran at a 16.4 ms median for seven objects and 2.2 KB per lookup with nothing resident, against 140.3 ms for the HTTP resolve over dmsg and 119 µs for a warm subscription costing 100 KB resident and 0.8 seconds of cold sync. It buys latency over HTTP and memory over subscription and wins outright over neither, so it is for a caller that cannot hold a feed at all: a browser tab, a microcontroller, a short-lived CLI run.

Two failure modes turned up live. A preview reader is idle by construction, so the node’s watchdog reaps its connection and the next lookup lands on a corpse; and a preview pins a Root a republishing publisher can collect mid-walk, indistinguishable from an absent child — a roughly 1.7% false-miss rate, now re-read up to three times. 4596 then found a defect negating the point of all of it: the object read guarded its local lookup with a condition that does not fire when the lookup succeeds, so a local hit fell through to the remote getter exactly like a miss, paying a round trip for a value already in hand.

Skywire: One Snapshot Disagreeing With Itself

The reward server’s transports-per-visor histogram had been refusing to draw, reporting a per-key index summing to 13,298 edges against 10,892 transports where 21,784 were expected — 39.0% apart. The index was not losing edges. 4595 establishes that both endpoints are reductions of one cached slice, so read off a single snapshot the per-key sum is exactly twice the aggregate; paired live fetches came back at exactly 2:1 in twenty of twenty samples while the total itself moved from 8,089 to 13,871 over seven minutes. What differed was when: the gate compared a live per-key body against the aggregate on the CXO stats feed, which is deliberately biased late and high. With the total moving 2.4x inside forty seconds, the difference between those reads is whatever the network did in between. Each response now carries the snapshot it was served from, and the per-key sum is compared against the total of that same snapshot — one snapshot disagreeing with itself is a real defect and nothing else.

Skywire: A Fix, Its Revert, and What the Config Promises

4590 fixed something real. cli visor hv add is documented as persisted, and it is — until the next update, because autoconfig regenerates the config without retaining the existing hypervisor list, so a hypervisor attached at runtime is silently dropped and the dmsgpty whitelist with it. This was observed, not inferred: while adding two hypervisors across 24 visors, a fleet update landed mid-rollout and every visor that restarted came back with its list reset.

4591 reverts it the same day. /etc/skywire.conf is the source of truth — autoconfig writes the hypervisor keys there and generates the json from it — so a regeneration discarding state that only ever existed in the derived artifact is the design working as intended. Retaining the list made the json partially authoritative for one setting out of the 45 runtime setters the visor API exposes, which fixes nothing general and makes the preservation rule more arbitrary rather than less. What shipped instead, in 4593, is help text and doc comments: each corrected string now says which of three lifetimes it has — runtime-only, written to the json, or declared in skywire.conf.

Skywire: Clicks That Fell Through the Window

The hypervisor UI was unclickable in the browser — the Angular app rendered, polled and updated, and never received a click. 4582 traces it to the desk’s window root, a viewport-spanning fixed overlay with pointer-events: none so clicks fall through wherever no window covers the page. The taskbar and menu items set the property back to auto inline; windows set nothing, so the whole subtree, iframe included, inherited none. Hit-testing the centre of a maximized window returned the document body; with the fix, the iframe. 4597 fixes a segfault in the direct-TCP pty host, which panicked as soon as its first session ended: it is built with no dmsg client, but the logging helper read that field unguarded and the teardown goroutine calls it on cancellation.

Two CI gates were stabilized. 4580 replaces a flat five-by-100ms retry in the dmsgcurl download test with eight attempts of exponential backoff capped at two seconds, after it failed on two consecutive develop commits while passing six of six locally and eight of eight under the race detector, with handshake and broken-pipe errors pointing at runner contention rather than a regression. 4581 fixes two pre-existing bugs behind intermittent Windows failures: a data race where one test’s leaked keepalive loop read package variables the next test’s harness was shrinking, and a churn test missing a fixed ten-second deadline with only 81 events to drain, where extending the deadline would have hidden the lost-delete hazard the test exists to catch. 4587 updates the 0magnet dependencies, most consequentially a browser-filesystem change where writing to an excluded path no longer schedules a snapshot.