The CXDB Go client was 1,200 lines of clean, idiomatic Go. It handled connection management, binary protocol framing, turn creation, branch forking, blob storage and retrieval, and type-safe projections through Msgpack deserialization. It had been battle-tested against the Rust server through thousands of scenario runs. It worked.
Now they needed it in TypeScript.
The React UI—6,700 lines of TypeScript and Next.js—talked to the Go gateway over HTTP. But there was a growing need for direct client access from TypeScript tooling. Build scripts. Test harnesses. Agent scaffolding. Things that didn't want to go through the gateway. Things that wanted to speak the binary protocol on port 9009 directly.
Navan took the lead. He'd been thinking about semporting since Jay had named the technique, turning the concept over in his mind, sketching notes in his physical notebook with a mechanical pencil. He'd drawn a diagram: two trees with the same root structure but different leaves. Same branching pattern, different foliage. That was a semport.
He started by writing the semantic document. Not the Go code. The meaning of the Go code. What the client did. What invariants it maintained. What the binary protocol frames looked like on the wire. What happened when a connection dropped mid-turn. What happened when a branch fork collided with a concurrent blob write. Every behavior, every edge case, every guarantee.
The document was fourteen pages. Navan spent two days on it.
Then he gave it to an agent with a single instruction: implement this client in idiomatic TypeScript. Do not reference the Go source. Do not transliterate Go idioms. Write it the way a TypeScript developer would write it from scratch, knowing everything in this document.
The agent produced code that used async/await instead of goroutines. Typed arrays and DataViews for binary protocol framing instead of Go's byte slices and binary.Read. Zod schemas for type-safe projections instead of struct tags and reflection. EventEmitters for connection lifecycle events instead of channels. A class hierarchy that felt natural in TypeScript, not a port of Go interfaces.
Navan ran the scenarios. The TypeScript client talked to the Rust server on port 9009. Turns were created. Branches were forked. Blobs were stored and retrieved with BLAKE3 content addressing. Projections came back typed and validated.
Every scenario passed.
Jay reviewed the TypeScript output, reading through it slowly. "I can't tell this was ported from Go," he said.
"It wasn't," Navan replied. "It was reimplemented from semantics. The Go client and this TypeScript client are siblings, not parent and child. They share the same specification DNA. Neither one is derived from the other."
Justin, who had been listening from across the room, smiled. "That's the cleanest description of a semport I've heard."
Navan wrote it down in his notebook. Siblings, not parent and child. The mechanical pencil scratched softly against the paper. Another leaf on the same tree.
Zod schemas instead of Go struct tags. EventEmitters instead of channels. This is what idiomatic means. Every ported codebase I've worked in had Go-isms leaking through. A semport avoids that entirely.