Wire reference
Enough to write a peer: the framing, the shapes, the encodings, the version rule and the failure modes. Three transports, two shapes, two encodings.
Three transports
| transport | framing | for |
|---|---|---|
| argv | one JSON object on stdout, then exit | a program that runs once per call. Nothing to keep alive, and a fresh process knows everything the last one did. |
| pipe | one object per line, newline-delimited | a program held for the session, talking to the one that started it. |
| socket | four bytes of big-endian length, then that many bytes | anything that may knock. Self-delimiting, so a peer that dies mid-frame is caught at the length. |
Two shapes
a call — somebody is waiting {"call": "<verb>", "args": [ … ]} {"ok": true, "family": 1, "n": <count>, "result": [ … ]} an event — nobody is {"event": "<name>", … }
result is always a list, so one call answers with what the function
did. Two ends disagreeing about that fail silently: a client that unpacks reads a
bare-value server as having returned nothing at all, so the bug presents as an empty memory
rather than as an error.
The version rule
family is the revision of the wire a reply is written in. A newer peer is
refused by name; an older one is not. A reply with no family at all is from
before the check and is accepted.
The constant is duplicated in each sibling rather than shared. A crate held in common would be a dependency between repositories, and this family has none.
Two encodings
| JSON | the default. What every peer understands, and what somebody with socat can read. |
| CBOR | the same shape as bytes, for a caller that is not going to read it. |
On a one-shot door, ask with --cbor. On a socket, just send it: the server
answers in whatever it was asked in.
how the encoding is read off a body — no handshake, no setting first non-space byte '{' or '[' → json 0x80..0xBF → cbor (a cbor map or array) anything else → json (so the error message is the useful one)
Refusals
A refused verb is ok: false with exit status zero, and a refusal is a
reply: only the transport failing closes anything. That is what lets a caller tell “it said
no” from “it is not there”.
| fault | the caller loses |
|---|---|
refused | a feature. Carry on. |
unavailable | the sibling. Carry on without it. |
failed | the write. Do not assume it was recorded. |
malformed | nothing yet — but this is a bug to report, not a condition to handle. |
Limits
| frame | 16 MiB on magi's own wire; 1 MiB between sessions. A message between instances is a sentence, not a payload — an unbounded read is a way to make a session allocate until it dies. |
| socket path | 108 bytes, which is the kernel's limit and not anybody's choice. Long temporary directories are how this is usually met. |
| identity | SO_PEERCRED. A caller the kernel will not identify gets nothing. |