Context Assembly Is a Build Step: Versioning the Prompt You Actually Send A developer argues that context assembly should be treated as a versioned build step, proposing a content-hash fingerprint over the inputs that affect model behaviour—model ID, template version, snapshot ID and rendered prompt—logged alongside every trace so quality regressions can be diagnosed without a code diff. The approach tags each context section with a trust level and eviction priority, screens untrusted retrieval and tool output for instruction-like text, and raises a hard error when a pinned section exceeds the token budget rather than silently trimming it. You review a 30-line template diff. Production sends 40,000 tokens assembled at request time from six sources, four of which changed this week and none of which appear in the pull request. The diff you approved is not the change you shipped. This is the part of LLMOps that keeps biting teams after they have evals, tracing, and a gateway in place. The template is versioned in git. The context is assembled by code paths nobody treats as release artifacts. List what actually goes into a request for a typical RAG-plus-tools system: Each is a dependency with its own version and its own deploy cadence. The retrieved chunks change when someone reindexes. The tool schema changes when a backend engineer renames a parameter and rewrites the description. Neither triggers a review of the prompt. So when quality drops on Tuesday and no code shipped, there is no diff to look at. That is the failure this whole post is about. Compute a content hash over the inputs that can change model behaviour, and log it with every trace: Store fingerprints always. Store full rendered prompts sampled or redacted, depending on your data rules — full-text retention is a compliance decision, not an observability one. The fingerprint is small, cheap, and answers "what changed" during an incident in one query. The trade-off: a fingerprint that includes a timestamp, a request id, or a per-user greeting has near-zero collision rate and zero diagnostic value. Hash the parts that affect generation, not the metadata around it. I have watched a team spend a day on "why is our prompt hash changing every request" when the answer was a locale string. You do not need a framework for this. You need three properties: every section carries a trust level, every section carries an eviction priority, and over-budget on a pinned section is a hard error rather than a silent trim. python from dataclasses import dataclass, field from hashlib import sha256 import re @dataclass class Section: name: str text: str trusted: bool False for retrieval, tool output, user uploads priority: int higher survives truncation pinned: bool = False never evict; over budget means fail loudly INSTRUCTION LIKE = re.compile r" ignore all ? previous|prior instructions|you are now|system prompt ", re.IGNORECASE, def screen text: str - str: Cheap heuristic, not a guarantee. Log hits and route to a stricter path. return INSTRUCTION LIKE.sub " filtered ", text def count tokens s: str - int: Swap in tiktoken/the provider counter. One counter per model family. return len s // 4 @dataclass class Assembled: prompt: str fingerprint: str dropped: list str = field default factory=list def assemble sections, budget, model id, template version, snapshot id : ordered = sorted sections, key=lambda s: s.pinned, s.priority , reverse=True kept, dropped, used = , , 0 for s in ordered: body = f"<{s.name} trust={'high' if s.trusted else 'low'} \n" body += s.text if s.trusted else screen s.text + f"\n