Skip to main content

vmn

A version number that can rebuild your repo.

Language-agnostic semantic versioning where a version is a restorable state, not just a label.
Designed for AI-assisted development. Versions live in git tags — no database, no server, no lock-in.

PyPI version PyPI downloads GitHub stars Semver Conventional Commits License

Works with any language in a git repository — vmn versions the repo, not the build system.


pip install vmn

vmn stamp -r patch my_app        # => 0.0.1   (auto-initializes, no setup)

# ...six months and 400 commits later, prod is broken on 0.0.1

vmn goto -v 0.0.1 my_app         # your repo AND every dependency repo,
                                 # exactly as they were when 0.0.1 shipped

Other versioning tools produce a string. vmn produces a state you can return to — across every repository your product spans, not just the one you run it in.

The same property is what makes vmn a natural fit for AI-assisted development: give each agent an isolated workspace, capture what it produced, and roll back what didn't work.

All state is stored as plain YAML in git annotated tag messages. Remove vmn and the tags remain fully readable.

vmn has been in continuous development since 2019 and versions its own releases. It is in daily production use by teams shipping multi-repository products, where recovering from a bad release means restoring every repo to a known state, not just one.


Install · The idea · AI agents · Why vmn · Commands · Config · Experiments · Web UI · Islands · CI · Help · Migrate


📦 Install

pip install vmn          # or: pipx install vmn / uvx vmn
pip install "vmn[ui]"    # + the web dashboard

Requirements: Python 3.8+, Git 2.10+ (2.17+ recommended). Linux, macOS, Windows/WSL. Nothing platform-specific to configure.

vmn is a Python CLI, but the project it versions can be in any language — it operates on git tags, not on your build system.

Try it in 30 seconds (copy-paste, no existing repo needed)
mkdir remote && cd remote && git init --bare && cd ..
git clone ./remote ./local && cd local
echo a >> ./a.txt && git add ./a.txt && git commit -m "first commit" && git push origin master

vmn stamp -r patch my_app   # => 0.0.1

echo b >> ./a.txt && git add ./a.txt && git commit -m "feat: add b" && git push origin master
vmn stamp -r patch my_app   # => 0.0.2

git tag -n1 my_app_0.0.2    # the version metadata is right there in the tag

No vmn init needed — the first vmn stamp initializes the repo and the app. Works in CI, in shallow clones, and fully offline.

Shell completion (bash / zsh / fish / tcsh)
vmn --completion-install      # auto-detects your shell, idempotent
vmn --completion              # or just print the script, change nothing
vmn --completion-uninstall    # remove it again

All three accept an explicit shell name if auto-detection picks the wrong one. After installing, restart your shell. vmn <TAB> lists commands, vmn stamp <TAB> suggests your tracked app names, and vmn stamp -r <TAB> offers the release modes.


💡 The idea

Most tools treat a version as a name for a moment. vmn treats it as a handle on a state — and once you have that, the same primitive answers four different problems.

Granularity Command What gets captured
Released state vmn stampvmn goto committed code + the exact commit of every dependency repo
Working state vmn snapshot ↑ plus uncommitted changes, unpushed commits, untracked files
Measured state vmn exp ↑ plus metrics, params, artifacts, and a run log
Parallel state vmn worktrees any of the above, checked out as a separate tree instead of replacing your current one

Each of the first three rows extends the one above it; the fourth materializes any of them as a separate tree. These are not four separate features: vmn exp is built on the snapshot primitive, and snapshots share the version grammar of stamps. That is why vmn goto -v 1.2.0-dev.a1b2c3d.e4f5g6h works — a snapshot is a version.

What this enables

State recovery across repos. If your product spans five git repos, vmn stamp records every dependency's commit hash into the tag. vmn goto restores all of them, in parallel, cloning any that are missing. Reproducing a six-month-old bug becomes one command instead of an afternoon of digging through CI logs.

Uncommitted work becomes addressable. git stash is unnamed, local, and single-repo. A WIP commit pollutes history. A snapshot turns your exact working state — modified files, local commits, untracked files, across every dependency — into a version string you can name, diff, share, and restore.

Experiment tracking with no server. An experiment is a snapshot plus an append-only metrics log. That's the whole design. No tracking server, no database, no cloud account — and unlike every dedicated tracker, the code state is captured, not just the numbers.

