CLI reference
sillon <command> [options]
sillon --help
sillon --version
Run sillon with no command for the project at a glance — its name, run count
and health, code versions, storage and whether the daemon is up. Outside a
project it tells you how to find your projects instead of erroring.
Every command except projects and init must be run from inside a project.
"Inside" means what it means in git: the project is found by searching upward
for a .sillon/ directory, so sillon list works from analysis/figures/.
Runs can be named by name, full uuid, or an unambiguous uuid prefix —
sillon show a3f9 works.
!!! warning "Renamed in 2.0"
context → list, search → query, grab → fetch, whose → trace,
and compare is gone in favour of diff. There are no aliases; see
Migrating to 2.0.
Exploring
sillon list — the runs, as a table
sillon list # every run
sillon list my_fit a3f9 # just these
Many runs at a glance. For one run in detail, that is sillon show.
sillon show — one run in detail
sillon show my_fit
sillon show my_fit -p # parameters
sillon show my_fit -r # results
sillon show my_fit -m %all% # all metadata
sillon show my_fit -f # figures, with their provenance
sillon show my_fit -A # analyses, with their provenance
| Flag | Shows |
|---|---|
-p [KEY ...] |
parameters (all, or named ones) |
-r [KEY ...] |
results |
-m [KEY ...] |
metadata — %all% for everything |
-t |
tags |
-f |
figures and what they were built from |
-A |
analyses and what they were computed from |
-n |
notes |
sillon query — find runs
sillon query -p degree=3
sillon query -p degree=3 lr=0.01 -t baseline
sillon query --status SUCCESS --after 2026-01-01
sillon query -r coef # runs that have this result
| Flag | Filter |
|---|---|
-p KEY=VALUE |
parameter equals value |
-m KEY=VALUE |
metadata equals value |
-t TAG |
has tag |
-r RESULT |
has this result |
-a ARTIFACT |
has this artifact |
-A ANALYSIS |
has this analysis |
--status |
run status |
--before / --after |
date bounds |
--limit |
cap the number of results |
Values are parsed as Python literals, so -p lr=0.01 compares as a float.
sillon versions — group runs by the code that produced them
sillon versions # every code version in the project
sillon versions c8d012 # the per-file breakdown of one version
sillon versions --backfill # recover versions for runs logged before this existed
A run's code version covers its entry script and your own modules, hashed at
the AST level with literal values normalised away — so a tuned constant
(N = 5 → N = 4) does not start a new version, while a changed expression
does. See Code versions.
| Flag | Effect |
|---|---|
--backfill |
Recover versions for older runs from their stored entry script. Those cover the entry script only and are shown as such. |
--files |
Per-file hashes (implied when you name a version). |
sillon init — make a directory a project
sillon init
sillon init --name "Shaking Lattice"
sillon init --storage-root /Volumes/bigdisk/sillon # heavy data elsewhere
sillon init ~/Code/phd/new-thing # somewhere else
Logging a run has always created the project it needs, and still does — that is
the point of sillon, and init is not required. What init adds is the two
things that side effect cannot do: name a project before it has any runs, and
put its heavy data somewhere other than .sillon/.
| Flag | Effect |
|---|---|
--name NAME |
The project's name. Without it the first run that passes project_name sets it. |
--storage-root PATH |
Where glob/, artifact/, figure/ and the database live. Recorded relative to the project when it is inside it, so the project still travels. |
Running it on an existing project reports and changes nothing — it will not rename a project or move its data. Running it inside one is refused: sillon does not nest projects.
sillon projects — every project on this machine
sillon projects
sillon projects --prune # forget projects whose folder is gone
sillon projects --repair # collapse duplicates from older versions
sillon projects --no-stats # skip run counts
The one command that works from any directory. See Projects.
Provenance
sillon lineage — what a run came from
sillon lineage refined
Shows the run's parents (↑) and the runs derived from it (↓).
sillon diff — what differs between runs
sillon diff baseline refined # compare two runs
sillon diff baseline refined --source # ...and show the source diff
sillon diff --across tag=sweep # what varies across a set
sillon diff --across # ...across the whole project
Reports parameters with a real delta, whether the code differed in logic or only in a tuned constant, and whether the results moved. Arrays are compared by shape, dtype and hash — never element by element — so a diff stays fast on large results.
Parameter From To Δ
degree 3 5 (+66.67%)
Code same logic 4756b7f9 — constants differ in run.py
Result From To
coef float64 (4,) float64 (6,) shape changed
rmse 9.7456762644862… 6.1295293145338… (-37.11%)
field float64 (5000,) float64 (5000,) contents differ
--across answers the question a sweep leaves behind — which knobs were
actually turned:
varying degree: 1, 2, 3, 4, 5
varying ridge: 0.0, 0.1
constant seed, solver
code one version (98167c00)
When a set spans more than one code version it says so, because those runs are not all comparable.
| Flag | Effect |
|---|---|
--across [KEY=VALUE ...] |
Summarise a set instead of comparing two. tag=sweep, status=SUCCESS, or nothing for the whole project. |
--source |
Also print the unified source diff. |
--max-bytes N |
Largest result to load for comparison (default 64 MB). Bigger ones are compared by shape and dtype and reported as not compared. |
sillon like — runs similar to a given one
sillon like happy_perlman
sillon like happy_perlman --limit 5
"Did I do something close to this?" — ranked by how much of the configuration
two runs share, with a tiebreak on how far the differing values moved, so
degree 3→2 ranks above degree 3→5.
Match Run Differences Code
75% d2_r0.0 degree 3→2 same
75% d4_r0.0 degree 3→4 same
50% d2_r0.1 degree 3→2, ridge 0.0→0.1 same
17% unrelated different parameter set —
More useful than an exact-duplicate check, which only fires on a perfect repeat and says nothing about the near misses.
sillon trace — which run produced this file
sillon trace figures/fit.png
sillon trace 9f2c8a1e... # or a bare SHA-256
Getting data out
sillon fetch — one item as a file
sillon fetch my_fit -r coef
sillon fetch my_fit -r coef --dest out/
sillon report — a run written up for a person
sillon report my_fit
sillon report my_fit --with-data --dest out/
A zip holding manifest.json and a readable report.md, for sending to a
colleague. It is lossy on purpose: results over 512 bytes are recorded as a
size rather than a value, and artifact bytes and figure images are never
included, even with --with-data. Nothing reads one back.
To move runs to another machine, that is export.
Moving runs between machines
A bundle is the machine-readable counterpart of report: everything needed to
reconstruct a run somewhere else, and nothing else. See
Moving runs.
sillon export — runs as a portable bundle
sillon export # the whole project, with its data
sillon export deg3 deg4 # just these
sillon export -t sweep -p degree=3 # whatever the filters match
sillon export --dest /Volumes/share/ # a directory gets a generated name
sillon export --no-data # the records only
Takes the same filters as query, so there is one
filter vocabulary rather than two. With no runs and no filters it exports
everything.
| Flag | Effect |
|---|---|
--dest PATH |
A file to write, or a directory to write a self-describing name into (<project>~<host>~<when>.sillon). Defaults to the current directory. |
--no-data |
Index only — no arrays, artifacts or figures. The runs import as stubs: the record is there, reading the data is not. |
-p / -m / -t / --status / --before / --after |
As in query. |
A run whose data a row claims but disk does not have — a pruned run — exports its record and says so, rather than producing a bundle that looks complete.
sillon import — add what you do not already have
sillon import lattice~laptop~20261007T185407Z.sillon # a bundle
sillon import ~/old-phd/shaking # another project
sillon import --scan ~/Code/phd # every project under it
sillon import --dry-run bundle.sillon # what would happen, changing nothing
sillon import --inspect bundle.sillon # what is in it, ignoring this project
sillon import --no-data bundle.sillon # the records only
The source is a bundle file or another project directory on this machine. Crossing a machine boundary needs a bundle; merging two projects that are both right here does not, so there is no packing step for that.
| Flag | Effect |
|---|---|
--scan |
Treat the source as a directory to search, and import every project under it. This project is left out of its own scan. |
--dry-run |
Print the plan and change nothing. The plan shown is the plan that would be carried out, names and all. |
--inspect |
Describe one source without looking at this project at all — works for a bundle you have not decided about yet. |
--no-data |
Records only. |
Union, never overwrite. A run already here is left exactly as it is; a run that is not is added. Nothing an import does can lose local work, which is why it is safe to repeat — and repeating is the normal case, since re-importing is how you find out whether a bundle has anything new in it.
| Situation | What happens |
|---|---|
| the run is already here (same uuid) | skipped and counted |
| the name is free | added as it was |
| the name is taken | added as <name>~<origin host> |
| a lineage edge points at a run in neither project | the edge is kept and reported |
Identity is the run's uuid, which has been globally unique since the beginning.
Names are not identity — two machines will both produce a deg3 — so the
incoming one is suffixed and the local one keeps its name. Lineage is unaffected
either way, because edges are followed by uuid.
The suffix names whatever actually distinguishes the source: the machine for
a bundle (deg3~laptop), the project for a directory on this one
(deg3~Alpha), since both projects share a hostname and it would say nothing.
Everything is preserved: the uuid, both timestamps, the status, the hostname, the author, and the version of sillon that wrote the run — not the one importing it.
Editing
sillon add — annotate a run
sillon add my_fit -n "Used in figure 3"
sillon add my_fit -t publication reviewed
sillon add run_a run_b -t sweep # several runs at once
sillon rename
sillon rename old_name new_name
Deleting
delete always prompts. prune prompts only for --delete-metadata, because
that is the irreversible half — a data-only prune leaves the record intact and
can be re-created by re-running. -y skips either prompt.
sillon delete — remove runs
sillon delete my_fit
sillon delete run_a run_b -y
Removes the database row and the stored data.
sillon prune — reclaim space
sillon prune --older-than 30d
sillon prune --older-than 2026-01-01
sillon prune --run-id a3f9 b7c2
sillon prune --older-than 90d --delete-metadata
By default prune deletes the stored data and keeps the database row, so the
record of what you ran survives. --delete-metadata removes the rows too — a
different and irreversible thing, which the prompt says explicitly.
Prune refuses to run without a scope: you must give --run-id or --older-than.
Health
sillon check — what no longer adds up
sillon check
sillon check --repair # apply the fixes that cannot lose anything
Most of what sillon records is a cross-reference, and each one can come apart: a pruned run, a half-finished copy, a child imported without its parent, a project moved while its data was on a disk that is not here. None of it is fatal, and all of it is confusing when you first meet it as a failed read six months later.
Findings come in three severities, worst first:
| Meaning | Examples | |
|---|---|---|
broken |
already failing | two runs sharing a uuid; a storage root that is not there; a database that will not open |
stale |
will mislead | a run whose data is gone; a lineage edge pointing at a run that is not here; a source a code version refers to but that is not stored |
tidy |
wasted space | data no run claims; stored source text no run refers to |
--repair applies only the findings it cannot be wrong about in a costly way:
re-registering the project, and deleting data and source blobs that no run row
refers to. It never touches a run row. Anything whose fix is a judgement
call — a run whose data is gone might want re-running or might want deleting —
is reported and left for you.
It is also the one command that works on a project which will not open: if the database cannot be read, that is the first finding rather than the reason you cannot ask.
Environment variables
| Variable | Effect |
|---|---|
SILLON_IDLE_TIMEOUT |
seconds before an idle daemon exits (default 300) |
Not implemented yet
sillon run, sillon watch, sillon estimate and the GUI appear in the
roadmap but do not exist. If a command is not listed on this page, it is not
there.