Prompting, retrieval-augmented generation (RAG) and fine-tuning are three different places to intervene in an LLM system, and they change three different things. Prompting changes the instructions and examples the model sees on this request. RAG changes the knowledge in front of the model on this request. Fine-tuning changes the model's weights, and therefore its default behaviour on every request. Interviewers ask "when would you fine-tune instead of using RAG?" because the answer reveals whether you understand what each mechanism can and cannot do, and whether you reach for the expensive tool first. The most common wrong answer is "fine-tune it on our documents so it knows them"; the strong answer starts from what is actually failing, measures it, and picks the cheapest lever that fixes that specific failure.
For a Backend or AI engineer this is a systems decision with cost, data, latency and maintenance consequences, not a modelling detail: a fine-tune creates a training-data pipeline, an evaluation harness and a model that must be re-trained when the base model changes; a RAG system creates an ingestion pipeline and an index to keep fresh; a prompt creates a text artifact that lives in version control. This page gives a decision framework, explains LoRA/PEFT at a conceptual level, and includes an illustrative cost sketch. For the retrieval side in depth see Retrieval-Augmented Generation (RAG), and for the prompt side see Prompt Engineering & Prompt Management.
These are three different ways to make an AI chatbot or assistant answer better, and people often reach for the wrong one. Each changes a different part of how the AI works:
Think of training a new tour guide. Prompting is handing them a quick instruction card before each tour, "keep it under an hour, mention the fountain." RAG is giving them a stack of today's updated brochures to check while they talk, so they get the current ticket prices right instead of memorized old ones. Fine-tuning is sending them through months of training so that, permanently, the way they guide tours changes — their storytelling style, pacing, and habits are different from that point on, on every tour they ever give again.
PROMPTING RAG FINE-TUNING
changes instructions, examples the context (retrieved text) the weights
(per request) (per request) (persistently, all requests)
good at format, task framing, private / fresh / large style, strict output format,
tone, simple rules knowledge, citations, domain-specific task behaviour,
per-user access control shorter prompts, smaller model
bad at behaviour a prompt reasoning over the adding facts reliably,
cannot pin down; retrieved text if retrieval freshness, citations, ACLs,
long prompts cost is poor easy rollback of "what it knows"
takes effect immediately when the index is updated after a training run + evaluation
who can edit anyone, in text anyone who can add documents an ML/data pipeline
Failure symptoms and the lever they point to:
"wrong facts / outdated / cannot cite" -> knowledge problem -> RAG
"right facts but wrong format / tone" -> behaviour problem -> prompt first, fine-tune if prompts plateau
"ignores retrieved text, invents anyway" -> grounding problem -> better prompt/retrieval, maybe fine-tune for grounded style
"prompt is 4,000 tokens of examples" -> cost/latency problem -> consider fine-tuning to internalise them
The clean mental model is knowledge versus behaviour. Facts that are private, fast-changing, large, or need a source belong outside the weights, where you can update, delete, permission and cite them; that is RAG. Habits (always answer in this JSON schema, use this tone, follow this multi-step procedure) belong in either the prompt or, when the prompt cannot hold them reliably or economically, in the weights. Real systems blur the line: a long prompt with examples changes behaviour, and retrieved examples can too, so the distinction is a guide to the first experiment, not a law.
Prompting is the default first step because iteration is nearly free: edit text, re-run an evaluation set, compare. It has a ceiling, because some behaviours (a rare-domain classification boundary, a very strict style) are hard to state and expensive to demonstrate in every request, and long few-shot prompts add input tokens, prefill latency and cost on every call (provider prompt caching, where available, can reduce the repeated part).
START: define the task and build an evaluation set (50-200 real examples, an automatic or rubric-based score)
|
1. Measure the best simple prompt on the eval set. Good enough? -> ship prompting.
|
2. Failures are missing / stale / private FACTS? -> add RAG (and evaluate retrieval separately).
|
3. Failures are BEHAVIOUR (format, tone, task pattern)?
| try: clearer instructions, few-shot examples, structured output / schema, a stronger base model
| still short of the target, with enough labelled examples and enough traffic to matter?
| -> fine-tune (PEFT/LoRA first), re-measure.
4. Both? -> combine: RAG for facts + fine-tune (or prompt) for behaviour.
5. Re-run the eval on every change; keep the cheaper option if quality is equal.
Common wrong choices:
X fine-tune to "teach the model our docs" facts are stored diffusely; recall is unreliable, can make hallucination worse,
cannot cite, cannot enforce per-user access, cannot delete one fact, stale next week
X RAG for a pure format problem retrieval adds latency and failure modes and does not change how the model writes
X RAG with huge, poorly chunked corpus retrieval quality caps answer quality (see the chunking page)
X fine-tune with a few dozen noisy examples the model learns the noise; evaluation set leaks into training
X skip the eval set you cannot tell whether any lever helped
The classic mistake is fine-tuning to add knowledge. Training adjusts weights so that the model's outputs match examples; it does not build a database. Some facts get absorbed, but recall is unreliable and can be wrong with confidence, the model cannot show where a claim came from, you cannot restrict which user may see which fact, you cannot remove one fact (for example after a legal takedown) without retraining, and everything is stale the day a document changes. Some published research also suggests that fine-tuning on new facts the base model did not already know can encourage hallucination, so treat "fine-tune it on the wiki" as high risk. If the question is "what is our refund policy today?", the policy should sit in a document that is retrieved and cited.
The mirror-image mistake is using RAG to fix behaviour. Stuffing retrieved style guides into every prompt is a slow, expensive way to imitate what a fine-tune or a good prompt with examples would give you directly. And if the eval set shows the base prompt already passes, none of this is needed. That is why evaluation comes before the decision: without a measured gap you are guessing which lever to pull.
Full fine-tuning: update every weight of a big matrix W (d x d) -> huge optimizer state, full model copy per task
LoRA: freeze W; learn a low-rank update dW = B @ A (A: r x d, B: d x r, r much smaller than d)
forward: y = W x + (alpha / r) * B (A x)
train only A and B; keep the base model shared and read-only
x ----+--> [ W (frozen, d x d) ] --------+--> (+) --> y
| |
+--> [ A: d -> r ] -> [ B: r -> d ] -+ small trainable "side path"
At serving time: keep base + adapter separate (swap adapters per task or tenant), or merge B@A into W (no extra latency)
# Parameter counts for LoRA on a HYPOTHETICAL transformer shape (d_model=4096, 32 layers), adapting q_proj and v_proj only
d, layers, targets = 4096, 32, 2
full_matrix = d * d
for r in (4, 8, 16, 64):
lora_matrix = r * (d + d) # A is r x d, B is d x r -> W' = W + (alpha / r) * B @ A
trainable = lora_matrix * targets * layers
frozen_adapted = full_matrix * targets * layers
print(f"rank {r:>2}: {lora_matrix:>9,} params per matrix vs {full_matrix:,} "
f"({100*lora_matrix/full_matrix:.2f}%), trainable total {trainable/1e6:5.1f}M, "
f"adapter file ~{trainable*2/1e6:5.1f} MB in 16-bit")
# Output:
# rank 4: 32,768 params per matrix vs 16,777,216 (0.20%), trainable total 2.1M, adapter file ~ 4.2 MB in 16-bit
# rank 8: 65,536 params per matrix vs 16,777,216 (0.39%), trainable total 4.2M, adapter file ~ 8.4 MB in 16-bit
# rank 16: 131,072 params per matrix vs 16,777,216 (0.78%), trainable total 8.4M, adapter file ~ 16.8 MB in 16-bit
# rank 64: 524,288 params per matrix vs 16,777,216 (3.12%), trainable total 33.6M, adapter file ~ 67.1 MB in 16-bit
PEFT (parameter-efficient fine-tuning) is the family of methods that train a small number of extra or selected parameters instead of the whole model; LoRA (low-rank adaptation) is the most common. The output above uses a hypothetical model shape purely to show scale: at rank 8 the adapter is well under 1% of the size of the matrices it adapts, so it trains with far less GPU memory than full fine-tuning, and the artifact you store is megabytes instead of a full model copy. QLoRA-style setups additionally hold the frozen base in quantized form during training to cut memory further (see Model Quantization & Compression); the accuracy impact is task-dependent and should be measured.
Operational consequences matter more than the math. An adapter is tied to the exact base model it was trained on: when the provider or your team upgrades the base, the adapter must be re-trained and re-evaluated. Because adapters are small, some inference servers (for example vLLM) can serve many adapters over one base model, which is attractive for per-tenant or per-task variants; check the current documentation of your serving stack for support and limits (see Multi-Model & Multi-Tenant Serving). Hosted fine-tuning APIs also exist, with provider-specific data formats, pricing and model availability that change often. LoRA is not a magic fix: a low rank can under-fit a hard task, and any fine-tune can degrade general abilities (catastrophic forgetting) if the training data is narrow, which is why you keep a general regression set alongside the task set.
PROMPTING RAG FINE-TUNING (LoRA)
up-front hours days-weeks (ingest, chunk, weeks: data collection + labelling,
index, evaluation) training runs, evaluation
data needed a handful of examples the documents themselves hundreds to thousands of clean examples
(varies a lot by task and method)
per-request + few-shot tokens + retrieved tokens (k x shorter prompt; possibly a price
cost chunk size) + embed/search premium for a tuned model (provider-specific)
latency longer prompt = longer + retrieval hop (embed + no extra hop; shorter prompt can be
prefill ANN search) + longer prompt faster; a smaller tuned model can be faster
freshness instant (edit text) as fresh as the last ingest stale until the next training run
maintenance prompt versioning index freshness, deletes, dataset curation, retraining on base-model
re-embedding, retrieval evals change, drift, regression testing
explainability can read the prompt can show retrieved sources opaque weights
rollback redeploy old prompt revert index/version redeploy old adapter/model
The cost structure differs in kind. Prompting and RAG add tokens to every request, so they are cheap at low volume and scale with traffic. Fine-tuning front-loads effort and cost (data, training, evaluation) and can lower per-request tokens, so it tends to pay off only at high volume or when it lets a smaller, faster model replace a larger one; it also carries a recurring maintenance cost because the dataset and adapter age. The demo below turns this into arithmetic with clearly invented prices, showing how the break-even moves with volume and why "fine-tuned" and "RAG" are not interchangeable in that table: they buy different things, and quality has to be equal for the comparison to mean anything.
Latency and reliability follow the same shape. RAG adds an embedding call, an index search and a longer prompt, and gives you a new dependency that can fail or return poor chunks (see Chunking & Embedding Strategies). Fine-tuning removes runtime moving parts but adds a build-time pipeline. Pair any choice with cost controls such as those in Cost Optimization for AI Systems and roll changes out with the approach in A/B Testing & Canary Deployments for Models.
Typical production stack (support assistant):
user question
|
v
[ retrieval: policy docs, product docs -> top-k chunks, filtered by user permissions ] <- KNOWLEDGE (RAG)
|
v
[ prompt: role, rules, citation format, retrieved chunks, few-shot examples ] <- CONTROL (prompting)
|
v
[ model: optionally fine-tuned for tone, refusal style, strict JSON ] <- BEHAVIOUR (fine-tune)
|
v
answer + citations
Evaluate each layer on its own:
retrieval : recall@k, MRR, nDCG on labelled questions
generation : faithfulness/groundedness to retrieved text, format validity, task accuracy
end to end : human or rubric-graded sample, plus regression set for general abilities
Combining is right when the failures are of different kinds: RAG to supply correct, current, permissioned facts; a prompt to state the task, rules and output contract; a fine-tune when a specific behaviour (say, strict structured output in a house style at high volume) will not stay put with prompting alone. A fine-tune can also be trained to use retrieved context well, for example to stay grounded and say "not found" when the context lacks the answer, which is a behaviour rather than a fact. The price of combining is the sum of the maintenance burdens, so add a layer only when the evaluation shows the previous layers plateaued.
To evaluate before choosing, freeze an evaluation set first, record the baseline with the simplest prompt, and change one lever at a time. Keep training and evaluation data strictly separate, otherwise fine-tuning appears to work because it memorised the test. Evaluate retrieval and generation separately for RAG, since retrieval quality bounds answer quality. Include a small regression suite for general behaviour so you notice if a fine-tune helped one task and hurt others. Where quality is judged by an LLM, calibrate that judge against human labels, because it has its own biases.
Standard-library Python. Part 1 is a small decision helper that encodes the framework above for five scenarios. Part 2 is a monthly cost sketch. Every price, token count and fixed cost in Part 2 is an invented ILLUSTRATIVE assumption (they do not describe any real model or vendor); the point is the shape of the curves and the break-even calculation, and you should replace the constants with your provider's current pricing and your own traffic.
from dataclasses import dataclass
# ---------- Part 1: a decision helper (encodes the framework, not a law of nature) ----------
@dataclass
class Need:
name: str
needs_private_or_fresh_facts: bool # facts the base model cannot know, or that change often
needs_citations_or_acl: bool # per-user access control, "show me the source"
behaviour_gap: bool # wrong format / tone / task pattern
prompt_pass_rate: float # measured on an eval set with the best prompt so far
target_pass_rate: float
labelled_examples: int
requests_per_day: int
proposes_finetune_for_facts: bool = False
def recommend(n):
out = []
if n.proposes_finetune_for_facts:
out.append("WARNING: fine-tuning to add facts is a weak choice (unreliable recall, stale, no citations/ACL); use RAG")
if n.needs_private_or_fresh_facts or n.needs_citations_or_acl:
out.append("RAG: knowledge/freshness/citations/ACL live outside the weights")
if n.behaviour_gap:
if n.prompt_pass_rate >= n.target_pass_rate:
out.append("Prompting is enough (instructions + few-shot + structured output)")
elif n.labelled_examples >= 1000 and n.requests_per_day >= 100_000:
out.append("Fine-tune candidate (LoRA/PEFT): prompt at %.0f%% vs target %.0f%%, %d examples, high volume"
% (100 * n.prompt_pass_rate, 100 * n.target_pass_rate, n.labelled_examples))
else:
out.append("Improve prompt/examples and collect labelled data first; fine-tuning not yet justified")
if len(out) >= 2 and out[-2].startswith("RAG") and out[-1].startswith("Fine-tune"):
out.append("=> COMBINE: RAG supplies the facts, the fine-tune fixes behaviour, prompt ties them together")
if not out:
out.append("Prompting: no knowledge or behaviour gap identified")
return out
cases = [
Need("HR policy Q&A", True, True, False, 0.0, 0.0, 0, 20_000),
Need("Invoice -> strict JSON", False, False, True, 0.91, 0.98, 3000, 2_000_000),
Need("Invoice -> strict JSON (small)", False, False, True, 0.91, 0.98, 80, 5_000),
Need("Brand voice + fresh docs", True, False, True, 0.80, 0.95, 1500, 300_000),
Need("'Teach' model our catalogue", True, False, False, 0.0, 0.0, 500, 50_000, proposes_finetune_for_facts=True),
]
for c in cases:
print(f"[{c.name}]")
for line in recommend(c):
print(" -", line)
# ---------- Part 2: monthly cost sketch. EVERY number below is an ILLUSTRATIVE assumption ----------
P_IN, P_OUT = 1.00, 4.00 # $ per 1M input / output tokens (made up; check your provider's current pricing)
BASE_IN, OUT = 300, 200 # tokens per request: task + user input, and the answer
FEWSHOT_EXTRA = 1200 # extra prompt tokens for in-context examples
RAG_EXTRA = 1500 # extra prompt tokens for retrieved chunks
RAG_FIXED = 400 # $/month for vector DB + ingestion pipeline (assumed)
FT_MULT = 1.5 # assumed price multiplier for serving a fine-tuned model
FT_FIXED = 800 # $/month: data curation + retraining amortised + evaluation (assumed)
def per_request(in_tok, mult=1.0):
return mult * (in_tok * P_IN + OUT * P_OUT) / 1e6
def monthly(option, rpd):
r = rpd * 30
if option == "prompt, few-shot": return r * per_request(BASE_IN + FEWSHOT_EXTRA)
if option == "RAG": return r * per_request(BASE_IN + RAG_EXTRA) + RAG_FIXED
if option == "fine-tuned": return r * per_request(BASE_IN, FT_MULT) + FT_FIXED
if option == "RAG + fine-tuned": return r * per_request(BASE_IN + RAG_EXTRA, FT_MULT) + RAG_FIXED + FT_FIXED
options = ["prompt, few-shot", "RAG", "fine-tuned", "RAG + fine-tuned"]
print("\nILLUSTRATIVE monthly cost, $ (assumed prices; not real quotes)")
print(f"{'requests/day':>13}" + "".join(f"{o:>19}" for o in options))
for rpd in (1_000, 10_000, 100_000, 1_000_000):
print(f"{rpd:>13,}" + "".join(f"{monthly(o, rpd):>19,.0f}" for o in options))
saving = per_request(BASE_IN + FEWSHOT_EXTRA) - per_request(BASE_IN, FT_MULT)
print(f"\nfine-tune saving per request vs few-shot: ${saving:.6f}")
print(f"break-even volume: {FT_FIXED / saving / 30:,.0f} requests/day (only if quality is equal; it often is not)")
# Output:
# [HR policy Q&A]
# - RAG: knowledge/freshness/citations/ACL live outside the weights
# [Invoice -> strict JSON]
# - Fine-tune candidate (LoRA/PEFT): prompt at 91% vs target 98%, 3000 examples, high volume
# [Invoice -> strict JSON (small)]
# - Improve prompt/examples and collect labelled data first; fine-tuning not yet justified
# [Brand voice + fresh docs]
# - RAG: knowledge/freshness/citations/ACL live outside the weights
# - Fine-tune candidate (LoRA/PEFT): prompt at 80% vs target 95%, 1500 examples, high volume
# - => COMBINE: RAG supplies the facts, the fine-tune fixes behaviour, prompt ties them together
# ['Teach' model our catalogue]
# - WARNING: fine-tuning to add facts is a weak choice (unreliable recall, stale, no citations/ACL); use RAG
# - RAG: knowledge/freshness/citations/ACL live outside the weights
#
# ILLUSTRATIVE monthly cost, $ (assumed prices; not real quotes)
# requests/day prompt, few-shot RAG fine-tuned RAG + fine-tuned
# 1,000 69 478 850 1,317
# 10,000 690 1,180 1,295 2,370
# 100,000 6,900 8,200 5,750 12,900
# 1,000,000 69,000 78,400 50,300 118,200
#
# fine-tune saving per request vs few-shot: $0.000650
# break-even volume: 41,026 requests/day (only if quality is equal; it often is not)
Things to notice: the helper refuses the "fine-tune the catalogue" proposal and points to RAG; the small-data version of the same invoice task does not qualify for a fine-tune; the "RAG + fine-tuned" column is the most expensive at every volume, which is the price of covering both knowledge and behaviour; and the fine-tune only wins on cost above roughly 41,000 requests per day under these invented assumptions, and only if quality is equal to the alternative, which is exactly what your eval set must prove. Try raising the fixed costs or the tuned-model price multiplier and watch the break-even move.
Q: What does each of prompting, RAG and fine-tuning change?
A: Prompting changes the instructions and examples on a request, RAG changes the knowledge supplied on a request, and fine-tuning changes the model's weights, hence its default behaviour on all requests. So facts, freshness and citations point to RAG, while consistent style, format or task behaviour that prompts cannot hold points to fine-tuning. The cheapest lever that fixes the measured failure wins.
Q: A stakeholder wants to fine-tune a model on the company wiki so it "knows" the company. What do you say?
A: I would push back: fine-tuning is unreliable for injecting facts, gives no citations, cannot enforce per-user permissions or delete a single fact, and is stale after the next wiki edit. I would build RAG over the wiki with permission filtering, and consider a fine-tune later only if a behaviour problem (tone, refusal style, format) remains after measuring.
Q: When is fine-tuning the right answer?
A: When the model's behaviour is what is wrong, prompting has plateaued on a measured eval set, and you have enough clean labelled examples and enough traffic to justify the pipeline. Typical cases are strict structured output in a specific style at high volume, a narrow classification with subtle boundaries, or distilling a large model's behaviour into a smaller, cheaper one. It should not be the first attempt.
Q: Explain LoRA to someone who knows neural networks but not fine-tuning.
A: Freeze the pretrained weight matrix W and learn a low-rank update B@A, where A and B are much smaller than W, so the output is Wx plus a scaled B(Ax). Only A and B are trained, which needs far less GPU memory and produces a small adapter file that can be merged into W or swapped per task. The catch is that the adapter depends on the exact base weights, and too low a rank can under-fit a hard task.
Q: How do cost and latency compare across the three?
A: Prompting and RAG add input tokens to every request (long few-shot examples, or k retrieved chunks), and RAG adds an embedding call and a search hop; both are cheap at low volume and scale with traffic. Fine-tuning front-loads data, training and evaluation effort, may shorten prompts, and can let a smaller model do the job, so it tends to pay off at high volume, provided quality is equal. I would compute break-even from real traffic and pricing rather than guess.
Q: How do you decide without running a huge experiment?
A: Build a small labelled evaluation set (50-200 realistic cases), measure the simplest prompt, classify the failures as knowledge or behaviour, and only then try the lever that matches. Changing one variable at a time and keeping the eval set out of any training data keeps the comparison honest. Skipping the eval set is the real mistake, because then nobody can tell whether any change helped.
Q: Can you combine them, and when should you?
A: Yes: RAG for correct, current, permissioned facts, a prompt for the task and output contract, and a fine-tune for a behaviour that will not stay stable in a prompt. I add a layer only when evaluation shows the earlier ones plateaued, because each layer adds a pipeline (index, prompts, dataset and adapter) to maintain and evaluate.
Q: What are the risks of fine-tuning you would raise in a design review?
A: Data quality and leakage (the model learns noise, or memorises the eval set), catastrophic forgetting of general abilities, tying an adapter to one base model version, sensitive data leaking into weights where it cannot be deleted, and the ongoing cost of retraining. I would mitigate with a clean split, a general regression suite, versioned datasets and adapters, and by keeping regulated or per-user data out of training.
Follow-up: Prompting gets you to 91% on the eval set and the target is 98%. What next?
A: First classify the remaining 9% of failures, because they may be a knowledge gap (RAG), ambiguous labels (fix the data), or a real behaviour gap. Then try a stronger base model, better examples or schema-constrained output, which are cheap. If a behavioural gap remains and I have thousands of clean examples and high volume, I would run a LoRA fine-tune and compare it against the prompt baseline on the same held-out set, including a general regression check.
Follow-up: Facts change daily but the tone must be consistent. Design it.
A: Keep facts in a retrieval index updated by an incremental ingestion pipeline, so freshness is a data operation, and put tone in the prompt or, if it drifts, in a small fine-tune trained only on behaviour examples with no facts in them. Evaluate retrieval and generation separately and pin versions of prompt, index and adapter so I can roll each back independently. The trade-off is running both pipelines, so I would start with prompt plus RAG and add the fine-tune only if tone stays inconsistent.
Follow-up: A provider upgrades the base model and your LoRA adapter stops working. Why, and what do you do?
A: The adapter's low-rank updates were learned against specific base weights, so they are not meaningful for a different base, and even a minor base update can change behaviour. I would pin the base version where possible, keep the training data and scripts versioned so retraining is a repeatable job, and re-run the eval and regression suites before switching. This recurring retraining cost is a genuine argument for prompting and RAG when they are good enough.
In system-design interviews this decision appears whenever an LLM feature is proposed, such as a support assistant, a document Q&A tool or a structured-extraction service. The default architecture is prompt plus RAG behind an API gateway, with fine-tuning introduced only when a measured behaviour gap and high volume justify it. Estimate with stated assumptions: with 1M requests per day, an extra 1,500 retrieved tokens per request is 1.5B additional input tokens per day, which is the number that makes prompt length a budget item and makes prompt caching, smaller retrieved context, or a smaller fine-tuned model worth discussing (the cost sketch above uses invented prices; use your provider's current price list in a real design). For extraction at that scale, moving instructions and examples into a LoRA-tuned smaller model can cut both cost and latency, but it adds a training pipeline, dataset versioning, evaluation gates, and adapter rollout (see Data & Model Versioning and Lineage and A/B Testing & Canary Deployments). Mention permissions: multi-tenant knowledge must be filtered at retrieval time, which fine-tuning cannot do. See Design a RAG-Based Support Chatbot for the full retrieval-first design.
The decision is a ladder of increasing commitment: prompting is cheap, reversible and limited; RAG buys freshness, citations and access control at the price of a retrieval pipeline whose quality caps the answer; fine-tuning buys deep, consistent behaviour and possibly lower per-request cost at the price of data work, training, retraining on every base-model change, opacity and the risk of degrading general ability. Each is the wrong choice in a recognisable situation: fine-tuning for facts, RAG for a pure formatting problem, prompting alone when the prompt has become thousands of tokens of examples sent on every call, or any of them without an evaluation set that shows a gap. The pragmatic rule is to climb only as far as measurement forces you, keep versions of prompts, indexes, datasets and adapters so each layer can be rolled back independently, and treat combining as an earned outcome rather than a starting point.