Microservice topology. Version services independently under one root app. Each service keeps its own semver; the root gets a monotonic integer that ticks on every child stamp — one number for "what changed last" across the whole platform.

Version formats vmn understands
1.6.0                        # release
1.6.0-rc.23                  # prerelease
1.6.7.4                      # hotfix — an optional 4th segment
1.6.0-rc.23+build01.Info     # build metadata
1.6.0-dev.a1b2c3d.e4f5g6h    # dev snapshot (commit hash + diff hash)

Standard Semver 2.0, plus two additions. The hotfix segment lets you ship an emergency fix without consuming a patch number, keeping the release train on schedule. The dev snapshot is content-addressed: identical code always produces the identical version string, so re-snapshotting an unchanged tree returns the same version instead of creating a duplicate.


🤖 Built for AI-assisted development

Coding agents are fast, parallel, and occasionally destructive. Each of those properties creates a state problem — and state is what vmn manages.

Problem vmn's answer
The agent doesn't know your versioning conventions vmn skill --install — writes vmn's usage instructions into the agent's instruction file
Multiple agents editing one working tree overwrite each other vmn worktrees — one isolated island each, dependencies included
An agent corrupted the tree and you need the last good state vmn snapshot / vmn goto
An agent shouldn't be cutting releases --no-stamp islands, where stamping is refused
Identifying which of many runs produced the good result vmn exp — every metric anchored to the tree that produced it

The tool documents itself to the agent. vmn skill --install writes vmn usage instructions into your agent's instruction file — .claude/skills/vmn/SKILL.md, .cursorrules, or AGENTS.md. The agent learns your versioning workflow from the tool that implements it rather than inferring it from the repo. The cursor and agents targets write a marker-delimited block, so re-running updates vmn's section and leaves the rest of your instructions untouched.

vmn skill --install                  # Claude Agent Skill
vmn skill --install --target cursor  # .cursorrules
vmn skill --install --target agents  # AGENTS.md

Every agent gets its own island. One command creates a git worktree for your repo and every dependency repo, pinned to a known-good state, alongside your work rather than in place of it. Agents working in separate islands cannot touch each other's files.

vmn worktrees create my_app --island-name agent-auth
vmn worktrees create my_app --island-name agent-perf

Each island includes an island.json manifest — paths, branches, dependency hashes — so an agent can orient itself without being told the layout. Use --no-stamp for a read-only island when the agent shouldn't create versions at all.

Recoverable by default. Agents leave behind uncommitted edits, half-finished refactors, and untracked scratch files — state that git stash and WIP commits handle poorly. vmn snapshot create captures all of it, across every dependency repo, as a named version you can return to. Restore operations snapshot whatever is currently dirty before overwriting it, so uncommitted work is never silently destroyed.

Keep the good run reproducible. When an agent iterates on something measurable — a prompt, a heuristic, a model — vmn exp run records the metrics and the exact tree that produced them. Identifying run 14 as the best one is still useful a month later, because run 14's code remains addressable.

The full text of the instructions vmn installs is in docs/agent-skill.md.


⚡ Why vmn

Capabilityvmnsemantic-releaserelease-pleasechangesets
Language-agnosticJS-centricJS-centricJS only
Git-tag source of truth
Conventional commits + changelogpartial
GitHub Release creation
Auto-embed version into project filesper-pluginJS only
Multi-repo dependency tracking
State recovery (vmn goto)
Microservice / root-app topologymonorepo
4-segment hotfix versioning
Zero-config start (auto-init)
Offline / air-gapped❌ *
Uncommitted-state capture
ML experiment tracking
Ships agent instructions (vmn skill)
Parallel agent isolation (islands)

Bold rows are things only vmn does. * changesets authors offline but needs GitHub/npm to publish.

vs. experiment trackers (MLflow, W&B, DVC, Neptune)
Capability vmn MLflow W&B DVC Neptune
No server required ❌ *
No cloud account ✅ self-hosted
Free & open source free tier free tier
Metrics + live curves
Web UI ✅ one command server + DB cloud cloud
Full code-state capture partial **
Uncommitted changes captured
One-command state restore
Stamp-tree / version DAG view
Built-in version management
Works offline / air-gapped self-hosted partial
Install pip install vmn server + DB account + pip pip + git config account + pip
Lock-in none (git tags + files) MLflow format W&B cloud DVC format Neptune cloud

