ZyrisDocs
Protocol specification

Datums and blobs

Text, file and image values, inline blobs, and attachments that move large bytes over a stream.

Datums

Three kinds of datum cross the wire as values inside params, result or stream items:

{ "kind": "text",  "text": "…", "format": "markdown" }
{ "kind": "file",  "filename": "report.pdf", "description": "Q3 export",
  "media_type": "application/pdf", "blob": <blob> }
{ "kind": "image", "name": "screen.png", "description": "current display",
  "media_type": "image/png", "blob": <blob> }

Blobs

A blob is either inline or an attachment.

Inline

Allowed only up to 512 KiB: a msgpack bin, or a base64 string under negotiated JSON — one reason msgpack is the default.

The ceiling comes from the far end rather than the wire. A deployment that measures a result as JSON pays base64's four-for-three, so 512 KiB arrives as 699,052 bytes; Attacca's result limit defaults to 1,000,000 bytes.

Attachment

Metadata in place of the bytes:

{ "attachment": { "stream": 42, "size": 48293812, "sha256": "…", "offset": 0 } }

The bytes follow as STREAM_DATA chunks on the referenced stream, and s_end.trailer.sha256 gives end-to-end integrity. An interrupted transfer resumes by re-issuing the call with offset.

When attachments are used

Attachments are negotiated: both peers must advertise the attachments feature in the handshake. Against a peer that did not, blobs stay inline whatever their size.

Where they are negotiated, the conversion is automatic and by size. A responder detaches every blob over 4 MiB as it writes the response:

  1. It allocates a stream from its own id parity — the byte sender allocates, so no req.stream.id is involved.
  2. It writes the attachment reference in place of the bytes and sends the res.
  3. It pushes the bytes as STREAM_DATA chunks of at most max_chunk, paced by the receiver's credit.
  4. s_end carries the digest, which the reference also names, so the receiver can check length and content without having trusted either end alone.

The receiver opens the stream when it decodes the payload, not when its caller gets around to reading it — chunks are already in flight by then. An announced attachment nobody claims within two minutes is cancelled, because an unclaimed stream otherwise holds its sender at the credit ceiling for the life of the connection.

Implementation status

Implemented: inline blobs, chunked uni_streams (which cover bulk file transfer through file_io.read_stream and write_at), and attachments in responses. Attachments in request parameters — a large datum travelling towards the callee, as file_io.write would want — are the same mechanism mirrored and are not implemented yet.

On this page