Version control
Content-addressed, immutable versions identified by hash, full history you can diff any-to-any, movable deploy labels, instant rollback, and the precedence the SDK follows to resolve which version your app gets.
Versions are the history; labels are the deployment. Every save is an immutable, content-addressed snapshot you can pin forever by its hash, and shipping is moving a pointer — never editing in place. This page is the mechanics of both. For the visual timeline, see Concepts.
Versions are content-addressed
Every Save version cuts an immutable snapshot of the whole content together — messages, model config, tools, output format, and variable declarations. You never edit a version; you save a new one. Trodo's history works like git:
Each version's identity is its hash — the id you see in the activity timeline, copy with one click, and pass to get() to pin that exact snapshot forever. Reverting to older content produces a new version with a new hash, so nothing is ever lost, and the dashboard can tell you when two versions are byte-identical when you diff them.
The hash is the only thing you need: it's the one identifier that shows up everywhere — the timeline, the copy button, and the version fetch option. (Internally each version also has a sequence number and a couple of derived hashes that link it to its history, but those are implementation detail — you'll never need them to use a prompt.)
- Git-style dedup. Saving content identical to the current version is a no-op — like an empty git commit — no new version is cut, and the save dialog tells you so. Reverting to an older version's content is not a no-op: it has a different parent, so it's a real, recorded version with its own hash.
- Full history. Every version is kept, forever. There is no retention limit — the complete record is what lets you diff any two versions against each other and pin an exact hash years later.
Diff any two versions
The activity timeline lists every version by hash, with who made the change and their commit message. Open any version to view its full contents, load it into the editor, or diff it against any other version — pick the other version right there and the line-by-line diff renders inline.
Labels point at one version
A label is a movable pointer that names exactly one version of a prompt. production and staging exist by default; add your own (like canary) from the save dialog.
Assigning a label moves it off whichever version held it before — atomically, so production is never ambiguous or briefly pointing at two versions. That single guarantee is what makes deployment and rollback trustworthy:
- Deploy = move
productionto a newer version. - Roll back = move
productionback to the older one. The old version is still there — full history is kept.
Rollback is instant because nothing is rebuilt: the old version already exists, and the label move propagates to running apps within one cache window. See Caching & availability.
How the SDK resolves a version
When you fetch a prompt, the version is chosen by this precedence:
- Explicit
version— an exact pin, wins over everything. label— the version that label points at.- Neither — the
productionlabel, falling back to the latest version only if noproductionlabel exists.
version and label are mutually exclusive — passing both raises.
version takes the hash you copy from the timeline — full, or an unambiguous short prefix like a3f9c2. It always names the exact same immutable content, which is what makes it reproducible.
await trodo.prompts.get('refund-agent'); // production (or latest if unlabelled)
await trodo.prompts.get('refund-agent', { label: 'staging' });
await trodo.prompts.get('refund-agent', { version: 'a3f9c2' }); // pin by hash (short prefix ok)
// The resolved version's hash is on the returned prompt:
const p = await trodo.prompts.get('refund-agent');
p.versionHash; // copy this to pin this exact version elsewheretrodo.get_prompt("refund-agent") # production (or latest if unlabelled)
trodo.get_prompt("refund-agent", label="staging")
trodo.get_prompt("refund-agent", version="a3f9c2") # pin by hash (short prefix ok)
p = trodo.get_prompt("refund-agent")
p.version_hash # copy this to pin this exact version elsewherePin with version or label, never both. version (a hash) is for reproducibility — a specific eval, a frozen integration; label is for deployment — follow whatever is live for that channel.
A/B testing prompts
Because a label resolves server-side on every fetch, you can split traffic without any built-in splitter: label two versions prod-a and prod-b, fetch both, and pick one per request in your app.
const [a, b] = await Promise.all([
trodo.prompts.get('refund-agent', { label: 'prod-a' }),
trodo.prompts.get('refund-agent', { label: 'prod-b' }),
]);
const chosen = Math.random() < 0.5 ? a : b;a = trodo.get_prompt("refund-agent", label="prod-a")
b = trodo.get_prompt("refund-agent", label="prod-b")
chosen = a if random.random() < 0.5 else bCompare the two in Experiments to decide which wins, then point production at it.
Tags vs labels
Both label a version, but they answer different questions:
| Labels | Tags | |
|---|---|---|
| Scope | Per-version — point at exactly one version. | Per-prompt — shared across every version. |
| Movable | Yes — moving one is how you deploy. | No — they categorise the prompt itself. |
| Purpose | Select which version is live (production, staging, canary). | Find and group prompts (support, billing, experimental). |
In one line: labels select a version to deploy; tags categorise a prompt. Filtering the prompts list by tag has nothing to do with which version is live.
Where to go next
Initialization & the core calls
Install the SDK, call init once, then use the two calls you'll reach for everywhere: get to fetch a prompt by name, and compile to fill its variables locally.
Access management
Who can view and edit a prompt: every teammate can view, the creator owns it, and the owner invites other team members as editors. How to grant and revoke edit access.