For the past few years I've maintained an SDK that ships in three languages at once: Python, Java, and C#. It bridges legacy defense systems to a universal command-and-control language, which means every release has to behave identically on platforms that were written decades apart, by different contractors, in different runtimes. The naive approach — write it once, port it twice — fails quietly and then catastrophically. What actually works is treating the SDK not as three codebases but as one contract with three renderings.

## The schema is the product

The single most important decision we made was demoting the code. The source of truth is a language-neutral message schema — every type, field, constraint, and state transition defined once, in one place. The Python, Java, and C# libraries are generated and hand-finished renderings of that schema, not siblings that happen to agree. When a platform integrator reports a discrepancy between the Java and C# behavior, the question is never 'which library is right?' — it's 'what does the schema say?' That reframing eliminates a whole category of argument.

## Idiomatic beats identical

The temptation is to make all three libraries look the same: same class names, same method shapes, same call patterns. Resist it. Python developers expect keyword arguments, context managers, and exceptions; C# developers expect async/await, properties, and IDisposable; Java developers expect builders and checked error handling. An SDK that fights its host language gets wrapped — and the wrapper is where the bugs live. We keep the wire contract identical and let every surface be native. The test suite, not the API shape, is what guarantees equivalence.

## One conformance suite to rule them all

Every release candidate in every language runs the same conformance suite: a shared corpus of message fixtures, golden encodings, and behavioral scenarios expressed as data, not code. Each SDK has a thin harness that replays the corpus through its own implementation. When a fixture fails in exactly one language, we've found a porting bug; when it fails in all three, we've found a schema bug. Building this felt like overhead in month one and became the reason we could ship monthly by month six.

## Versioning for systems that can't update

Defense platforms don't do rolling upgrades. Some integrators are two major versions behind and will stay there through a deployment cycle measured in years. So the contract evolves additively: new fields are optional, old fields are never repurposed, and deprecations get a documented sunset measured in versions, not months. It's slower than the 'move fast' instinct — and it's why the SDK is on 12+ platforms instead of maintaining 12 forks.

## Takeaways

If you're about to ship an SDK in more than one language: put the contract in a schema, not in a reference implementation. Generate what you can, hand-write what must feel native, and spend the saved effort on a shared conformance corpus. Treat every published field as a promise you'll keep for years. Three languages at once is not three times the work — but only if there is exactly one source of truth.