<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Thomas Lee</title>
    <description>The latest articles on DEV Community by Thomas Lee (@tomlee).</description>
    <link>https://dev.to/tomlee</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4025134%2F8985b855-e470-402b-96ca-619af3b61fc0.png</url>
      <title>DEV Community: Thomas Lee</title>
      <link>https://dev.to/tomlee</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tomlee"/>
    <language>en</language>
    <item>
      <title>A Team of One, Running Two Teams</title>
      <dc:creator>Thomas Lee</dc:creator>
      <pubDate>Tue, 21 Jul 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/tomlee/a-team-of-one-running-two-teams-f93</link>
      <guid>https://dev.to/tomlee/a-team-of-one-running-two-teams-f93</guid>
      <description>&lt;p&gt;The initial implementation of &lt;a href="https://omnist.dev" rel="noopener noreferrer"&gt;Omnist&lt;/a&gt;, a tested, &lt;a href="https://github.com/omnist-dev/omnist" rel="noopener noreferrer"&gt;open-source Python library&lt;/a&gt;, came together from a &lt;a href="https://github.com/omnist-dev/omnist/blob/master/docs/paper/Lee-Cheung-2010-XML-Schema-Computations-CIKM.pdf" rel="noopener noreferrer"&gt;16-year-old PhD paper&lt;/a&gt; in &lt;a href="https://lee.yt/posts/omnist-personal-open-source-project/" rel="noopener noreferrer"&gt;two weeks&lt;/a&gt; — one person, part-time. That fact tends to get read as "AI writes code fast." It doesn't quite explain it. What actually happened: I ran two teams during those two weeks, not one. I was the only human on either of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two layers, two teams
&lt;/h2&gt;

&lt;p&gt;Split any R&amp;amp;D project into two layers. &lt;strong&gt;Conceptualization&lt;/strong&gt;: interpreting the idea, deciding what's true, choosing what to build, naming things so they make sense. &lt;strong&gt;Materialization&lt;/strong&gt;: turning a decision into working code, a test that proves it, a release someone can actually install.&lt;/p&gt;

&lt;p&gt;Traditionally those two layers need different people: a researcher, and an engineering team. The handoff between them is where projects lose time. I didn't have either team. What I had was AI — doing both jobs, but never both at once, and never under the same rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Research Assistant and DevOps
&lt;/h2&gt;

&lt;p&gt;For conceptualization, AI was my &lt;strong&gt;Research Assistant&lt;/strong&gt; — the one I argued with about whether an idea actually held up, before a line of implementation existed. Its whole value depended on one rule: it had to be allowed to disagree with me. A research assistant that agrees with everything is a mirror, not a second opinion. A mirror wasn't going to tell me when a design was wrong.&lt;/p&gt;

&lt;p&gt;That disagreement ran both directions. It started upstream of the disagreement itself. I mostly wasn't handing it a spec and asking it to build to it. The actual questions looked more like &lt;em&gt;does this idea make sense&lt;/em&gt; or &lt;em&gt;what's another way to think about this&lt;/em&gt; — open, not directive. When it pushed back and I still wanted to go the other way, I owed it a reason, not just an overrule. A disagreement rule with no obligation on my side to explain myself would have just been theater.&lt;/p&gt;

&lt;p&gt;For materialization, AI was my &lt;strong&gt;DevOps&lt;/strong&gt; team. It turned a settled decision into tested, documented, shippable software, held to the same practices a professional engineering team would use. It ran the release process underneath it. Its whole value depended on the opposite rule: the agent that built something never verified it. A different agent got a specific, separate job: check the change against the spec for correctness, and check it against explicit performance targets, before anything merged. That check was always about &lt;em&gt;how&lt;/em&gt; — does this match the spec. It never re-opened &lt;em&gt;why&lt;/em&gt; the spec asked for it that way. That question had already been settled. Settling it was never DevOps's job.&lt;/p&gt;

&lt;p&gt;Same underlying tool. Two jobs. Two different rules for when to trust it. "Trustworthy" means something different depending on whether you're being asked a question or being handed a result. Put plainly: Research Assistant answered why and what. DevOps answered how.&lt;/p&gt;

&lt;h2&gt;
  
  
  Naming it out loud
&lt;/h2&gt;

&lt;p&gt;This wasn't a label I applied afterward, looking back at what happened. I told AI explicitly, upfront: these are the two roles, here's how each one is allowed to behave. And before acting on anything I ask, say which hat you're wearing. A design question got answered wearing the Research Assistant hat, argument included. A request to actually build or ship something got executed wearing the DevOps hat, verification included.&lt;/p&gt;

