---
title: How immutable pipeline versions work
description: Why a pipeline change is always a new carve, never an edit — and how that makes every past run reproducible and every rollback a pointer move.
locale: en
version: 0.2.0
canonical: https://enterium-developer-docs.pages.dev/developers/how-it-works/immutable-pipeline-versions/
---

> Documentation index: https://enterium-developer-docs.pages.dev/developers/llms.txt — every page of this portal has a Markdown twin at <route>.md; the frozen API contract is at https://enterium-developer-docs.pages.dev/developers/api/openapi.json.

**The problem:** an LLM content pipeline changes constantly — prompts, gates,
step order. If the code that produced yesterday's thousand articles can be
edited in place, you lose the ability to answer the only question that
matters when quality regresses: *what exactly produced this article?*

The naive answer — "check git history" — fails operationally: a running
fleet doesn't execute git history, it executes whatever is on disk, and a
"small fix" deployed at noon silently changes what the nightly run does.

## Layer 1 — a version is a directory, not a ref

Every pipeline version is a **complete, immutable code directory**:
`pipelines/{id}/{variant}/{version}/` with its own `manifest.yaml`. Once
registered, the directory is never edited. A change — however small —
becomes a **carve**: byte-copy the parent version, apply the change,
register the new version in the catalog.

```mermaid
flowchart LR
    A[v3.1.16<br/>frozen] -- carve: copy + patch --> B[v3.1.17<br/>frozen]
    B -- carve --> C[v3.1.18<br/>latest]
    A -.rollback target.-> CAT[catalog.yaml<br/>latest pointer]
    C -.-> CAT
```

The manifest is the contract: the dispatcher reads it — steps, required
environment, entrypoints — and never introspects the code. Old versions
stay listed and stay runnable.

## Layer 2 — resolution and pins

Which version runs is resolved at dispatch time:
**explicit request → per-contract pin → catalog `latest`**. The nightly
production run additionally pins its Collect/Generate versions explicitly,
which turns promotion into a deliberate two-step: land the version, then
move the pin. A regression discovered between those steps never touches the
fleet.

## Layer 3 — what this buys

- **Forensics.** Every run records its version; the version can never
  change under it. "Which code wrote this sentence" has an exact answer,
  months later.
- **Instant rollback.** Move the pointer back. No revert commits, no
  redeploys of changed files, no "mostly the same" ambiguity.
- **Safe parallelism.** Multiple agents carve future versions while
  production stays pinned; nothing they do can destabilize the running
  code.
- **Honest A/B.** Two versions coexisting in the catalog can run against
  live contracts and be compared on output, because neither is a moving
  target.

## Trade-offs

Immutability accumulates directories. The retention policy keeps the HEAD
tree small — live-pinned versions, `latest` and its predecessor, and named
rollback targets survive; everything else is deleted from the tree by an
explicit, owner-acknowledged cleanup (history stays in git, and every
removed version leaves a tag it can be restored from). Version numbering
carries meaning: a new **major** marks an architectural generation (for
Collect, v3.x is the vector-ranking generation; v2.x — the keyword one —
remains listed as the emergency fallback).

## See it in two minutes

Fetch the catalog surface and the current pins through the API, or simply
read the machine-readable manifest of this documentation build — the same
philosophy applies to these docs: `versions.json` +
`manifest.json` with content hashes, at the root of this portal.
