ZyrisDocs
Protocol specification

Streams

How streams are born, the transfer mechanisms a tool can declare, credit-based flow control, and how streams end.

A stream is born from a request. The request sender allocates the stream id and declares it in req.stream.id.

  • Ids are u32 and never reused within a connection.
  • The dialer allocates odd ids; the acceptor allocates even ids.

Transfer mechanisms

Each tool declares one in its capability descriptor:

transferBehaviour
unaryreq → (prog)* → res or err. Datums travel inline or via a declared stream.
uni_streamThe caller declares a stream. The res arrives immediately, carrying the typed stream head (metadata such as a file's stat or an initial status). The callee then sends item chunks towards the caller until s_end (optionally with a summary in trailer) or s_err. A failure to start yields a plain err and no chunks.
bi_streamReserved in v1. The frames are defined, but implementations MUST reject tools declaring it.
videoNo websocket data frames. The request establishes a video session and WebRTC signaling follows. See Video.

Chunk payloads are defined per tool. A typed uni_stream encodes exactly one item per chunk in the connection's negotiated serialization — msgpack by default. This is how attacca_api.turn_events frames and file_io.read_stream byte chunks (a bin-encoded item) travel.

MJPEG fallback chunks are [u64 BE timestamp in µs][one complete JPEG].

Flow control

Credit-based, mandatory, and per stream.

  1. At stream birth the receiver implicitly grants initial_stream_credit bytes.
  2. It tops up with additive s_credit { stream, bytes } messages.
  3. The byte sender MUST NOT exceed its outstanding credit.

A violation is a protocol bug: s_err credit_violation followed by connection close 4409. CONTROL frames are never subject to credit.

Why. One websocket multiplexes many streams. Without credit, a slow 2 GiB file sink would block PTY keystrokes and heartbeats behind it. And across a relayed hop there is no end-to-end TCP backpressure, so credit is the only bound on how much a relay buffers.

Termination

  • Clean end — the byte sender sends s_end { stream, trailer? }. The trailer may carry a sha256.
  • Sender failure — s_err.
  • Receiver abort — s_cancel. The sender stops and discards.
  • Implicit close — the final res or err of the owning request closes any stream it declared. Well-behaved senders still send s_end first; the implicit rule covers crashes.
DECLARED ──first chunk──▶ FLOWING ⇄ BLOCKED(credit=0)
    │                        │ s_err / s_cancel
    │ s_cancel               ▼
    ▼                      ENDING ──s_end──▶ CLOSED
  CLOSED ◀── owning req res/err or connection loss (forced)

On this page