Git blame for prompts: incident archaeology, git-native prompt versioning, and a no-SaaS production runtime for LLM workflows
Project description
๐ llmhq-promptops
Git blame for prompts. When prod breaks, know exactly which prompt version was running, in seconds. No SaaS, no vendor lock-in โ all in your own git.
llmhq-promptops is a prompt management framework that records every deploy, builds an immutable snapshot at CI time, and lets you ask promptops blame --at <timestamp> to find out what was running when an incident happened. Built for teams who already use git for everything else.
โจ Key Features
- ๐ Incident archaeology โ
promptops blame --at <ts>resolves "what prompt was running in prod at that moment" by composing the deploy log with git history. - ๐ณ Production runtime without
.git/โpromptops snapshot buildwrites a self-contained.promptops/snapshot.json. Ship that in your Docker image;AutoResolverpicks it up automatically. - ๐ Deploy event log โ append-only
.promptops/deploys.jsonlrecords every deploy with provenance (env, commit, actor, metadata). Committed to git alongside your prompts. - ๐ Automated git versioning (opt-in) โ after
promptops hooks install, a pre-commit hook detects prompt changes, bumps semver in YAML metadata, and re-stages.promptops init reponever touches.git/hooks/on its own. - ๐ Version-aware testing โ
:unstaged,:working,:latest,commit-<sha>, or any tag. - ๐ Python SDK โ
PromptManager(resolver=AutoResolver())works the same in dev (uses git) and prod (uses snapshot).
โก Quick Start
Two paths depending on what you want to do.
Path A โ 60-second demo on a sample repo
Walk through the incident-archaeology flow on a pre-baked repo, no setup of your own.
pip install llmhq-promptops
git clone https://github.com/llmhq-hub/promptops.git
cd promptops/examples/incident-archaeology-demo
./setup.sh # builds a tmp git repo with pre-baked history
cd /tmp/promptops-demo
# 1. Look at what's been deployed
promptops deploy list
# 2. "Production broke at 10:00 UTC. What was running?"
promptops blame --at 2026-05-20T10:00:00Z
# 3. Same question, full text of one prompt
promptops blame --at 2026-05-20T10:00:00Z --prompt summarizer
That's the hero use case. Three commands, one provable answer.
Full walkthrough + screencast script:
examples/incident-archaeology-demo/README.md
Path B โ Try it on your own repo
For when you want to add this to a real project.
pip install llmhq-promptops
cd <your-project> # any git repo
promptops init repo # creates .promptops/ (does NOT touch git hooks)
# Scaffold your first prompt โ .promptops/prompts/user-onboarding.yaml
promptops create prompt user-onboarding
# Render it (the starter template uses {{ user_input }}; :unstaged reads
# your uncommitted working-tree edits)
python -c "from llmhq_promptops import get_prompt; print(get_prompt('user-onboarding:unstaged', {'user_input': 'World'}))"
# Record a deploy and look it up later
promptops deploy event --env prod -m release=v0.1.0
promptops blame --at now
# Optional: enable auto-versioning on commit (opt-in since v0.4.0)
promptops hooks install
๐ Usage Examples
Recommended production setup
from llmhq_promptops import PromptManager, AutoResolver
# Same code in dev (uses .git/) and prod (uses .promptops/snapshot.json)
manager = PromptManager(resolver=AutoResolver(repo_path="."))
prompt = manager.get_prompt("user-onboarding", {"name": "Alice"})
Resolving with full provenance
When you need to know which version actually ran (for logging, observability):
resolved = manager.resolve("user-onboarding")
# resolved.text โ raw YAML
# resolved.version โ e.g. "v1.2.3" or "commit-abc12345"
# resolved.commit โ full 40-char SHA
# resolved.source โ "git" or "snapshot"
# resolved.resolved_at โ tz-aware UTC datetime
log_to_observability({
"prompt": resolved.prompt_id,
"version": resolved.version,
"commit": resolved.commit,
"source": resolved.source,
})
Specific versions
from llmhq_promptops import get_prompt
prompt = get_prompt("user-onboarding") # smart default
prompt = get_prompt("user-onboarding:v1.2.1") # tagged version
prompt = get_prompt("user-onboarding:commit-abc12345") # untagged commit
prompt = get_prompt("user-onboarding:unstaged") # working tree
prompt = get_prompt("user-onboarding:working") # HEAD (committed)
Using with LLM frameworks
from llmhq_promptops import get_prompt
prompt = get_prompt("user-onboarding:working", {"name": "John"})
# OpenAI
import openai
openai.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}],
)
# Anthropic
import anthropic
anthropic.Anthropic().messages.create(
model="claude-3-sonnet-20240229",
messages=[{"role": "user", "content": prompt}],
)
๐ง CLI Commands
| Command group | What it does |
|---|---|
promptops init repo |
Create .promptops/ directory structure |
promptops hooks install |
Opt-in auto-versioning hooks (pre-commit + post-commit) |
promptops create prompt <id> |
Scaffold a new prompt YAML |
promptops render prompt <file> |
Render a prompt with variables |
promptops test status / test diff / test runtest |
Version-aware prompt testing |
promptops deploy event --env <e> |
Append a deploy event to .promptops/deploys.jsonl |
promptops deploy list |
Show recent deploys, newest first |
promptops snapshot build |
Write .promptops/snapshot.json (production-runtime artifact) |
promptops snapshot inspect |
Print contents of a snapshot |
promptops blame --at <ts> |
Incident archaeology: what was running when? |
promptops backfill-deploys --from-git-log |
Seed the deploy log from existing git history |
promptops migrate tag-history |
Create per-prompt git tags for historical commits |
Full help: promptops --help or promptops <command> --help.
๐ Project Structure
.promptops/
โโโ prompts/ # YAML prompt templates (auto-versioned)
โโโ deploys.jsonl # Append-only deploy event log (committed to git)
โโโ snapshot.json # Production-runtime snapshot (built at CI time)
โโโ config.yaml # Hook + tool configuration
โโโ tests/ # Test datasets
โโโ results/ # Test reports
โโโ logs/ # Audit logs
โโโ reports/ # Auto-generated version change notes
๐ Prompt Schema
PromptOps reads two interchangeable layouts. promptops create prompt
scaffolds the compact prompt: form; the richer metadata: form below
adds tags, multiple models, and inline tests. Both are parsed the same way.
Extended (metadata:) format:
# .promptops/prompts/user-onboarding.yaml
metadata:
id: user-onboarding
version: "1.2.0" # auto-incremented once you run `promptops hooks install`
description: "User onboarding welcome message"
tags: ["onboarding", "welcome"]
models: # NOTE: nested under metadata
default: gpt-4-turbo
supported: [gpt-4-turbo, claude-3-sonnet, llama2-70b]
template: |
Welcome {{ user_name }}!
Available features:
{% for feature in features %}
- {{ feature }}
{% endfor %}
variables:
user_name: {type: string, required: true}
features: {type: list, default: ["Browse", "Purchase"]}
tests:
- dataset: .promptops/tests/onboarding-data.json
metrics: {max_tokens: 150, min_relevance: 0.8}
Compact (prompt:) format โ what promptops create prompt generates:
# .promptops/prompts/user-onboarding.yaml
prompt:
id: user-onboarding
description: "User onboarding welcome message"
model: gpt-4-turbo
template: "Welcome {{ user_name }}!"
variables:
user_name: {type: string, required: true}
๐ Automated Versioning
Automated versioning is opt-in โ enable it once per repo with
promptops hooks install. (Since v0.4.0, promptops init repo never
modifies .git/hooks/ on its own.) After that, semantic version rules
applied by the pre-commit hook:
- PATCH (1.0.0 โ 1.0.1) โ template content changes only
- MINOR (1.0.0 โ 1.1.0) โ new variables added (backwards compatible)
- MAJOR (1.0.0 โ 2.0.0) โ required variables removed (breaking change)
Workflow (after promptops hooks install):
- Edit a prompt โ changes in working tree
promptops test runtest --prompt name:unstaged(test before commit)git add+git commitโ pre-commit hook bumps version and re-stages- Post-commit hook tags the new version and generates a changelog entry
Zero manual version management.
๐ Version References
| Reference | Resolves to | Use case |
|---|---|---|
prompt-name |
Smart default (unstaged if different, else working) | Development |
:unstaged |
Uncommitted working-tree content | Testing changes before commit |
:working / :latest / :head |
Latest committed (HEAD) | Production |
:v1.2.3 |
Specific semver tag | Reproducible builds |
:commit-abc12345 |
Immutable commit reference for untagged commits | Incident archaeology |
๐ ๏ธ Requirements
- Python 3.10+
- Git (for versioning at dev time โ not required at production runtime if you ship
snapshot.json) - YAML + Jinja2 (auto-installed)
๐ Dependencies
- Core: Typer (CLI), Jinja2 (templates), PyYAML (parsing), GitPython (git access)
๐ค Contributing
See CONTRIBUTING.md.
git clone https://github.com/llmhq-hub/promptops.git
cd promptops
python -m venv venv
source venv/bin/activate
pip install -e .
pip install pytest
pytest tests/
๐ License
MIT โ see LICENSE.
๐ Support
- Issues: github.com/llmhq-hub/promptops/issues
- Discussions: github.com/llmhq-hub/promptops/discussions
- Releases & changelog: github.com/llmhq-hub/promptops/releases
Made with โค๏ธ for teams shipping LLMs in production.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file llmhq_promptops-0.4.0.tar.gz.
File metadata
- Download URL: llmhq_promptops-0.4.0.tar.gz
- Upload date:
- Size: 95.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fe23ca87bed14fbad3b147b14f61082f5b750126fdbc72c951cb11615a2edf58
|
|
| MD5 |
7c9d5202a866e0e881bcdfc70244ba4b
|
|
| BLAKE2b-256 |
f82167a61499ccfe8b45b526be0c9e52162cfa89408f9730ea64f97082649ffd
|
File details
Details for the file llmhq_promptops-0.4.0-py3-none-any.whl.
File metadata
- Download URL: llmhq_promptops-0.4.0-py3-none-any.whl
- Upload date:
- Size: 73.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8e7d79b06cc17619da14b08873a05de5292f3aeaa41db65749e3072253b3c9d3
|
|
| MD5 |
964b21cd05f29ba0e5c069210eec8291
|
|
| BLAKE2b-256 |
e1409da4b9bd20225ee88047f10f9dab4c451ed2dd8170dd8127519d63629325
|