&lt;p&gt;That small discipline turned out to catch more than I expected. When a request didn't cleanly fit either hat, the mismatch showed up immediately — there was no hat that fit. That happened when I'd asked for a decision but only materialization was in scope. Or when I'd asked for an implementation while the real question underneath was still unsettled. More than once, the fastest way to notice I'd asked the wrong question was that AI couldn't say which hat it had on.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I managed each one
&lt;/h2&gt;

&lt;p&gt;The two roles didn't just run under different rules. They needed different amounts of me.&lt;/p&gt;

&lt;p&gt;The Research Assistant needed a lot of me — a running conversation, not a one-off ask. I stayed in the loop for as long as an idea took to settle. That's what "interactive" actually means: I couldn't hand off a half-formed question and walk away.&lt;/p&gt;

&lt;p&gt;The DevOps team needed almost none. Once conceptualization settled on something, I asked the DevOps side for an actual plan first: the concrete steps, and how it intended to delegate parts of the work across its own agents. I read that plan once, agreed to it, and then stopped supervising. That was the entire point: I could go to sleep while it worked, because correctness wasn't riding on me watching. It was riding on the spec, the docs, and the tests holding it to the plan.&lt;/p&gt;

&lt;p&gt;That's also where the two roles actually connect. A spec is the bridge between them. It's the artifact where an argued-out decision from the Research Assistant becomes a concrete thing the DevOps team can plan against, execute, and be checked against later. Without a written spec, "agree on a plan and walk away" wouldn't be safe. With one, it's just delegation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's coming
&lt;/h2&gt;

&lt;p&gt;Conceptualization is R. Materialization is D. That shorthand carries through both posts below.&lt;/p&gt;

&lt;p&gt;The next two posts each open one of those doors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://lee.yt/posts/the-research-assistant-in-the-room/" rel="noopener noreferrer"&gt;The Research Assistant in the Room&lt;/a&gt;&lt;/strong&gt; is about the two weeks of conceptualization — including a design AI kept proposing, three separate times, that I refused each time because it would have broken the one guarantee the model exists to keep.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://lee.yt/posts/the-devops-team-that-never-sleeps/" rel="noopener noreferrer"&gt;The DevOps Team That Never Sleeps&lt;/a&gt;&lt;/strong&gt; is about the process that made "AI writes the software" safe to say out loud — including the two times that process caught AI reporting success on work that, quietly, hadn't actually succeeded.&lt;/p&gt;

&lt;p&gt;Both stories are about the same two weeks. They just watch a different member of the team show up to do the work.&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>schema</category>
      <category>json</category>
    </item>
    <item>
      <title>Omnist: I Turned My Sixteen-Year-Old PhD Research Into a Personal Open Source Project in Two Weeks</title>
      <dc:creator>Thomas Lee</dc:creator>
      <pubDate>Mon, 20 Jul 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/tomlee/omnist-i-turned-my-sixteen-year-old-phd-research-into-a-personal-open-source-project-in-two-weeks-299h</link>
      <guid>https://dev.to/tomlee/omnist-i-turned-my-sixteen-year-old-phd-research-into-a-personal-open-source-project-in-two-weeks-299h</guid>
      <description>&lt;p&gt;Sixteen years ago, in 2010, I published &lt;a href="https://github.com/omnist-dev/omnist/blob/master/docs/paper/Lee-Cheung-2010-XML-Schema-Computations-CIKM.pdf" rel="noopener noreferrer"&gt;a paper on formalizing schemas for tree-structured data&lt;/a&gt;, like &lt;a href="https://omnist.dev/formats/xml/" rel="noopener noreferrer"&gt;XML&lt;/a&gt; and &lt;a href="https://omnist.dev/formats/json/" rel="noopener noreferrer"&gt;JSON&lt;/a&gt;. My goal was to move beyond guesswork: could we mathematically decide if a schema change is backward-compatible? I wanted to see if we could prove schema equivalence, or reliably infer schemas from examples.&lt;/p&gt;

&lt;p&gt;The research offered a solid theoretical framework, though it remained largely academic. For years, I hoped to build something practical from it. But the demands of a full-time job made finding the time nearly impossible.&lt;/p&gt;

