DEV Community

Cover image for PromptLedger: Local-first prompt version control
Ertugrul
Ertugrul

Posted on

PromptLedger: Local-first prompt version control

Treat prompts like code: version them locally, diff them, label releases, and inspect history — without any backend services.


Prompt engineering has quietly become production work. Prompts evolve, regress, get tuned for edge cases, and eventually land in “prod”. Yet most teams still track them in scratch files, notebooks, or chat logs.

PromptLedger is a deliberately small tool that fixes this by treating prompts like code: every change is versioned, diffable, and labeled — all stored locally in a single SQLite database.

This post is a technical deep dive into how PromptLedger works, what data it stores, and why its design is intentionally boring.


Design goals

PromptLedger is built around a few strict constraints:

  • Local-first: no server, no SaaS, no telemetry
  • Single source of truth: one SQLite file
  • Git-aware: works naturally inside repositories
  • Read-only UI: all writes go through the CLI/API
  • Deterministic output: exports are stable and diffable

If you are looking for an agent framework or a prompt playground, this is not it.


tecture overview

The codebase is intentionally small and split by responsibility:

  • cli.py – argument parsing and user-facing commands
  • core.py – domain logic (PromptLedger class)
  • db.py – SQLite connection, schema, migrations
  • ui.py – Streamlit-based read-only viewer

There are no background services and no long-running processes. PromptLedger runs when you invoke it and exits.

Architecture


Storage and path resolution

PromptLedger always stores data locally in promptledger.db.

Resolution rules are deterministic:

  • Inside a git repo: <repo_root>/.promptledger/promptledger.db
  • Outside git: <cwd>/.promptledger/promptledger.db
  • Override with PROMPTLEDGER_HOME=/custom/path
  • Hard override via PromptLedger(db_path="/abs/path/to.db")

This avoids accidental duplication when running commands from nested directories and keeps prompt data out of git history by default.


SQLite schema

Two tables make up the core of PromptLedger:

  • prompt_versions: immutable prompt history
  • labels: mutable pointers to specific versions

Each prompt version stores:

  • prompt_id
  • version
  • content (the prompt text)
  • content_hash (SHA-256)
  • created_at (UTC ISO timestamp)
  • optional metadata: reason, author, tags, env, metrics

Labels store:

  • prompt_id
  • label (e.g. prod, staging)
  • version
  • updated_at

This separation lets you move release pointers without creating new versions.


Versioning algorithm

When you add a prompt, PromptLedger:

  1. Normalizes newlines (CRLF/CR → LF)
  2. Hashes the normalized content
  3. Fetches the latest version for that prompt
  4. Skips insertion if the hash matches (no-op)
  5. Otherwise inserts a new version with incremented number

This keeps history clean and avoids formatting-only noise.


Newline normalization

Cross-platform newline differences are a common source of useless diffs.

PromptLedger normalizes line endings before hashing and diffing, which means:

  • Windows CRLF and Unix LF content are treated as identical
  • Diff output focuses on real textual changes

Metadata model

PromptLedger supports lightweight metadata for each version:

  • reason – why the prompt changed
  • author – who made the change
  • tags – arbitrary labels for grouping
  • envdev, staging, prod
  • metrics – JSON blob (accuracy, latency, cost, ratings)

This turns raw text history into something closer to an audit trail.


Labels: release-style pointers

Labels are the feature that pushes PromptLedger beyond simple history tracking.

Think of labels like git tags that move:

promptledger label set --id onboarding --version 7 --name prod
promptledger label set --id onboarding --version 9 --name staging
Enter fullscreen mode Exit fullscreen mode

Now you can answer questions like:

  • What prompt is currently in production?
  • Which version was deployed last week?

Without copying or duplicating prompt content.


CLI workflow

Core commands:

  • init – create DB and .gitignore entry
  • add – add or skip a version
  • list – list versions
  • show – show content + metadata
  • diff – unified diff between versions
  • search – content + metadata search
  • export – deterministic JSONL / CSV
  • label – manage release pointers
  • ui – launch Streamlit viewer

A typical flow:

promptledger init
promptledger add --id demo --text "Hello"
promptledger add --id demo --text "Hello World"
promptledger diff --id demo --from 1 --to 2
promptledger label set --id demo --version 2 --name prod
Enter fullscreen mode Exit fullscreen mode

Streamlit UI (read-only)

The UI is intentionally non-destructive:

  • Timeline view of versions
  • Filters by prompt id, tags, env
  • Full content preview
  • Unified diff and side-by-side comparison

All writes remain in the CLI/API path.


Export and determinism

Exports are designed for reproducibility:

  • JSONL uses sorted keys
  • CSV has a fixed column order
  • Repeated exports of the same data are byte-for-byte identical

This makes PromptLedger suitable for audits, reviews, and downstream tooling.


Security notes

PromptLedger never sends data anywhere.

It includes a minimal warning for common secret patterns (sk-, AKIA, -----BEGIN). This is advisory only and can be disabled. The responsibility remains with the user to avoid storing secrets in prompt text.


What PromptLedger is not

  • Not an LLM framework
  • Not an agent system
  • Not a hosted service
  • Not a playground

It is a local ledger for prompt evolution.


Workflow for those interested

Closing

If your prompts matter enough to review, promote, and roll back, they matter enough to version properly.

PromptLedger keeps that history local, inspectable, and boring — which is exactly the point.

Contact and Links

Pypi : Pypi
Github : Github
My Linkedin : Linkedin
My Website : Website

Top comments (0)