Skip to content

Protocols

PicoMQ speaks two client protocols. The Pico protocol is the native API, with all custom headers under the Pico-* prefix. Durable Streams is an open protocol implemented on its exact wire vocabulary, with Stream-* and Producer-* headers. A listener serves one or the other, chosen in the node's configuration, and both are thin translations over the same stream service. Nothing in the storage or metadata layers knows which protocol a record arrived through.

The resource model

The deliberate choice here is that a stream is just a URL and the standard methods keep their usual meaning: PUT creates, POST appends, GET reads, DELETE removes. There is no session, no handshake, and no client library required, so anything that can issue an HTTP request is a full client. The exact endpoints and status codes are in the API reference.

The second choice is that all position state travels in response headers: the next offset, whether the reader is at the tail, a cursor for resuming. The server keeps nothing about its consumers, so a reader can disappear for a week, come back with its last offset, and continue. This is also what makes reads through redirects and transfers safe, since any node can answer from just the request.

Records on the wire

Every record is stored as an envelope so nothing about it is lost between protocols.

versionu8timestampi64, msheaderscount, then name and value pairsbodythe record bytesone record, one envelopelength-prefixed fields, no padding

The envelope is the reason the two protocols can share storage. A record appended through one protocol reads back through the other with its timestamp and metadata intact, because the stored form belongs to neither. The timestamp is assigned by the owning node and is monotonic per stream, so equal wall-clock readings still order correctly. Headers are the record's own key-value metadata, distinct from HTTP headers.

Appends come in three shapes, a single body, a JSON batch, and a binary batch, each with its own content type. Records in a batch are ordered under the stream's gate together, so they always occupy consecutive offsets, and the append is acknowledged once the whole batch is durable.

Producers

Exactly-once appends over HTTP need the server to remember, because a client that times out cannot know whether its write landed. Both protocols solve this the same way: a producer identifies itself with an id, an epoch, and a per-record sequence number, the server accepts each sequence once, acknowledges repeats without writing, and rejects stale epochs. A mismatch response includes what the server expected next to what it received, so a producer can tell a lost acknowledgement from a real gap. The state behind this is in the stream's registry entry, described in Streams, and survives restarts and transfers.

Routing at the edge

Both frontends share one routing step in front of every handler, so the protocol code never thinks about ownership. Creates are always served locally, since create is what places a stream in the first place, and everything else follows the decision table in Ownership and routing, redirecting to the owner when the stream is served elsewhere. A routing failure returns an error rather than a guess.

SSE connections are capped at 55 seconds and long polls at 25 by default, both below common proxy idle timeouts, so intermediaries see regular traffic instead of connections worth killing. Clients resume from their last offset or cursor and lose nothing across reconnects.