Productionizing research is a boundary problem — RPC Productionizing research is a boundary problem<br>Phillip C. · July 21, 2026 ·19 min read
A research prototype and a production system can share most of their code, but they’re ultimately built to answer two different questions.
The prototype asks:
Can we make this work?
The production system asks:
Can other people depend on this continuing to work?
Those two questions pull toward two very different sets of engineering decisions.
A prototype should make it cheap to try ideas. It can assume a knowledgeable operator, a controlled environment, a familiar dataset, and the occasional bit of manual intervention. It might only need to run successfully a handful of times before the researcher changes it again.
A production system has to keep working when the inputs are surprising, the infrastructure is flaky, the original author is on vacation, and several other systems depend on its behavior — and it has to stay understandable while both the product and the underlying research keep moving.
The natural mistake is to treat the research prototype as an immature version of the production system: add an HTTP endpoint, drop it in a container, deploy it somewhere, and call the result “v0.” Then you begin a slow process of “cleaning it up” — adding logging and metrics, improving error handling, making the code look more like the rest of your production services.
Sometimes that’s the right approach for a demo or an MVP. It’s rarely a good long-term architecture.
Productionizing research isn’t mainly a code-cleanup project. It’s the work of designing a boundary between two environments that need to optimize for different things.
Research code is optimized for learning
Application engineers are often alarmed the first time they open research code.
They find thirty imports, two thousand lines, and every function in the system defined in one place. Variables named x2, tmp, and best_result_new. Important constants living as unexplained numbers in the middle of an expression. Configuration as a pile of mutable globals near the top of the file. Commented-out versions of the last three experiments, a hard-coded path to someone’s home directory, and a dependency on whatever happened to be installed the last time the thing worked. Sometimes it only runs after another script has generated a file in the right place. Sometimes it only runs if a notebook’s cells are executed in the right order. Failure handling consists of printing an exception, changing one of the magic numbers, and trying again.<br>I want to be clear that I’m describing this with affection. The mistake isn’t writing code like this — the mistake is confusing that code for something it was never trying to be.
This code trips nearly every instinct an application engineer has spent years developing. We see implicit state, hidden dependencies, unclear interfaces, and an incident report waiting to happen, and our first impulse is to stop everything and clean it up before anyone builds on top of it.
That impulse is understandable, and it’s often premature. The code may be perfectly well adapted to its actual purpose. A variable hasn’t been given a durable name because the underlying concept might not be durable yet. A constant hasn’t been extracted because the researcher expects to throw it out tomorrow. Three approaches coexist in one giant file because comparing them is the work, not an unfortunate stop on the way to a clean abstraction.
Production engineering rewards explicit configuration, reproducibility, clear interfaces, and predictable failure modes. Research code is usually optimized for a different thing entirely: shrinking the time between having an idea and finding out whether it was wrong. A researcher might need to change the model, the preprocessing, the input representation, and the evaluation criteria several times in a single week. Building a stable abstraction around each of those decisions would slow the work down without producing anything useful. The point of the code is to help the researcher figure out which decisions are even worth keeping.
Application engineers have the opposite problem. Once a capability becomes part of a product, the uncertainty moves outward. The system may have to handle requests from many users, take data from external systems that misbehave, run jobs that take minutes or hours, support cancellation, or blow past available resources. Results may need to be stored, inspected, compared, and traced back to the exact version of the system that produced them. And the product creates expectations: other engineers start building against its outputs, customers start relying on its behavior, operators need to understand why it failed, and security and deployment constraints that were irrelevant during experimentation suddenly show up.
None of those production requirements mean the prototype was badly built. They mean it’s crossed into a different problem domain.
”Put an...