Sprout is a tiny, hackable, and completely local tool for managing machine learning runs and experiments. It uses BorgBackup under the hood to snapshot and deduplicate experiment folders, keeping your models, logs, and checkpoints consistent without heavy frameworks or cloud dependencies.
When training reinforcement learning agents or fine-tuning models, it's easy to end up with dozens of runs - each slightly different in configuration, parameters or seed. Keeping them organized, reproducible, and comparable is tedious.
Sprout was built to be simple and local-first:
- One file - ~2k lines of Python, easy to read and modify
- Fully local - stores everything in plain folders and Borg archives
- Snapshotting with deduplication - each run is an immutable state stored efficiently by Borg
- Human-friendly structure - make runs active, compare folders, or restore states easily
- Integrates with your workflow - especially handy when dumping or restoring Torch models
It's designed to be hackable, scriptable, and available as an API - ideal for personal workflows and small research projects.
For example, the following code can be used to create a snapshot of an existing model before training starts:
# Create Sprout instance
spr = sprout.Sprout("models")
# Build a model directory using any model name
model_dir = os.path.join("models/active", model_name)
if not os.path.isdir(model_dir):
# Create a model state specifying the model name and all model parameters
spr.create(group=group_name, head=model_name, params_str=model_params)
elif not args.cont:
# Create a snapshot of the model state
spr.create(from_head=model_name, params_str=model_params)- Python 3.9+
- BorgBackup CLI tool (
borg) available in your$PATH
Install Borg (Debian/Ubuntu):
sudo apt install borgbackupOr via pip (optional Python wrapper):
pip install borgbackupSprout expects a working directory (we'll call it WORK/). Typical layout:
WORK/
├── .borg/ # Borg repository (snapshots)
├── .heads/ # Active states (folders restored for work)
├── active/ # Symlinks pointing to current active runs
└── metadata.json # Run metadata (models, runs, parents, params)
.heads/contains the actual restored folders (active states).active/contains human-friendly symlinks (e.g.active/A -> .heads/<runid>).Borgarchives are stored in.borg.
All commands require --working WORK to point to a working folder.
Create a new run (fresh or fork from an existing run):
./sprout.py --working WORK create ModelA
./sprout.py --working WORK create --from RUN_ID
./sprout.py --working WORK create --from RUN_ID --head A --params "--lr 0.01" --description "note"--head makes the new run active under a human-friendly name (creates
a symlink in active/).
Persist a head back into Borg and remove its working folder:
./sprout.py --working WORK persist HEAD_NAME
# or persist all:
./sprout.py --working WORK persistRemove runs, branches, heads, or entire models. Use --whole-branch
to delete recursively.
./sprout.py --working WORK remove RUN_ID
./sprout.py --working WORK remove --model ModelA
./sprout.py --working WORK remove --head A
./sprout.py --working WORK remove RUN_ID --forceEdit alias, created date, params (table style), and description in
your $EDITOR. Template is YAML-style and params is a block:
./sprout.py --working WORK edit RUN_IDExample editor contents:
alias: quick-run
created: 2025-09-22T11:07:39
params: |
lr = 5e-05
batch_size = 10240
frames = 50000000
description: |
Short free-form description.
Keep indentation of this block.If description is left unchanged from the template, it will be stored as empty.
Pretty-print hierarchical run tree with inline short param summary and active-head markers:
./sprout.py --working WORK log
# or
./sprout.py --working WORK treeSymbols used:
●- active head (green in terminals that support color)∅- no params≡- params exist but unchanged vs parentΔ- shows which params changed (short summary)
Show ancestry chain for a run and parameter diffs:
./sprout.py --working WORK history RUN_IDCheck consistency between Borg archives, metadata.json, .heads/,
and active/ symlinks. Detect missing or extra runs.
Sprout provides two main human-facing views:
-
Tree (
tree) - hierarchical, shows ancestry and active heads with simple param diffs inline. -
Flat list (
log) - compact, one-line-per-run summary with parent and short param summary (good for quick scanning or scripting).
Both formats aim to be terminal-friendly and easy to parse visually.
- Single-source approach: the main implementation is intentionally kept in one file (easy to inspect and modify).
- Metadata: stored as JSON under
.borg/metadata.jsonto keep everything inside the Borg repo. - Heads vs active:
.heads/stores restored run folders;active/contains symlinks that name those active folders. - Borg usage: Sprout relies on Borg for
create,extract, anddeleteoperations. You should have Borg installed and available in$PATH. - No background work: Sprout runs commands synchronously - no daemon or server process required.
The sprout.py source file includes basics unit-tests, which can be executed as follows:
./sprout.py testMIT License - use, modify, and share.
PRs, issues, and feature suggestions are welcome. If you want help splitting into modules, adding a tiny UI, or integrating with other tools, open an issue or submit a PR.