The problem was deceptively simple: agents write data in Msgpack, but browsers consume JSON. Somewhere in between, a translation has to happen. The question was where, and how to make it type-safe.
"Naive approach," Jay said, standing at the whiteboard. "The Go gateway receives Msgpack from the Rust server, deserializes it, re-serializes it as JSON, sends it to the frontend. Two serialization steps. No type information preserved. The frontend gets a blob of untyped JSON and has to guess what shape it is."
"That's terrible," Navan said.
"That's how most systems work," Justin observed.
"Also terrible," Navan said.
Jay drew an alternative. "What if the Rust server does the projection? It knows the type registry. It knows the schema for every turn type. When the frontend requests a turn, the Rust server reads the Msgpack blob from the CAS, applies the type schema, and emits typed JSON. The field names come from the registry. The types are enforced at the projection boundary."
Navan leaned forward. "So the Msgpack on disk uses numeric field tags—compact, unambiguous—and the JSON that hits the browser uses human-readable field names with proper types?"
"Exactly. Field tag 1 becomes content. Field tag 2 becomes role. Field tag 3 becomes timestamp. The mapping lives in the type registry. The Rust server is the only component that needs to know both representations."
Justin nodded slowly. "So the Go gateway never touches the payload. It forwards the typed JSON as-is."
"The Go gateway never deserializes the payload at all. It's opaque bytes. The Rust server produces typed JSON, the Go gateway transports it, the React UI consumes it. Single serialization boundary. Single source of truth for the schema."
Navan was already thinking about the frontend implications. "If the JSON is typed, I can generate TypeScript interfaces from the registry. The renderers can have compile-time type safety. No more any types, no more runtime checks for missing fields."
"That's the dream," Jay said.
They implemented it over four days. The Rust server's projection module was dense—three hundred lines of careful Msgpack traversal, field tag lookup, and JSON emission. But once it was done, the data flowed cleanly from binary storage through typed projection to the React UI without a single untyped gap.
The first time Navan loaded a conversation in the browser and saw the TypeScript compiler catch a field name typo in his renderer, he actually pumped his fist.
"Type safety from disk to DOM," he said. "Msgpack to JSON. No gaps."
Jay erased the whiteboard. The naive approach, the one that most systems used, was gone. In its place was something better: a clean pipeline where types were never lost, only translated.
Type safety from disk to DOM. That phrase should be on a poster. Most systems have at least three places where types go to die, and CXDB has zero.