PromptOps
Git-native prompt versioning for production LLM agents
Why PromptOps?
Your agent's behavior is defined by prompt text — but most teams ship prompts as raw strings baked into code, with no version history, no rollback, and no way to test a change before it hits production.
PromptOps turns prompts into first-class versioned artifacts. Opt in to the git
hooks once and every git commit grades the change and tags it with a
semantic version. Reference any version in code by name: :latest for
production, :v1.2.0 for a specific release, or :unstaged
to test uncommitted changes without touching anything live.
And when something does break, promptops blame --at "2026-05-20T14:32:00Z"
answers "what was this prompt's text when the incident happened", from the deploy log
and git history. No more "what prompt was running last Tuesday?"
Features
Incident archaeology
blame --at <timestamp> answers what text was live when something broke, from the deploy log and git history.
Uncommitted Change Testing
Test prompts instantly with :unstaged, :working, :latest references before committing.
Semver impact at review time
promptops test diff grades a change MAJOR, MINOR or PATCH from the variable signature, and --exit-code gates CI on the verdict.
Opt-in git hooks
One promptops hooks install and every commit versions your prompts, graded by the same engine as test diff. init never writes to .git/hooks/ unless you ask.
Installation
pip install llmhq-promptops
Quick Start
# Create .promptops/ in your repo. Does NOT touch .git/hooks/.
promptops init repo
# Opt in to auto-versioning on commit (a deliberate second step)
promptops hooks install
# Create a new prompt template
promptops create prompt welcome-message
# Test uncommitted changes
promptops test runtest --prompt welcome-message:unstaged
# Check status of all prompts
promptops test status
# Is this setup sane? Checks hooks can actually run.
promptops doctor
Python SDK
from llmhq_promptops import get_prompt
# Smart default (unstaged if different, else working)
prompt = get_prompt("user-onboarding")
# Specific version references
prompt = get_prompt("user-onboarding:v1.2.1")
prompt = get_prompt("user-onboarding:unstaged")
prompt = get_prompt("user-onboarding:working")
# With variables
rendered = get_prompt("user-onboarding", {
"user_name": "Alice",
"plan": "Pro"
})
Version References
Every prompt can be resolved at any point in its history.
| Reference | Resolves To | Use Case |
|---|---|---|
prompt-name | Smart default (unstaged if different, else working) | Development |
:unstaged | Uncommitted changes in working directory | Testing changes |
:working | Latest committed version (HEAD) | Production |
:latest | Alias for :working | Production |
:v1.2.3 | Specific semantic version | Reproducible builds |
Semantic Versioning Rules
Impact is derived from the prompt's variable signature and its declared models, and from nothing else. Prose is never graded: the variable interface is the only part of a prompt with a caller-facing contract, and a rule that guessed at intent would produce annotations nobody trusts.
| Bump | Trigger | Example |
|---|---|---|
| MAJOR | Required variable added, removed, renamed or retyped; an optional variable promoted to required; a declared model dropped | 1.2.0 → 2.0.0 |
| MINOR | Optional variable added or removed; a required variable relaxed to optional; a model added; an optional default changed | 1.2.0 → 1.3.0 |
| PATCH | Prose changed, signature identical | 1.2.0 → 1.2.1 |
| none | Nothing a caller depends on moved | 1.2.0 → 1.2.0 |
The git hook and promptops test diff use the same grader, so a
change your CI blocks as breaking cannot be stamped with a minor version on commit:
git commit -m "feat: tier-aware refund policy"
[promptops] Impact: MAJOR
[promptops] Version: v1.2.0 → v2.0.0
[promptops] • required variable 'account_tier' added
[promptops] Updated version to v2.0.0
[promptops] Created tag: prompt-refund-policy-v2.0.0
A brand-new prompt keeps the version you declared: there is nothing for a first
commit to be backward incompatible with. The bump rewrites the version line and
nothing else, so comments and template: | formatting survive.
Upgrading? The git hooks were broken from v0.2.0 through v0.5.0 and are repaired in v0.6.0. If you installed them on an earlier release and versioning never seemed to fire, that is why. See upgrading to v0.6.0.
Framework Integration
Works with any LLM framework. Get a versioned prompt, pass it to your provider.
from llmhq_promptops import get_prompt
prompt_text = get_prompt("user-onboarding:working", {
"user_name": "John",
"plan": "Enterprise"
})
# Use with OpenAI
import openai
response = openai.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt_text}]
)
# Use with Anthropic
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
messages=[{"role": "user", "content": prompt_text}]
)
Requirements
- Python 3.10+
- Git, for the CLI's git-backed commands. The SDK's snapshot path needs neither the
.git/directory nor the git binary, so it runs in apython:3.11-slimimage. - Dependencies: Typer, Click, Jinja2, PyYAML, GitPython
See it in action
Watch PromptOps version prompts and feed them into ReleaseOps bundles.