* MLflow can log to local files, but the comparison UI needs mlflow server.   ** DVC versions data/model files via git but captures no uncommitted code.

Use vmn when you want a CLI-first, local-first workflow, you work offline or air-gapped, you want versioning and experiments in one tool, or you don't want to run infrastructure to track training runs.

Use MLflow / W&B when you need hosted dashboards, team collaboration features, reports, or sweep orchestration.

Is vmn for me?
Any language — Python, Rust, Go, C++, Java, JS, anything in a git repo Microservices — independent versions per service, one root counter
Multi-repo — reproducible state recovery across repositories Zero config — no plugins, no pipelines, no ecosystem buy-in
Offline / air-gapped — works with no network at all Zero lock-in — versions are plain git tags
ML / research — reproducible snapshots with metrics, no tracking server CI — handles shallow clones automatically
AI-assisted development — per-agent isolation, generated agent instructions, state rollback Fast-moving teams — capture and roll back state without ceremony

🔧 Commands

Command What it does Example
stamp Create a new version vmn stamp -r patch my_app
release Promote a prerelease to final vmn release my_app
show Display version info vmn show my_app
goto Restore repo + deps to a version vmn goto -v 1.2.3 my_app
snapshot Capture uncommitted working state vmn snapshot create my_app
experiment Track runs with metrics (alias exp) vmn exp run my_model -- python train.py
worktrees Isolated parallel dev islands vmn worktrees create my_app
ui Serve the web dashboard vmn ui
gen Render a file from a Jinja2 template vmn gen -t ver.j2 -o ver.txt my_app
add Attach build metadata to a version vmn add -v 1.0.0 --bm build42 my_app
config Edit app config (TUI or scriptable) vmn config my_app
skill Emit AI-agent instructions for vmn vmn skill --install
init / init-app Explicit init — rarely needed, stamp auto-inits vmn init-app -v 1.4.2 my_app

Global flags: --debug, --version, --completion[-install|-uninstall] [SHELL]

stamp

vmn stamp -r patch my_app             # => 0.0.1
vmn stamp -r minor my_app             # => 0.1.0
vmn stamp -r patch --pr rc my_app     # => 0.1.1-rc.1
vmn stamp my_app                      # no -r needed with conventional_commits
vmn stamp --dry-run -r patch my_app   # preview, commit nothing
vmn stamp --pull -r patch my_app      # pull first, retry on conflict

Idempotent — it won't re-stamp a commit that already has a version. Auto-initializes the repo and app on first run.

Conventional commits, changelogs, GitHub Releases

With conventional_commits enabled, -r becomes optional. Commit prefixes map to release modes: fix: → patch, feat: → minor, BREAKING CHANGE or ! after the type → major.

git commit -m "feat: add search endpoint"
vmn stamp my_app     # => 0.2.0, minor inferred
conf:
  conventional_commits: true
  default_release_mode: optional   # or "strict"
  changelog:
    path: "CHANGELOG.md"
  github_release:
    draft: false

Changelog generation requires conventional_commits. GitHub Releases need the gh CLI and GITHUB_TOKEN.

-r vs --orm, and every stamp flag

Without -r: works during an in-progress prerelease sequence, or always if conventional_commits is on. Otherwise it errors on a release commit.

Flag Behavior
-r patch Strict — always advances. 0.0.10.0.2; 0.0.2-rc.30.0.3.
--orm patch Optional — advances only if no prerelease already exists at the target.
Flag Description
-r, --release-mode major, minor, patch, hotfix, micro
--orm, --optional-release-mode major, minor, patch, hotfix
--pr, --prerelease Create a prerelease (--pr rcX.Y.Z-rc.N)
--pull Pull before stamping; retries on conflict
--dry-run Preview without committing or tagging
-e, --extra-commit-message Append text to the stamp commit message
--ov, --override-version Force a specific version string
--orv, --override-root-version Force a specific root-app version
--dont-check-vmn-version Skip the vmn compatibility check
--git-push-user / --git-push-token Push credentials (see below)

