Skip to Content
BlogVein Format: A Deep Dive

Vein Format: A Deep Dive

March 14, 2026

Vein is the binary wire format at the heart of LODE. This post walks through the byte-level structure, explains the design decisions, and shows how determinism is achieved.

The Problem with JSON

JSON is human-readable, universally supported, and completely non-deterministic. The same object can be serialized in countless ways:

{"a":1,"b":2} {"b":2,"a":1} { "a" : 1, "b" : 2 }

All three are semantically identical, but produce different byte sequences — and therefore different hashes. This makes JSON unsuitable for content-addressable systems where the hash is the identity.

Vein’s Answer

Vein solves this with three constraints:

  1. Field order follows the schema — not the source object’s key order
  2. Map keys are sorted — lexicographic ordering before encoding
  3. Fixed-width length prefixes — big-endian uint16, no variable-length encoding

The result: given the same source object and schema, compile() always produces the identical Uint8Array. Byte-for-byte.

Binary Layout

Every Vein payload starts with a 4-byte header:

BytePurpose
00x4C — ‘L’ (magic)
10x44 — ‘D’ (magic)
2Schema version
3Field count

Fields follow immediately. Each field is:

BytesPurpose
1Field ID (1-255)
1Type tag
2Data length (big-endian)
NEncoded data

Size Comparison

For a typical model layer definition with 5 fields:

FormatBytes
JSON (pretty)~350
JSON (compact)~220
Vein~90

Vein achieves roughly 60% compression over compact JSON — not through clever algorithms, but by eliminating redundancy. No field names in the payload. No quote characters. No colons or commas. Just type-tagged data keyed by numeric IDs.

Forward Compatibility

When the decoder encounters an unknown field ID, it reads the 2-byte length prefix and skips that many bytes. No error. No crash. The field simply doesn’t appear in the decoded object.

This means a v1 consumer can safely read a v2 payload. New fields are invisible to old decoders. This is the same strategy Protocol Buffers uses — and it works just as well in a 150-line TypeScript implementation.

Read More

Last updated on