Step 1 of nescore's 2026-10-16 media timing spec. Media carried no send time and audio no time at all, so every layer had to infer lateness from gaps between arrivals. - clock: one sender clock, CLOCK_MONOTONIC in u64 microseconds, shared by every process that stamps media. Off unix the client uses its own process clock, which is all a receiver needs. - Datagram headers (and PyroWave's) carry sent_us, stamped by neshub at hand-off to QUIC. Keyframe streams open with it too. - Video carries captured_us (from nescapture's present time) in place of milliseconds since the encoder started. Audio carries its first sample's position on the PipeWire graph clock, which neswire already read to fill missed cycles. The IPC stamp is a u64 carrying either. - delay: microseconds. The baseline no longer expires on a 30 s timer: clock skew is measured as the least-squares slope of each second's minimum one-way delay, and only a path change resets the baseline. observe() returns each unit's queueing delay. - ReceiverReport carries video and audio queueing delay summaries for the rate control that will consume them. - Shared encode/decode helpers for video and audio payloads, so the hub and receivers stop hand-building byte offsets. No compatibility kept. STREAM_VERSION is 4. nescore needs a pin and the client side (step 2) before it can talk to this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
neshub
One connection out of the box.
Everything inside the guest that produces or consumes a stream talks to neshub over a Unix socket. neshub muxes it all into a single iroh QUIC endpoint and fans client input back the other way. A client dials that endpoint with a ticket and gets video, audio, cursor and stats on it.
The sockets
| socket | default | direction | carries |
|---|---|---|---|
--video-ipc |
/tmp/nestri-video.sock |
nescapture → neshub | encoded video frames |
--audio-ipc |
/tmp/nestri-audio.sock |
neswire → neshub | Opus packets |
--input-ipc |
/tmp/nestri-input.sock |
neshub ↔ nescope | input out, cursor and stats back |
--stats-ipc |
/tmp/nestri-stats.sock |
nescapture → neshub | encoder stats |
--screenshot-ipc |
/tmp/nestri-screenshot.sock |
neshub → nescope | a picture of the screen, on request |
--ticket-ipc |
/tmp/nestri-ticket.sock |
neshub → nesinit | the ticket, once |
| — | /tmp/nescapture-cmd.sock |
neshub → nescapture | IDR requests, encode settings |
neshub is the listener on every one of them and the other side dials in. That is deliberate: it removes the startup ordering problem entirely, since a producer that is not running yet simply has not connected yet.
The ticket
nestri:<base64 of {endpoint_addr, stream_name}>
Generated once per boot, when the endpoint binds. It is served on a socket
rather than printed because stdout here is a log file inside a virtual machine
and the person who needs it is outside one — nesinit reads it and carries it
to the host.
What it does not do
neshub does not start the payload, know its name, or decide when the box is
finished — nesinit owns all three, and shuts the VM down around this process.
The same neshub binary serves a game, a desktop, or anything else that draws to
nescope, because it never learns which one it is looking at.
Running it
cargo run --bin neshub # every socket at its default
cargo run --bin neshub -- --relay none # direct connections only
RUST_LOG=neshub=debug cargo run --bin neshub