Teams plan migrations as though portability were something a vendor grants. It is not. Portability is decided by the form the artifact is in, and a single stack contains three forms with wildly different answers. Knowing which form you are holding predicts the migration better than knowing which vendor you are leaving.
The claim
Here is the position this page defends: an artifact is portable exactly to the degree that its meaning is contained in itself. Text is self-contained — a prompt means the same thing on any machine that can read UTF-8. A 1,536-dimensional vector is not: its meaning lives in a model that is not in the file. A fine-tuned adapter is worse still, because its meaning lives in a base model that is not merely absent but unobtainable.
That gives three tiers, and the interesting one is the middle. Tier one moves and everyone knows it. Tier three does not move and everyone knows that too. Tier two exports cleanly, transfers cleanly, and then does not work, which is why it accounts for most of the nasty surprises in a cutover.
Text moves
Prompts, system instructions, few-shot examples, documents, evaluation cases, transcripts, tool schemas expressed as JSON Schema, test fixtures. All of it is bytes with self-contained meaning, and all of it transfers without loss.
There is an important qualification and it is not a hedge. Text is portable as an artifact; the behaviour it produces is not portable at all. A prompt tuned over months against one model family is fitted to that family’s instruction-following quirks, formatting habits and refusal boundaries. Moving it costs nothing and gains you a starting point rather than a working system — the details are in what breaks when prompts move across families.
Notice what that means practically: the migration cost of tier one is entirely re-verification. Which is precisely why the evaluation suite belongs in this tier and belongs in your repository. If your golden cases are text in git, the expensive half of every tier-one move is already tooled.
Coordinates do not travel
An embedding is a position in a space that one model version defined. Nothing outside that model can interpret it. Two embedding models produce vectors that may not even share a dimensionality, and where they do, the shared dimensionality is a coincidence — the axes mean different things, so cosine similarity between a vector from one and a vector from the other is not a small error, it is an undefined quantity.
So the export succeeds and the system fails. You move ten million vectors into a new database, the queries run, the latency looks fine, and retrieval quality has quietly collapsed because half the index was written by one model and half by another. This failure has no error message. It surfaces as complaints about answers.
Everything fitted to a distribution belongs in this tier alongside the vectors themselves: similarity thresholds, top-k values, reranker score cutoffs, any “if the best match scores below X, say I don’t know” rule, and any clustering or deduplication boundary. These are numbers you can copy and should not. They were fitted to a score distribution that no longer exists.
Log probabilities have the same character, and so do token counts. A count is a fact about a tokenizer, not about a string: the same text measured by two tokenizers gives two different numbers, and every budget, truncation rule and cost model built on the old number is off by whatever the ratio turns out to be. That is a mechanical consequence rather than a vendor decision, and why token counts change after a provider switch works through it.
Learned and hosted artifacts
The third tier is not portable in any sense and the honest planning move is to stop trying. A fine-tuned model is a modification of somebody else’s base weights. Even if the adapter downloaded perfectly, there is nothing to apply it to. What you own is the training file, and the training file is tier one — which is the whole reason keeping it outside the platform matters so much.
Managed indexes belong here when the platform chose the chunking and the embedding for you, because then you hold neither of the two inputs needed to rebuild. Workflow and graph definitions belong here absolutely: they export as a description of themselves and no other runtime consumes them, so the migration cost is measured in engineer-days of reimplementation and not in bytes.
Twelve months of traces, evaluation runs and cost history belong here too, and this is the loss people mourn late. It does not stop the new system working. It removes your ability to say whether the new system is better, which is a question you will be asked within a week of cutting over.
The design rule that follows
If portability is a property of representation, then it is something you decide at design time and cannot buy back later. The rule is one sentence: store the reproducible input beside every derived artifact.
The document beside the chunk. The chunker configuration beside the index. The embedding model identifier and version stamped on every vector row, because an index that cannot tell you what wrote it is an index you cannot safely add to. The training file beside the fine-tune id. The exact request body beside the logged response. The prompt version beside the evaluation score.
Each of those costs a column or a file at the time and converts a tier three artifact into a tier two one, or a tier two into a rebuild you can actually schedule. That is the entire mechanism by which a migration becomes a project with an estimate rather than an argument about what was lost.
The counter-argument deserves a hearing: designing for portability you may never use is a cost paid for an option. It is, and it would be a bad trade if the price were high. It is not — the practices above are things a well-run system wants anyway, because the same records that let you leave are the records that let you debug a regression, attribute a cost, or reproduce a complaint from three months ago. Portability is a side effect of good bookkeeping. That is why it is worth having, and why it is not worth building an abstraction layer for its own sake. To turn this argument into a work list, score your own stack with the component-by-component lock-in checklist.
Top comments (0)