A research notebook can produce a convincing chart and still leave the next analyst with a difficult question: how did we get here? The code might be in Git, but the input file has changed, a parameter lives in an untracked cell, and the version that produced the chart is no longer obvious.
Hajin Lim, an analyst on our Development team, took on that problem by building a research runner around a simple idea: every result should carry enough information for someone else to reproduce it. I worked with Hajin on the project, and the most interesting part was how he narrowed a broad infrastructure problem into a small, useful contract.
Start with the handoff
Rather than beginning with a new orchestration framework, Hajin started with the moment one analyst hands a notebook to another. What would the second person need to run the same experiment without asking the original author to reconstruct their environment?
We settled on three inputs: a fixed snapshot of the source data, a versioned configuration, and a reference to the code. The runner would record those inputs alongside each output. It would also say clearly when an experiment could not be reproduced, rather than quietly substituting the newest available dataset.
That last decision mattered. A system that always returns something can look convenient while making comparisons less trustworthy. A missing snapshot should be a visible error, not an invitation to change the experiment.
A snapshot is a promise
Hajin designed the ingestion step to separate fetching data from running research. Fetching creates a snapshot with an identifier and a small manifest. Running an experiment consumes an existing snapshot. The research code never needs to decide whether today's download is close enough to yesterday's.
The manifest records the source, retrieval time, schema version, and a content hash. Those fields answer different questions: where did this come from, when was it obtained, how should it be interpreted, and has its content changed?
He also made validation part of ingestion. Duplicate observations, unexpected columns, and missing required values stop a snapshot from becoming available to the runner. Checks that used to sit inside individual notebooks now happen at the boundary, where failures are easier to explain.
Keep the interface small
The first version could have accepted dozens of options. Instead, Hajin kept the command narrow: choose a snapshot, choose a configuration file, and give the run a name. Everything else belongs in the versioned configuration or the manifest.
For an analyst, that means the important decisions live in a file that can be reviewed with the rest of a change. For the runner, it means fewer hidden sources of state. Two runs with the same recorded inputs should have the same starting point, even if they happen on different days.
There is a tradeoff here. Fixed snapshots use more storage than a single mutable file. Hajin chose to accept that cost in exchange for a clearer audit trail, while keeping retention as a separate policy rather than mixing it into the research interface.
Test the failures, too
The happy path was only half the work. Hajin wrote checks for malformed manifests, changed input files, and interrupted runs. An unfinished output goes into a temporary location and becomes visible only after the run completes. A failed run should not leave behind a result that looks ready to use.
One useful test deliberately changes a snapshot after its manifest has been written. The runner must reject it. Another starts with a configuration that points to a missing snapshot. The error should identify the missing input and explain what the analyst can do next.
These tests capture the project's real purpose: making the boundary between a result and its supporting evidence explicit. A green test suite matters most when it protects the assumptions people rely on.
What Hajin owned
Hajin owned the snapshot manifest, validation layer, and runner interface. He also wrote the handoff instructions and worked through the failure cases with other members of the team. My role was to help review the scope and question the assumptions behind the design.
The project is a useful example of work on the Development team. The central problem was not choosing a particular database or framework. It was deciding which guarantees a tool should make, expressing those guarantees in a small interface, and testing what happens when they cannot be met.
The next step is to make it easier to compare two recorded runs and see exactly which inputs changed. The foundation is already the same: preserve the context of a result so the next person can understand it, question it, and build on it.
