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
u32and 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:
transfer | Behaviour |
|---|---|
unary | req → (prog)* → res or err. Datums travel inline or via a declared stream. |
uni_stream | The 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_stream | Reserved in v1. The frames are defined, but implementations MUST reject tools declaring it. |
video | No 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.
- At stream birth the receiver implicitly grants
initial_stream_creditbytes. - It tops up with additive
s_credit { stream, bytes }messages. - 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 asha256. - Sender failure —
s_err. - Receiver abort —
s_cancel. The sender stops and discards. - Implicit close — the final
resorerrof the owning request closes any stream it declared. Well-behaved senders still sends_endfirst; 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)