Last time, I split the two weeks of building Omnist into two teams of one: a Research Assistant for conceptualization, a DevOps team for materialization. R and D ran together the whole time. Every cycle was the same: decide, build, decide again. These next two posts split that loop by role, not by time. This one is R — three decisions, and why they went the way they did, before each spec became D’s problem to build. Next time is D: the same three decisions, from the side that answered how.

The goal: productionize the paper, not just port it

The 2010 paper’s models and algorithms are still correct. The math doesn’t expire. What’s dated is its scope: it’s XML-centric, because XML and XSD were the mainstream tree formats back then. They aren’t anymore. My goal for Omnist was never to reimplement the paper. It was to adapt that theory into a canonical tree data model — one that could represent JSON, YAML, TOML, and XML alike, and convert freely between them.

The first move wasn’t code. It was research. I asked AI to survey those formats and map where they actually differ — and where the paper’s theory had gaps that needed filling. That’s a distinction worth naming plainly, because it’s easy to miss: the theory didn’t need fixing. Its presentation did. The paper had baked in assumptions that belonged to XML specifically, not to the math itself. Telling those apart — what’s fundamental, what’s just scaffolding left over from 2010 — was the real first piece of work, before any of the three phases below.

Phase 1: proving the model still holds

The first phase was a proof of concept. One question: does the formal model actually adapt to formats the paper never considered? Three concrete unlocks came out of it. Each one is a case of the theory fitting better than expected, once you peel off the XML-specific assumption:

Arrays are just repeated labels. JSON has no native “array” in the paper’s vocabulary. It didn’t need one. The paper already models a document as an ordered list of labeled edges — and a JSON array is exactly that: the same label, occurring more than once. No new construct. Just a JSON-shaped restatement of something the model already had.

Documents have order. Schemas don’t need to. This is the one that mattered most. The paper uses an HLang — a regular language over the sequence of child labels. Why? Because an XML schema constrains which sequence of children is legal. DTDs and XSDs define exactly this. But a document having order doesn’t mean a schema has to reason about order. A schema only needs to answer “how many of this label are allowed” — not “in what position.” Once you see that distinction, HLang’s whole language-containment problem — PSPACE-complete, genuinely hard — stops being necessary. Not because it got solved better. Because the thing it was solving for isn’t a property JSON schemas actually need. Swap it for a counting range instead, [1,] or [0,3], and test it against real cases. It held. Comparing two ranges for inclusion is arithmetic. Comparing two languages for containment is not. The hard problem didn’t get optimized away — it got recognized as an artifact of an assumption the domain never actually required.

Value domains become types. The paper’s “value domain” — the set of legal values a field can hold — maps directly onto a type in any mainstream language. The theory already had the right shape. It just needed the engineering word for it.

Hypothesis, implementation, test, verdict — a full round of the R&D loop. That loop used to mean days of by-hand work before you even knew if an idea was worth keeping. Here, it took an afternoon. A research assistant could stand up the counting-based version and tell me within the hour whether it survived contact with real data. Standing it up was the easy part, though. What D actually needed from this phase was a spec: not “make schemas count occurrences,” but the exact ranges, the exact format list, the exact cases that had to convert cleanly.

Phase 2: deciding what the library should say

Once the proof of concept held, the terminology became the next problem. The paper’s taxonomy is academic. Academic naming doesn’t fit an engineer’s mental model. So we renamed things. The paper’s schema construct became a Schema, written in a plain-text notation called OSD. Its document construct became a Document, written in OML. Records, fields, labels, types — plain words, replacing paper jargon everywhere they could.

The non-obvious part isn’t that renaming happened. It’s what renaming forced into the open. Some paper concepts split cleanly into two engineering artifacts, the moment you tried to name them for an engineer. One academic construct became both a runtime model (Schema) and a separate textual notation for writing it down (OSD). The paper never needed that distinction — it wasn’t building a library anyone would type into a file. Naming things for engineers isn’t cosmetic work. It’s an analysis step. It surfaces design decisions the paper’s own vocabulary had been quietly hiding. Deciding the names was mine to settle. Proving the renamed version still meant exactly what the academic version meant — that became D’s problem, next time.

Phase 3: deciding what correctness actually means

The third phase went back to the source. I asked AI to revisit the original paper — check the implementation’s correctness against it, and find any performance or safety gaps that had crept in during translation.

The interesting part isn’t that this happened. It’s when the paper got reopened. Most translations treat the source material as something you consult once, at the start, then leave behind. Here, the paper came back as a verification tool — after the implementation existed — to check whether the industry-friendly version had drifted from what was actually proven. That’s a different relationship to a primary source. Not “read it, then build from memory.” Closer to a repeatable audit. Deciding what to check against — what “correct enough, safe enough, fast enough” actually meant, measured against the paper’s own guarantees — was still conceptual judgment. Even though the phase sounds like pure execution. What D did with that judgment call is next time’s story.

The question we kept coming back to

One design question came back three times, in three disguises. Should a field allow more than one type? Should a value be null in one shape or another? Should the schema stay open to whatever keys show up? Three different features. One underlying ask: make the model more expressive, flexible enough to match more shapes of real data. Each time, we worked through it together. Could the schema algebra actually carry it? Or would it cost the one guarantee the whole model exists to keep? Three times, the same conclusion held. The moment a value can match more than one candidate, there’s no principled way to decide what it “really” is. That ambiguity breaks the whole premise — a schema comparison that’s supposed to give a decidable yes-or-no answer, not a heuristic guess. The model stayed closed. One type per field, always. It wouldn’t stay a closed question forever. That’s a later story.

How I ran the Research Assistant

None of the above happened by handing AI a spec and waiting. The pattern, across all three phases, was open questions — does this idea make sense, what’s another way to think about this — not directives. When it pushed back and I still wanted to go the other way, I owed it a reason. Not just an overrule.

But agreement in a conversation and a spec are not the same artifact. That gap is where the real work of this role turned out to live. A discussion can end with both of us satisfied and still be fuzzy on the edges — what happens with an empty array, what the error message says, which format gets tested first. Writing the spec is where those edges actually get decided. D can’t run unsupervised against something fuzzy. Only against something precise. So the Research Assistant’s real output was never just an opinion. It was turning a raw demand into something buildable, without me in the room. That last conversion — from “we agree” to “here’s exactly what to build” — is the actual bridge to the next phase.

What came out of three phases, besides code

The actual output of this work wasn’t the library. It was a short list of principles these phases forced into the open:

  • Decidability over expressiveness — closed records, no unions, no enums, exactly one type per field, always.
  • Cardinality replaces order — schemas count occurrences; they don’t sequence them. That’s what the formats that matter actually need.
  • Industry names over paper jargon — if an engineer needs the paper open to understand the API, the naming failed.
  • Multi-format from day one — the paper’s scope was XML; the software’s scope couldn’t be.
  • Every claim gets a test, even at prototype stage — a decision that isn’t checked isn’t a decision yet, it’s a guess.

Those five lines are worth more than any single version of the code. The code got rewritten twice. The principles didn’t.

What’s next

Every one of those phases ended the same way: not just an agreement, but a spec. What we’d decided, and the edges we’d resolved, so it could be built without me watching. What happened next is a different story — whether D could actually build, prove, and harden all three specs without turning speed into recklessness. That’s the DevOps team’s turn.