Model Registry
v0.26.0 ships a local model registry at ~/.soup/registry.db. Every fine-tune can be pushed, tagged, searched, diffed, and walked as a lineage DAG — entirely offline, no server, no cloud.
Why a registry
Most people end up with 17 checkpoint directories called output-3-final and no idea which one won. The registry fixes that: one row per fine-tune, carrying config, eval baseline, and parent lineage.
Push a run
# After training a model
soup registry push \
--run-id run_20260420_143052_a1b2 \
--name chat-llama \
--tag v1 \
--notes "Alpaca 15k, 2 epochs, lr=2e-4"The registry derives lineage from the run's config (same base model, different run id); there is no --parent flag.
List, show, search
# List everything
soup registry list
# Filter
soup registry list --name chat-llama
soup registry list --tag prod
soup registry list --base Qwen/Qwen3-8B --task sft
# Show details + lineage
soup registry show chat-llama:v1
# Search across name, base, task, notes
soup registry search "medical"--limit (-l) caps the rows: soup registry list shows 100 by default and soup registry search 50. A value outside the allowed range is clamped, not rejected: 1 to 1000 for list, 1 to 500 for search.
Referring to an entry
Every command that takes a reference accepts an entry id, a unique id prefix, name:tag (the form soup registry push prints, for example chat-llama:v1) or registry://<id>. The separator is a colon: chat-llama@v1 is read as an id prefix, matches nothing and exits 1 with "Registry entry not found". If a name and tag match several entries, the most recent one wins.
Diff two versions
soup registry diff chat-llama:v1 chat-llama:v2soup registry diff prints a table of config differences between the two entries, and an eval-delta table when either entry has eval results attached:
Config diff: reg_20261006_6a0a45ef1ac4 -> reg_20261006_10a81a6c72ae
┌─────────────────┬─────────┬────────┬────────┐
│ Path │ Kind │ Left │ Right │
├─────────────────┼─────────┼────────┼────────┤
│ training.lora.r │ changed │ 16 │ 32 │
│ training.lr │ changed │ 0.0002 │ 0.0003 │
└─────────────────┴─────────┴────────┴────────┘Promote to prod
soup registry promote chat-llama:v2 --tag prodTagging is how you mark stable versions. Any number of tags can point at the same entry.
Lineage DAG
soup history chat-llamasoup history takes an entry name and prints one node per entry with that name: its id, its tags in parentheses and its creation time. Where they exist, ancestors, descendants and branches appear as child nodes. It prints no eval scores.
chat-llama
├── reg_20261006_6a0a45ef1ac4 (v1) 2026-10-06T01:14
└── reg_20261006_10a81a6c72ae (prod, v2) 2026-10-06T01:14Cycle detection is enforced on every walk — forking a descendant back onto an ancestor raises an error.
Delete
soup registry delete chat-llama:v1 --yessoup registry delete <ref> --yes deletes that one entry together with the database rows that hang off it (its tags, artifact records and lineage links). Entries that descended from it are not deleted; they lose the link. It removes registry records only, not the files they point at. Without --yes it only prints About to delete <id> (<name>). Pass --yes to confirm. and exits 0, so there is no interactive confirmation and no subtree preview.
Use as an eval-gate baseline
The eval gate accepts registry://<id> as the baseline, so quality regressions are diffed against any historical run rather than a static file.
Storage
- SQLite at
~/.soup/registry.db— human-readable, portable - Single writer, no daemon
- Name (up to 128 characters) and tag (up to 64) are validated: letters, digits,
_,-and.only, starting with an alphanumeric, so neither can contain:,@or a path separator - SQL LIKE-wildcard escaping on every search term
See also
- Eval gate — baselines can point at registry entries
- Soup Cans — export a registry entry as a portable
.can - CLI reference
Soup is free and Apache-2.0. If it saved you a training run, starring the repo costs nothing and helps most. You can also fund the GPU time behind the work a 4 GB laptop cannot reach.