Cutting a Payload 94% With Custom Serialization Patterns

6 min readYaseen Khatib · AI Systems Architect

On IntegrateX's React Flow automation canvas, the agent-graph payload was quietly becoming the bottleneck — so I cut it by 94% without losing a node, edge, or coordinate. The win wasn't gzip — it was a Serialization Adapter, the bridge in the pattern I call Trinity Architecture, shaped around what the data actually is: a known schema, not prose. Once you stop shipping generic JSON and serialize the structure, the redundant bytes evaporate.

Generic JSON pays for the same keys over and over

A graph of 400 nodes serialized as idiomatic JSON repeats the strings "position", "data", "type", "sourceHandle" four hundred times. It ships default values that never changed. It encodes absolute coordinates that mostly differ from their neighbours by a few pixels. Every one of those is a byte you're paying to transmit, parse, and re-transmit — and none of them carry information. Multiply that by WebSocket hops, JSON.parse cost, diffing, and render churn, and you're burning budget for zero signal.

verbose JSON100% · repeated keys + defaultsdedupe keysdrop defaultsdelta-encodeschema-aware encoding6% — a 94% reduction, lossless
The 94% came from designing the format around the data's structure — shared schema, omitted defaults, delta-encoded positions — not from gzipping verbose JSON after the fact.

Design the format around the data, not the other way around

The reduction came from three schema-aware moves, each lossless. Dedupe the keys: the schema is known, so field names live in one header, not on every record. Drop the defaults: if a node's type is the common case, omit it and reconstruct on read. Delta-encode the positions: store each coordinate as an offset from the last, so a tidy layout compresses to near-nothing. In my Trinity split, the Serialization Adapter does that work and hands the orchestrator lean buffers; the UI stays declarative and dumb by design. gzip on top of that is gravy; gzip on top of verbose JSON is lipstick.

serialize.ts
// before: 1 record = repeated keys + defaults + absolute coords
{ "id": "n12", "type": "agent", "position": { "x": 740, "y": 320 }, ... }

// after: columnar header + omitted defaults + delta coords
schema: ["id", "type?", "dx", "dy"]   // keys declared once
rows:   [["n12", , 12, 8], ...]       // type omitted = default; coords are deltas

Payload is a budget — measure it like latency

The reason this is worth doing isn't bytes for their own sake. Payload size is a budget that buys you faster loads, cheaper bandwidth, and smaller diffs over the wire on every interaction. On a real-time canvas with live node execution, the payload is the latency and it drives backpressure. On IntegrateX (and on streamerOS under 60fps constraints), tracking payload beside FPS and RTT kept the socket steady, reconciliation snappy, and render thrash down. Treat it as a first-class metric and the 94% is just what falls out of taking it seriously.

Compression isn't a bolt-on. The biggest wins come from encoding the data as what it is — structure with a schema — long before gzip ever sees it.

A small payload is half of feeling instant; the other half is streaming. This pattern grew out of the agent orchestration canvas and fits cleanly in my Trinity Architecture boundary: presentation renders, orchestration decides, the adapter serializes — one of the load-bearing ideas behind the five products I shipped solo this year, and the judgment I bring is designing the wire format before reaching for a compression library. Continue on the roadmap.

Need an engineer who can build this?

I'm Yaseen Khatib — a Senior Full-Stack AI Engineer (MERN + TypeScript) who ships production AI systems solo. Open to senior and lead roles, remote or on-site.