Push credentials. For checkouts with no credentials of their own (CI runners, containers), --git-push-user / --git-push-token — or VMN_GIT_PUSH_USER / VMN_GIT_PUSH_TOKEN — make vmn rewrite the remote to an authenticated HTTPS URL for that single push only, leaving your git remote config untouched. ssh:// and git@host: remotes are converted to HTTPS. Both values must be supplied together; a lone one is ignored with a warning. vmn release accepts them too.

vmn init-app flags: -v/--version (initial version, default 0.0.0), --dry-run, --orm/--default-release-mode (optional | strict).

release

vmn release my_app                  # auto-detect from the current commit
vmn release -v 0.0.1-rc.1 my_app    # explicit version — tag only
vmn release --stamp my_app          # full stamp flow: commit + tag + push

Promotes a prerelease to final. Idempotent. -v and --stamp are mutually exclusive.

Iterating on release candidates
vmn stamp -r major --pr alpha my_app   # 2.0.0-alpha.1
vmn stamp --pr alpha my_app            # 2.0.0-alpha.2
vmn stamp --pr mybeta my_app           # 2.0.0-mybeta.1
vmn release my_app                     # 2.0.0

show

vmn show my_app              # current version
vmn show --verbose my_app    # full YAML metadata
vmn show --dev my_app        # dev version (commit + diff hash)
vmn show --type my_app       # release / prerelease / metadata
vmn show -u my_app           # unique ID (version + commit hash)
vmn show --root my_platform  # root-app version (an integer)
Remaining show flags
Flag Description
-v, --version Show info for a specific version
-t, --template Render with an ad-hoc template
--raw Skip template formatting
--conf Print the effective app configuration
--from-file Read local state instead of git tags
--ignore-dirty Don't fail on a dirty working tree

goto

vmn goto -v 1.2.3 my_app                        # repo + all deps
vmn goto my_app                                 # latest on the current branch
vmn goto -v 1.2.3 --deps-only my_app            # dependencies only
vmn goto -v 5 --root my_platform                # by root-app version
vmn goto -v 1.2.0-dev.a1b2c3d.e4f5g6h my_model  # restore a dev snapshot
vmn goto -v 1.2.3 --pull my_app                 # fetch first if not found locally

Missing dependency repos are cloned automatically, in parallel. Restoring a dev version checks out the base commit, replays local commits, then applies the working-tree patch.

snapshot

Capture your exact working state — uncommitted changes, unpushed commits, untracked files, across every dependency — as a deterministic version you can restore. No WIP commits, no stash management.

Use it when: you're hours into a refactor that half-works and want to try a different approach without losing this one. Committing pollutes history with work you may discard; git stash gives you an unnamed entry with no record of dependency state. A snapshot gives you a version string — try the other approach, and if it's worse, restore.

Also useful for: sharing a bug that only reproduces with your local debug changes; saving your state before an agent edits the same files.

vmn snapshot create my_app --note "promising results"
# => 1.2.0-dev.a1b2c3d.e4f5g6h

vmn snapshot list my_app
vmn snapshot diff my_app -v 1.2.0-dev.a1b   # second side defaults to your working tree
vmn snapshot restore my_app --latest        # dirty work is auto-saved first
Snapshot vs. experiment — which do I want?

An experiment is a snapshot plus an append-only metrics log. Use a plain snapshot to save or restore code state; use an experiment when you want to track and compare runs.

vmn snapshot vmn exp
Captures code state (tracked + untracked + deps)
Metrics / params / notes one note ✅ append-only log
Run a command, record its outcome ✅ (exp run)
Compare across runs diff only diff + metric deltas + compare
Typical use saving WIP before a risky change tracking and comparing runs
What's inside a snapshot
.vmn/{app}/snapshots/{version}/
  metadata.yml            # version, branch, timestamp, note, dirty states
  working_tree.patch      # uncommitted changes (git diff HEAD)
  local_commits.patch     # commits not yet pushed
  untracked_files.tar.gz  # untracked files
  deps/{dep_name}/...     # the same three, per dependency repo
  artifacts/{filename}    # attached files

Dependency state feeds into the content hash, so two snapshots differing only inside a dep get different version strings.

Note: unlike vmn goto, snapshot restore does not clone a missing dependency — it warns and skips it. Deps are expected to be on disk already.

All snapshot flags

