Concepts
A fresh resolve is a different environment.
Renest reinstalls your Python dependencies on every rebuild, but it never re-decides them. A nest stores the dependency lock from the environment that worked, and a restore installs exactly that list with uv, with no new resolution step.
A resolver's answer depends on the state of the package index
When you type pip install -r requirements.txt, you are not handing the
machine a list of software. You are handing it a constraint problem — "torch,
some numpy that torch tolerates, whatever these forty transitive packages agree on" —
and asking a solver to find versions that satisfy it. The solver's answer depends on an
input nobody writes down: the state of the package index at the moment you
ask.
Run the same file in March and in September and the solver is solving two different problems, because the world it solves against has moved: new releases came out, one got yanked, a transitive dependency loosened a bound. Both runs succeed. Both print the same package names. They are not the same environment.
A fresh resolve gives you a new environment under the old name
This is why "I reinstalled everything exactly like before and now it behaves differently" is such a common — and such a maddening — failure. Nothing you did changed. The requirements file didn't change. But a requirements file was never a description of your environment; it was the question that produced it, and the same question now yields a different answer. What a fresh resolve gives you is a new environment wearing the old environment's name — close enough to load, different enough to break subtly, in the numerics, in a default, in some corner the release notes didn't think worth mentioning.
A nest stores the dependency lock, not the requirements file
A nest records the answer: a dependency lock taken from the environment that actually worked, plus the Python version it ran on. If the environment has a lock file, that file is stored as-is. If it doesn't, Renest reads which packages are installed there, at which versions, and stores that list.
Some packages exist only as vendor builds, like a torch built for one CUDA version
(torch==2.4.1+cu124). The public index doesn't carry those, so by default
packing records a direct download address for each of them, with its fingerprint whenever the index
supplies one. Pack
with --offline and those addresses are skipped; the nest is then marked as
not rebuildable on another machine, and a restore says so up front.
A rebuild reinstalls the locked list without re-resolving it
Here is the distinction this page exists for, because the two sentences sound alike and are opposites in practice:
- Renest reinstalls your dependencies on every rebuild. True. A rebuild is not a disk image; packages are installed fresh on the target machine.
- Renest re-resolves your dependencies on every rebuild. False — and if it were true, everything on this page would apply to us.
The restore creates a fresh environment for the recorded Python version. It then hands the stored lock to uv in the mode that installs exactly the listed packages. Nothing gets solved: uv's job is reduced to fetch these exact versions and put them in place. The decision was made once, on the machine where your setup worked, and the lock carries that decision to the new one.
Some honest limits apply:
- A lock pins versions, which is not the same as pinning bytes. Packages that carry a recorded fingerprint are checked against it. The rest are fetched by name and version from the package index, and nothing compares their bytes.
- A lock can't bring back a package that has vanished from its source. There is one case where the restore does substitute. If the install fails because a pinned vendor download has been taken down, the restore retries once with the plain version of that package (the same version number without the vendor tag). It warns you when that happens, and your stored lock is not changed.
- Some packages, such as flash-attn, have to be compiled for the card that runs them. Packing warns about these: the file check can't vouch for them.
Environments don't only break when someone changes them. They also break when someone re-derives them and assumes derivation is deterministic. It isn't — so we keep the derived thing.
These docs describe renest 0.1.15, the latest release.