ADR-0013: How the template pins the libraries¶
Status: Accepted Date: 2026-09-29 Deciders: Alex Nodeland
Context¶
The libraries install from GitHub by commit, not from PyPI (artifactr#23). ADR-0011 made each pin a Copier question (artifactr_rev, reflexr_rev and evalr_rev). It also had an application declare evalr's source itself, on the premise that a dependency's own [tool.uv.sources] doesn't apply to its dependents. Its amendment made evalr follow reflexr's pin; with both [langfuse] extras now requiring evalr, it would have had to follow both.
Two things were wrong with that:
- The application's evalr source was never needed. uv resolves a library's requirements with the library's own sources, and a git URL one of them requires satisfies every other requirement on evalr, the application's own
evalr[langfuse]included. An application requiringartifactr-ai[langfuse],reflexr[langfuse,evals]andevalr[langfuse], with sources for the two libraries only, locks evalr at the commit both pin, and so does every variant of the template. When the two pin different commits,uv lockfails and names both URLs. So a pin of the template's own could only repeat theirs, or break the lock. - Answers held the pins back. Copier records answers and reuses them on
copier update. An application kept the revisions it was generated with while the template's code moved on, which is the one combination no check ever runs: newer template code on older libraries.
Decision¶
- The template pins artifactr and reflexr, and nothing else. The generated
pyproject.toml's[tool.uv.sources]names the two libraries. evalr comes with them, at the commit their own sources pin. An application with evals still requiresevalr[langfuse], since it imports evalr, but declares no source for it. - The pins are the template's values, not questions.
artifactr_revandreflexr_revincopier.ymlhavewhen: falseand no validator. They are never asked and never recorded in.copier-answers.yml, socopier updatemoves them with the template code written for them. An application that holds a library back edits its rev inpyproject.toml; Copier's three-way merge keeps the edit, and marks a conflict when an update changes that line or one beside it, such as the other library's pin. make bump-librariesmoves artifactr and reflexr to theirmain, or to a named revision, and doesn't look at evalr. When the libraries' evalr pins differ,uv lockfails in the pull request's Template jobs, naming both.
This supersedes the revisions in ADR-0011's Questions bullet, its bullet on pinning the libraries by git revision, and the "evalr follows reflexr" bullet of its amendment on make bump-libraries. The rest of that amendment (the script, --check, and a person opening the pull request) stands, for the two libraries.
Options considered¶
| Option | evalr's pin | An application's pins on copier update |
|---|---|---|
| The template's values; evalr from the libraries (chosen) | The libraries', once | Move with the template's code |
| Questions, and evalr's source declared too (ADR-0011) | Repeated, and must match the libraries' | Stay where they were generated |
| Questions, and evalr from the libraries | The libraries', once | Stay where they were generated |
Consequences¶
- Easier: evalr is pinned in one place, the libraries. An updated application runs the libraries its template code was written and checked against, and the bump script has two libraries to move.
- Harder: holding a library back is an edit to
pyproject.toml, which a latercopier updatethat moves either pin marks as a conflict. - Revisit: published releases of the libraries, which would replace git revisions with version ranges.