Actions: create (default), list, show, note, diff, export, restore. Version-taking actions default to the latest and accept a full version, a unique prefix, --latest, or @N.

Flag Description
-v, --version Target a specific snapshot
--latest Use the most recent snapshot
--last N Show only the N most recent (for list)
--note Attach or update a note
--to Second version for diff (default: current, your working tree)
--tool External diff tool; falls back to git config diff.tool
-o, --output Export destination
--meta / --meta-file Extra metadata, repeatable key=value or a YAML file
--filter Filter list by key=value, repeatable
--verbose Full ISO timestamps
--backend / --bucket / --endpoint-url / --prefix local (default) or s3; S3 works with MinIO, Spaces, etc.

gen

vmn gen -t version.j2 -o version.txt my_app
vmn gen -t version.j2 -o version.txt -c custom.yml my_app

Template variables: version, base_version, name, release_mode, prerelease, previous_version, stamped_on_branch, release_notes, changesets, root_name, root_version, root_services.

add

vmn add -v 0.0.1 --bm build42 my_app
vmn add -v 0.0.1 --bm build42 --vmp ./build.yml --vmu https://ci/build/42 my_app

Attaches build metadata to an existing tag (0.0.1+build42). --vmp records a path to a YAML metadata file; --vmu an associated URL.

config

vmn config                       # list all managed apps
vmn config my_app                # interactive TUI
vmn config my_app --vim          # open in $EDITOR
vmn config --branch my_app       # override for the current branch
vmn config --root my_platform    # root-app config
vmn config --global              # repo-level .vmn/conf.yml
Non-interactive (config gen) — for CI and scripting

Creates a config file with no TTY. Never overwrites an existing one.

vmn config gen my_app                              # .vmn/my_app/conf.yml
vmn config gen --branch my_app                     # branch config, seeded from the effective conf
vmn config gen --branch --root my_platform         # branch config for a root app
vmn config gen --branch --sync-dep-branches my_app # pin each branch-tracked dep to its current branch

--sync-dep-branches is only valid with config gen --branch.

skill

Emits vmn's own usage instructions for AI coding agents — see Built for AI-assisted development.

vmn skill --install                     # .claude/skills/vmn/SKILL.md (default)
vmn skill --install --target cursor     # .cursorrules
vmn skill --install --target agents     # AGENTS.md
vmn skill --install --methodology       # + opinionated TDD / worktree rules
vmn skill --install --force             # overwrite an existing Claude SKILL.md
vmn skill                               # just print it

--install resolves the managed repo root even from a nested directory. The Claude target refuses to overwrite an existing skill without --force; the Cursor and AGENTS targets rewrite only vmn's marker block and preserve the rest of the file. Full text: docs/agent-skill.md.

Using vmn as a Python library
from version_stamp.cli.entry import vmn_run

ret, ctx = vmn_run(["show", "my_app"])

vmn_run takes an argument list and returns (exit_code, context). It prints to stdout/stderr, so wrap calls in contextlib.redirect_stdout / redirect_stderr to capture output.

Environment variables

Read by vmn:

Variable Description
VMN_WORKING_DIR Override the working directory
VMN_LOCK_FILE_PATH Custom lock file path (default .vmn/vmn.lock)
GITHUB_TOKEN / GH_TOKEN Required for GitHub Releases
VMN_GIT_PUSH_USER / VMN_GIT_PUSH_TOKEN Fallbacks for the --git-push-* flags
VMN_UI_TOKEN Fallback for vmn ui --token

Set by vmn for the child process of vmn exp run:

Variable Description
VMN_EXPERIMENT_ID The verstr of the running experiment
VMN_APP_NAME The app name
VMN_METRICS_FILE Path your command appends key=value metrics to

⚙️ Configuration

vmn writes .vmn/<app>/conf.yml when an app is first stamped. Edit it directly or via vmn config.

