Interfaces for Models
I don’t start with architecture anymore. I start with the contract. Before I name a layer, I write down what the model takes, what it gives, and what it must never do. Inputs with units and reference frames. Valid ranges and coordinate conventions. Latency budget and memory ceiling. Outputs with uncertainty attached and a statement about where they’re valid. A model without an interface is a story you have to keep retelling, while a model with one is a component you can trust.
The obvious part is typing. Floats vs. integers, shapes and dtypes, batch semantics. The non-obvious part is semantics. “Temperature” means °C at the sensor, not simulated Kelvin at the boundary. “Pose” is SE(3) with right-handed axes, not whatever the last team decided. “Time” is monotonic, not wall clock, and every timestamp claims a timezone or none. These specifics seem fussy until the first integration failure costs a week. Most of the bugs I regret weren’t numerical, but unit, frame, or contract bugs that never had a chance to be caught because we never wrote them down.
Pre-conditions and post-conditions turn taste into tests. Before the call: inputs are within the envelope, calibration hash matches, the trust band gate passed, no more than N% of fields are missing. After the call: uncertainty covers at the stated rate over a rolling window, the output respects the invariants we’ve declared elsewhere, and if the band is violated, we abstain or fallback with a logged reason.
Property-based tests generate counterexamples against the contract until they stop finding them. When they still find them, the code is innocent and the spec needs work.
Interfaces also have to admit failure on purpose. “Allowed failures” is not an apology section. It is how collaborators plan. This policy may return “unknown” when inputs drift. This planner may degrade to a safe control set if the compute budget tightens. This detector may output “maybe” when confidence is below a calibrated threshold. You can’t promise everything, so promise what you can defend, and make the rest predictable. Grace is part of the interface.
Versioning is where seriousness shows. A model that changes without an interface bump is an infection vector. Schema evolution needs a story: additive fields with defaults, deprecations with dates, migrations that can be simulated offline. Content-addressed artifacts make provenance boring. If I can’t reproduce an output by pinning model@sha, data@sha, and env@sha, we don’t have an interface. We have folklore. I care less about ceremony than about friction reduction: teammates move faster when the edges are explicit.
I’ve learned to include behavior over time. Is the interface stateless or does it promise hysteresis bounds? Are outputs idempotent across retries? What jitter is acceptable, and what is the policy when the budget is breached? The world is not batch-shaped. An interface that ignores time invites heroics elsewhere in the stack. If the SLA is part of the contract, the scheduler can reason. If not, it guesses.
Testable claim: an interface-first model will cut integration defects in half compared to code-first efforts of the same scope. Not because interfaces are fashionable, but because they let people disagree earlier and repair faster. When a failure happens, the question becomes “which clause did we violate?” not “what did you mean by pose?”
What I want from these contracts is less personality in the plumbing, more attention on the ideas. If tomorrow’s change ships with a bumped schema, a migration note, and proof that the post-conditions still hold, the next person can keep working without guessing what I meant. That’s when architecture stops being the main character and starts being a choice.