&lt;p&gt;AI finally helped me bridge that gap. Working with it in my spare time, I turned that research into &lt;a href="https://omnist.dev" rel="noopener noreferrer"&gt;Omnist&lt;/a&gt; — an &lt;a href="https://github.com/omnist-dev/omnist" rel="noopener noreferrer"&gt;open source Python project&lt;/a&gt; on PyPI, with 600+ tests and a CLI, built in two weeks. This post covers what Omnist does. (How two weeks was even possible is &lt;a href="https://lee.yt/posts/a-team-of-one-running-two-teams/" rel="noopener noreferrer"&gt;its own post&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What Omnist actually does
&lt;/h2&gt;

&lt;p&gt;Omnist's core idea is a single tree model underneath everything: a node is either a scalar value or an ordered list of labeled edges. &lt;a href="https://omnist.dev/formats/json/" rel="noopener noreferrer"&gt;JSON&lt;/a&gt;, &lt;a href="https://omnist.dev/formats/yaml/" rel="noopener noreferrer"&gt;YAML&lt;/a&gt;, &lt;a href="https://omnist.dev/formats/toml/" rel="noopener noreferrer"&gt;TOML&lt;/a&gt;, and &lt;a href="https://omnist.dev/formats/xml/" rel="noopener noreferrer"&gt;XML&lt;/a&gt; all map onto that same structure. That's what lets Omnist convert between them without a bespoke converter for every pair.&lt;/p&gt;

&lt;p&gt;Omnist also has its own native data and schema languages built on that model. The &lt;a href="https://omnist.dev/formats/oml/" rel="noopener noreferrer"&gt;Omnist Markup Language (OML)&lt;/a&gt; is the native data format — the one with zero adjustments needed to map onto the underlying tree. The &lt;a href="https://omnist.dev/schema/" rel="noopener noreferrer"&gt;Omnist Schema Definition (OSD)&lt;/a&gt; is the text syntax for defining schemas. Named &lt;code&gt;record&lt;/code&gt; types, with closed fields, each given a cardinality range. A field's type is either a fixed scalar or a reference to another record.&lt;/p&gt;

&lt;p&gt;Simplicity is the design philosophy behind that schema model. Every field has exactly one type — no unions, no enums. That's a real constraint. But it's a deliberate trade: it's what keeps &lt;a href="https://omnist.dev/schema/#operations-compare-and-infer" rel="noopener noreferrer"&gt;&lt;code&gt;compatible_with&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://omnist.dev/schema/#operations-compare-and-infer" rel="noopener noreferrer"&gt;&lt;code&gt;equivalent&lt;/code&gt;&lt;/a&gt; actual computations instead of heuristics. Once a field can be "this type, or that, or null," there's ambiguity left over — and compatibility checking gets stuck on it. There's one deliberate escape hatch: an &lt;code&gt;any&lt;/code&gt; type, added later. It doesn't reopen that ambiguity. It has to be spelled out explicitly, or explicitly asked for — never something the tool reaches for on its own. And its cost is stated up front: compatibility checking stops wherever &lt;code&gt;any&lt;/code&gt; starts.&lt;/p&gt;

&lt;p&gt;That foundation gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://omnist.dev/guide/#reading--writing-other-formats" rel="noopener noreferrer"&gt;Convert&lt;/a&gt; between any of those formats, ingest and export freely, without hand-writing a converter for each pair — or &lt;a href="https://omnist.dev/guide/#custom-formats" rel="noopener noreferrer"&gt;develop a plugin&lt;/a&gt; to add a new one.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://omnist.dev/schema/#validation" rel="noopener noreferrer"&gt;Validate&lt;/a&gt; a document against a schema, with exact, path-based error reporting instead of a vague "something is wrong somewhere."&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://omnist.dev/api/#schema-directed-deserialization" rel="noopener noreferrer"&gt;Schema-directed deserialization&lt;/a&gt;: read an untyped text format into typed data, upgrading values like ISO date strings into real &lt;code&gt;date&lt;/code&gt;/&lt;code&gt;time&lt;/code&gt;/&lt;code&gt;datetime&lt;/code&gt; types whenever the conversion is value-exact.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://omnist.dev/schema/#operations-compare-and-infer" rel="noopener noreferrer"&gt;Infer&lt;/a&gt; a schema automatically from example documents.&lt;/li&gt;
&lt;li&gt;Ask &lt;a href="https://omnist.dev/schema/#operations-compare-and-infer" rel="noopener noreferrer"&gt;&lt;code&gt;equivalent(a, b)&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://omnist.dev/schema/#operations-compare-and-infer" rel="noopener noreferrer"&gt;&lt;code&gt;compatible_with(v1, v2)&lt;/code&gt;&lt;/a&gt; — do two schemas accept exactly the same set of documents, or is one backward-compatible with the other.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://omnist.dev/schema/#operations-compare-and-infer" rel="noopener noreferrer"&gt;Normalize&lt;/a&gt; a schema down to its minimal equivalent form.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://omnist.dev/schema/#subschema-extraction" rel="noopener noreferrer"&gt;Extract&lt;/a&gt; the minimal subschema that recognizes only a chosen subset of fields. This was the paper's original headline result — it trimmed real-world industrial XML schemas down to a fraction of their size. It's a CLI command too: &lt;a href="https://omnist.dev/cli/#omnist-schema-extract" rel="noopener noreferrer"&gt;&lt;code&gt;omnist schema extract&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of it works as a CLI and as a Python library. It drops into a CI pipeline or a script equally well.&lt;/p&gt;

&lt;p&gt;Omnist is built for software engineers, not researchers. It's meant to answer practical questions you run into while shipping software — not to explore schema theory for its own sake. A few places it fits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CI gate for config and API schema changes.&lt;/strong&gt; Run &lt;code&gt;compatible_with&lt;/code&gt; in a pipeline step so a backward-incompatible schema change fails the build instead of breaking a downstream consumer in production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migrating a config format.&lt;/strong&gt; Moving a project from &lt;a href="https://omnist.dev/formats/yaml/" rel="noopener noreferrer"&gt;YAML&lt;/a&gt; to &lt;a href="https://omnist.dev/formats/toml/" rel="noopener noreferrer"&gt;TOML&lt;/a&gt;, or vice versa, without writing and maintaining a one-off converter script.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Onboarding a legacy data source.&lt;/strong&gt; Infer a schema from a pile of existing JSON examples when no one wrote one down, then validate new data against it going forward.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type-safe ingestion in a data pipeline.&lt;/strong&gt; Deserialize untyped &lt;a href="https://omnist.dev/formats/json/" rel="noopener noreferrer"&gt;JSON&lt;/a&gt; or &lt;a href="https://omnist.dev/formats/yaml/" rel="noopener noreferrer"&gt;YAML&lt;/a&gt; straight into typed structures, with errors that point at the exact path that's wrong instead of a generic parse failure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Compatibility, in practice
&lt;/h2&gt;

&lt;p&gt;A config schema evolves — you add an optional field. Is that safe for everyone still running the old schema?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;record R { &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: string }&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;root R&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;v2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;record R { &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: string, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; [0,1]: integer }&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;root R&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compatible_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# True — every v1 document is still valid under v2
&lt;/span&gt;
&lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compatible_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# False — a v2 document with a port isn't valid under v1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole feature, in four lines. No example corpus, no fixtures, no "looks fine to me." Just an actual, decidable answer — the same way a type checker gives you an actual answer instead of a guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two weeks with AI — so is it any good?
&lt;/h2&gt;

&lt;p&gt;"Built with AI in two weeks" is exactly the kind of claim that should make you suspicious. Here's what actually backs it up: 600+ tests at 100% line coverage. A &lt;a href="https://omnist.dev/testing/" rel="noopener noreferrer"&gt;&lt;code&gt;mypy --strict&lt;/code&gt;&lt;/a&gt; type-checking gate in CI. Property-based fuzzing. Formal grammars for both text formats, verified directly against the parsers. And every code example in the docs, executed as part of the test suite — so the docs can't quietly drift from what the code actually does.&lt;/p&gt;

&lt;p&gt;The part I'm proudest of: the schema algebra is checked three independent ways, not just tested against examples. Two different algorithms check it. One tests subschema inclusion directly. The other minimizes both schemas and checks they're isomorphic. They're fuzzed against each other and required to always agree. On top of that, a brute-force oracle enumerates thousands of small documents. It checks every operation's answer against the literal, set-theoretic ground truth — about 2.5 million individual checks. When I mutated the code to deliberately reintroduce a bug during testing, that oracle caught it immediately, with a concrete counterexample. That's the property I actually wanted from the 2010 research — not "the tests pass," but "the algorithm is provably doing what it claims."&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;pip install omnist&lt;/code&gt; gets you the library and the &lt;code&gt;omnist&lt;/code&gt; CLI. Head to &lt;a href="https://omnist.dev" rel="noopener noreferrer"&gt;omnist.dev&lt;/a&gt; to get started:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://omnist.dev/quickstart/" rel="noopener noreferrer"&gt;Quickstart&lt;/a&gt; — up and running in five minutes&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://omnist.dev/example/" rel="noopener noreferrer"&gt;Real-life example&lt;/a&gt; — a worked end-to-end walkthrough&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://omnist.dev/api/" rel="noopener noreferrer"&gt;API reference&lt;/a&gt; — full Python API docs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://omnist.dev/cli/" rel="noopener noreferrer"&gt;CLI docs&lt;/a&gt; — all commands and flags&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The source is at &lt;a href="https://github.com/omnist-dev/omnist" rel="noopener noreferrer"&gt;github.com/omnist-dev/omnist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you try it, I'd love to hear what you think — what worked, what didn't, or what you wish it could do. Find me on &lt;a href="https://x.com/lee_tom" rel="noopener noreferrer"&gt;X (@lee_tom)&lt;/a&gt; or &lt;a href="https://linkedin.com/in/tomlee" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next up
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://lee.yt/posts/a-team-of-one-running-two-teams/" rel="noopener noreferrer"&gt;A Team of One, Running Two Teams&lt;/a&gt; — how a sixteen-year-old paper became a working package in two weeks, and the two roles AI played to make that possible: a Research Assistant to argue with, and a DevOps team to run unsupervised.&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>json</category>
      <category>schema</category>
    </item>
    <item>
      <title>Omnist: Canonical Schema and Data Model for JSON, YAML, TOML, and XML</title>
      <dc:creator>Thomas Lee</dc:creator>
      <pubDate>Sun, 19 Jul 2026 09:55:03 +0000</pubDate>
      <link>https://dev.to/tomlee/omnist-canonical-schema-and-data-model-for-json-yaml-toml-and-xml-19ld</link>
      <guid>https://dev.to/tomlee/omnist-canonical-schema-and-data-model-for-json-yaml-toml-and-xml-19ld</guid>
      <description>&lt;p&gt;If you've ever hand-written a converter between JSON and YAML, or guessed whether a schema change would break someone downstream, this might be useful.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://omnist.dev" rel="noopener noreferrer"&gt;Omnist&lt;/a&gt; — an &lt;a href="https://github.com/omnist-dev/omnist" rel="noopener noreferrer"&gt;open-source&lt;/a&gt; Python library with one tree model underneath JSON, YAML, TOML, and XML. Read any of them into the same structure, validate it against a schema, write it back out to any of the others. No per-pair converters.&lt;/p&gt;

&lt;p&gt;I put together a short deck walking through the model and implementation:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://omnist.dev/presentation.html" rel="noopener noreferrer"&gt;omnist.dev/presentation.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It also ships a schema algebra, all decidable rather than heuristic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;compatible_with&lt;/code&gt; — will every old document still validate under a new schema?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;equivalent&lt;/code&gt; — do two differently-written schemas accept the same documents?&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;normalize&lt;/code&gt; — collapse a schema to its canonical minimal form&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;extract&lt;/code&gt; — the minimal subschema for a subset of fields&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;100% line coverage, &lt;code&gt;mypy --strict&lt;/code&gt;, property-based fuzzing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The story behind it
&lt;/h2&gt;

&lt;p&gt;Omnist started as a 16-year-old idea from my PhD research, built out in two weeks of spare time with AI doing most of the heavy lifting on both the thinking and the shipping side. If you're curious how that actually worked — not just "AI writes code fast," but how research decisions and engineering execution stayed separated even with one person running both — I wrote it up as a five-part series:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://lee.yt/posts/omnist-personal-open-source-project/" rel="noopener noreferrer"&gt;Omnist: I Turned My Sixteen-Year-Old PhD Research Into a Personal Open Source Project in Two Weeks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lee.yt/posts/a-team-of-one-running-two-teams/" rel="noopener noreferrer"&gt;A Team of One, Running Two Teams&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lee.yt/posts/the-research-assistant-in-the-room/" rel="noopener noreferrer"&gt;The Research Assistant in the Room&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lee.yt/posts/the-devops-team-that-never-sleeps/" rel="noopener noreferrer"&gt;The DevOps Team That Never Sleeps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lee.yt/posts/from-alpha-to-beta/" rel="noopener noreferrer"&gt;Alpha to Beta: Bringing In QA&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try it out
&lt;/h2&gt;

&lt;p&gt;Quick start: &lt;a href="https://omnist.dev/quickstart/" rel="noopener noreferrer"&gt;omnist.dev/quickstart&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Install Omnist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;omnist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feedback and issues genuinely welcome — &lt;a href="https://github.com/omnist-dev/omnist" rel="noopener noreferrer"&gt;github.com/omnist-dev/omnist&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>python</category>
      <category>json</category>
      <category>schema</category>
    </item>
  </channel>
</rss>