Full conf.yml reference
conf:
  template: '[{major}][.{minor}][.{patch}][.{hotfix}][-{prerelease}][.{rcn}][-dev.{dev_commit}.{dev_diff_hash}][+{buildmetadata}]'
  hide_zero_hotfix: true
  extra_info: false
  create_snapshots: false
  conventional_commits: true
  default_release_mode: optional   # "optional" (--orm) or "strict" (-r). Top-level, not nested.
  changelog:
    path: "CHANGELOG.md"
  github_release:
    draft: false
  deps:
    ../:
      other_repo:
        vcs_type: git
  version_backends:
    npm:
      path: "package.json"
  policies:
    whitelist_release_branches: ["main"]
  snapshot_storage:
    backend: local
    bucket: my-bucket
    prefix: vmn-snapshots
    endpoint_url: https://...
  experiment:
    metrics:
      loss: { goal: min, primary: true }
      acc:  { goal: max }
    storage:            # same shape as snapshot_storage; CLI flags override
      backend: local

create_verinfo_files was renamed to create_snapshots. The old key still works but warns.

Auto-embedding the version into project files

vmn stamp can write the version straight into your project files:

Backend File Field
npm package.json version
cargo Cargo.toml version
poetry pyproject.toml [tool.poetry].version
pep621 pyproject.toml [project].version
version_backends:
  npm:
    path: "relative/path/to/package.json"

Regex find-and-replace in any file:

version_backends:
  generic_selectors:
    - paths_section:
        - input_file_path: in.txt
          output_file_path: in.txt
      selectors_section:
        - regex_selector: '(version: )(\d+\.\d+\.\d+)'
          regex_sub: \1{{version}}

{{VMN_VERSION_REGEX}} matches any vmn version string (playground).

Jinja2 rendering:

version_backends:
  generic_jinja:
    - input_file_path: f1.jinja2
      output_file_path: jinja_out.txt
      custom_keys_path: custom.yml

Same variables as vmn gen.

Or skip file injection entirely — with hatch-vcs, read the version from the tag at build time:

[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"

[tool.hatch.version]
source = "vcs"
tag-pattern = "my_app_(?P<version>.*)"
Per-branch configuration

A branch can override the app config. The canonical location:

.vmn/<app>/branch_conf/<branch>/conf.yml        # slashes in the branch name
.vmn/<app>/branch_conf/<branch>/root_conf.yml   # become real directories

Create one with vmn config --branch <app> or vmn config gen --branch <app> — both seed it from the currently effective config. vmn resolves a branch config first and falls back to conf.yml.

Two older layouts are still read: flat <branch-with-dashes>_conf.yml and nested <branch>/conf.yml beside conf.yml. Precedence is canonical > flat > legacy, and legacy files are auto-migrated to canonical on the next vmn stamp. Stale branch configs from other branches are cleaned up on stamp.


🧬 Experiments

Local-first experiment tracking for any versioned app. Experiments are plain files under .vmn/{app}/experiments/ — git-ignored, never committed or pushed. No server, no database, no account.

Use it when: you're tuning something over days — hyperparameters, a cache policy, a retrieval prompt, compiler flags — editing code between runs. Two weeks later, "what did we change to get 0.91?" is unanswerable, because the tree that produced it was overwritten twenty runs ago. An experiment pins each result to the exact code that produced it, so exp diff shows the metric delta and the source change side by side.

Also useful for: benchmark and load-test runs where the configuration is the variable; agent-driven iteration loops; any workflow where many attempts produce one keeper.

# Capture code state, run the command, record metrics + exit code + duration
vmn exp run my_model --note "baseline CNN" -- python train.py
# => 0.1.0-dev.a1b2c3d.e4f5g6h

# Change the model, run again — a distinct experiment, even on the same commit
vmn exp run my_model --note "with dropout" -- python train.py

# Leaderboard, best loss first
vmn exp list my_model --sort loss --top 3

# Metric delta AND a real source diff between two runs
vmn exp diff my_model

# Winner — restore that exact state (dirty work auto-saved first)
vmn exp restore my_model --latest

No training script required. An experiment is a tree snapshot plus a metrics log — ML training is one use case; config sweeps, benchmarks, and load tests work identically.

Your command reports metrics by appending key=value lines to $VMN_METRICS_FILE. Prefix a line with step=N to build a per-step series — vmn tails the file during the run, so training curves appear live in the web UI.

Subcommands at a glance
Command What it does
exp run Capture state, run a command, record metrics + exit code + duration
exp create Capture a snapshot with metrics/params/notes, no command
exp add Append metrics, notes, or artifacts to an existing experiment
exp list List experiments, filter and sort by any metric
exp show Full detail for one experiment, including its log
exp diff Metric delta + real source diff between two experiments
exp compare Side-by-side metric table across N experiments
exp restore Restore the exact code state (dirty work auto-saved)
exp export Package an experiment as a directory or tarball
exp prune Clean up by count (--keep N) or age (--older-than 30d)

Version-taking actions default to the latest experiment and accept a full version, a unique prefix, --latest, or @N (the row index from exp list).

Re-running over an identical code state starts a new run (.r2, .r3, …) rather than overwriting — "same code, different seed" never replaces a previous run.

Metrics schema — teaching vmn which direction is better
experiment:
  metrics:
    loss:     {goal: min, primary: true}   # lower is better; default sort key
    val_loss: {goal: min}
    acc:      {goal: max}

goal: min sorts best-first ascending, goal: max descending. primary: true sets the default sort for list and the web-UI leaderboard.

📖 Full experiments guide → — the metrics protocol, structured params, addressing, S3 storage, and the no-script workflow.


🖥️ Web UI

A dashboard over data you already have. It reads git tags and .vmn/ files directly; the whole SPA ships inside the wheel.

pip install "vmn[ui]"
vmn ui               # http://127.0.0.1:8265, opens your browser
  • Leaderboard — sortable, goal-aware metric columns; @N indices matching the CLI.
  • Run detail — params vs. metrics, live training curves, full log, copy-paste reproduce commands.
  • Compare — pick two runs for a metric-delta table plus a color-coded code diff.
  • Stamp tree — your version history as a DAG, colored by release mode, with root-app topology and cross-repo dependency pins. No experiment tracker has this — it falls out of vmn's git-tag model.
  • Actions — run stamp / restore / goto / release / prune from the browser. Each runs as a real vmn subprocess, so it takes the repo lock correctly and streams its log live.
Team / remote deployment
vmn ui --host 0.0.0.0 --port 8265 \
       --token "$VMN_UI_TOKEN" \
       --data-dir /srv/vmn-ui \
       --repo /srv/checkouts/model-a --repo /srv/checkouts/model-b
  • Workspaces — one server hosts many isolated checkouts. Several can be clones of the same repo (one per branch or user); a stamp in one never touches another. S3 buckets register as read-only experiment sources with no local repo at all.
  • Auth — a shared bearer token; put TLS and user management behind a reverse proxy.
  • --read-only disables every mutation endpoint.
  • --data-dir (default ~/.vmn-ui) holds the workspace registry and a derived SQLite cache that keeps leaderboards instant; --no-index reads sources directly.

The whole /api/v1/... surface is documented at /api/docs.

📖 Full UI guide →


🏝️ Islands (parallel worktrees)

An island is a set of git worktrees — your main repo plus every dependency — pinned to a known-good state, sitting beside your work instead of on top of it.

Use it when: you want three agents working on three features at once in a product that spans four repos. Doing that by hand means a git worktree add per repo per feature, with every dependency checked out at the exact hash the last good version recorded — twelve checkouts, each re-derived from tag metadata. One vmn worktrees create per feature does it, and agents in separate islands cannot touch each other's files.

Also useful for: reproducing a customer bug on 2.1.0 while your current work stays where it is — vmn goto would move your checkout, an island provides that state alongside it. Or a disposable --no-stamp island for a risky experiment.

Problem Without islands With vmn worktrees
Parallel features git worktree + manually clone each dep at the right hash one command
Dependency alignment vmn goto mutates your checkout islands are non-destructive copies
Agent isolation agents collide in one tree each agent gets its own island
Reproducibility "works on my machine" island.json records the exact state
vmn worktrees create my_app                                    # from current HEAD, auto-named
vmn worktrees my_app                                           # 'create' is the default action
vmn worktrees create my_app --island-name feat-auth -fv 2.1.0  # from a version
vmn worktrees create my_app --island-name feat-perf -fb develop # from a branch
vmn worktrees create my_app --island-name ci-test --no-stamp   # read-only: stamping disabled
vmn worktrees list
vmn worktrees remove feat-auth                                 # removes worktrees and branches
Layout, branch model, and the manifest
../vmn-islands/                # configurable with --base-path
  feat-auth/
    my_project/                # main repo — git worktree on a new branch
    auth_service/              # dep — git worktree, detached HEAD
    payment_gateway/           # dep — git worktree, detached HEAD
    island.json                # machine-readable manifest
  • Main repo gets a new branch island/{name}/{original-branch} — ready for commits and PRs.
  • Dependencies are detached HEAD at the exact hash recorded when the source version was stamped. Detaching sidesteps git's rule that the same branch can't be checked out in two worktrees.
  • --editable-dep <name> gives that dep its own island/{name}/{dep-branch} for cross-repo work.

island.json records name, creation time, app, version, source ref, the main repo's path/branch/remote, and every dep's path/hash/branch/remote — so an agent can orient itself without being told the layout.

--shallow-deps is a fallback, not a speed knob: if a dependency repo is already on disk, vmn always makes a worktree from it. The flag only applies when a dep is missing locally, permitting a --depth 1 clone from its remote. Without it, a missing dep is a hard error.

Stamping inside an island

vmn stamp works inside islands by default. The version commit stays on the local island branch and vmn pushes only the tag, so it never assigns the island branch to origin/main or publishes the branch. --pull fetches remote version state without merging another branch into the island.

Use --no-stamp for islands meant for CI, testing, review, or agents that shouldn't create versions.


🔄 CI

steps:
  - uses: actions/checkout@v4
    with:
      fetch-depth: 0          # required — vmn reads tags and history
  - uses: progovoy/vmn-action@latest
    with:
      app-name: my_app
      do-stamp: true
      stamp-mode: patch
    env:
      GITHUB_TOKEN: ${{ github.token }}

fetch-depth: 0 is not optional — vmn computes the next version from git history and tags.


🔍 Troubleshooting

vmn can't find tags / reports the wrong version

Most CI systems shallow-clone by default. vmn needs full history:

- uses: actions/checkout@v4
  with:
    fetch-depth: 0

Or manually: git fetch --tags --unshallow

"Another vmn process is running" / lock file error

vmn takes a per-repo lock so concurrent stamps can't interleave. If a previous run crashed:

rm .vmn/vmn.lock            # default location
# or, if VMN_LOCK_FILE_PATH is set:
rm "$VMN_LOCK_FILE_PATH"
Tag name collision

vmn tags are {app_name}_{version} (slashes in app names become -). If your repo already has tags matching that pattern, rename the app or clean up the conflicting tags before the first stamp.

"Dirty" state warnings on stamp

vmn refuses to stamp over uncommitted changes or unpushed commits. Commit or stash first — or capture the state with vmn snapshot create so you can return to it. vmn show --verbose prints the exact flags (pending, outgoing, detached).

App name rejected

App names cannot contain - or start with /. Use _, or / to express root-app topology (my_platform/auth).


🔀 Coming from another tool?

From Guide
semantic-release migration guide
release-please migration guide
setuptools-scm migration guide
standard-version (archived 2023) migration guide
bump2version migration guide

Your existing tags keep working — vmn uses its own {app}_{version} format and won't collide with v1.2.3-style tags.


Every version, restorable.

pip install vmn

If vmn is useful to you, consider starring the repo. Found a problem? File an issue.

Contributing   Report an issue   PyPI

Add the badge to your project:
[![vmn: automatic versioning](https://img.shields.io/badge/vmn-automatic%20versioning-blue)](https://github.com/progovoy/vmn)

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vmn-0.9.4-py3-none-any.whl (366.9 kB view details)

Uploaded Python 3

File details

Details for the file vmn-0.9.4-py3-none-any.whl.

File metadata

  • Download URL: vmn-0.9.4-py3-none-any.whl
  • Upload date:
  • Size: 366.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for vmn-0.9.4-py3-none-any.whl
Algorithm Hash digest
SHA256 16c8a3a3d52c1ea55c7371a5e7945b461f5ee62abf0e92f4e2e54277fa479b88
MD5 7d843cfd5ec4131e8fb5e44b1d4ca45d
BLAKE2b-256 5bfdd212fad917c93caac02afe3c3c340adf1e8d35ea2c17e729b152488dd61a

See more details on using hashes here.

Release history Release notifications | RSS feed

0.10.1

1 file

0.9.6

1 file

0.9.5

1 file

This release

0.9.4 This release

1 file

0.9.3

1 file

0.9.2

1 file

0.9.1

1 file

0.9.0

1 file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page