flexlock 0.8.2__tar.gz → 0.9.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. flexlock-0.9.0/.github/scripts/check_version.py +35 -0
  2. flexlock-0.9.0/.github/workflows/ci.yml +66 -0
  3. flexlock-0.9.0/.github/workflows/docs.yml +55 -0
  4. flexlock-0.9.0/.github/workflows/release.yml +93 -0
  5. flexlock-0.9.0/CHANGELOG.md +53 -0
  6. flexlock-0.8.2/.claude/worktrees/dedup-deps/README.md → flexlock-0.9.0/PKG-INFO +34 -8
  7. flexlock-0.8.2/PKG-INFO → flexlock-0.9.0/README.md +18 -24
  8. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/docs/api.rst +5 -0
  9. flexlock-0.9.0/docs/changelog.md +2 -0
  10. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/docs/cli_reference.md +100 -4
  11. flexlock-0.9.0/docs/dev_plan_pipeline_tasks.md +252 -0
  12. flexlock-0.9.0/docs/dev_plan_presets_records.md +408 -0
  13. {flexlock-0.8.2 → flexlock-0.9.0}/docs/hpc_integration.md +175 -17
  14. {flexlock-0.8.2 → flexlock-0.9.0}/docs/index.rst +1 -0
  15. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/docs/philosophy.md +49 -20
  16. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/docs/python_api.md +10 -1
  17. {flexlock-0.8.2 → flexlock-0.9.0}/docs/resolvers.md +44 -2
  18. {flexlock-0.8.2 → flexlock-0.9.0}/docs/usage_guide.md +193 -13
  19. {flexlock-0.8.2 → flexlock-0.9.0}/flexlock/__init__.py +1 -1
  20. {flexlock-0.8.2 → flexlock-0.9.0}/flexlock/api.py +424 -21
  21. {flexlock-0.8.2 → flexlock-0.9.0}/flexlock/backends/slurm.py +17 -5
  22. {flexlock-0.8.2 → flexlock-0.9.0}/flexlock/cli.py +211 -17
  23. {flexlock-0.8.2 → flexlock-0.9.0}/flexlock/config.py +37 -0
  24. {flexlock-0.8.2 → flexlock-0.9.0}/flexlock/diff.py +31 -2
  25. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/flexlock/diff_cli.py +40 -13
  26. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/flexlock/export.py +5 -0
  27. {flexlock-0.8.2 → flexlock-0.9.0}/flexlock/fingerprint.py +78 -3
  28. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/flexlock/git_utils.py +98 -0
  29. {flexlock-0.8.2 → flexlock-0.9.0}/flexlock/index.py +62 -26
  30. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/flexlock/load_stage.py +6 -8
  31. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/flexlock/mlflow.py +11 -11
  32. {flexlock-0.8.2 → flexlock-0.9.0}/flexlock/parallel.py +21 -4
  33. flexlock-0.9.0/flexlock/presets.py +515 -0
  34. {flexlock-0.8.2 → flexlock-0.9.0}/flexlock/query.py +23 -31
  35. flexlock-0.9.0/flexlock/record.py +171 -0
  36. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/flexlock/resolvers.py +39 -9
  37. {flexlock-0.8.2 → flexlock-0.9.0}/flexlock/run_record.py +145 -1
  38. {flexlock-0.8.2 → flexlock-0.9.0}/flexlock/runner.py +323 -55
  39. {flexlock-0.8.2 → flexlock-0.9.0}/flexlock/skills/flexlock-new-stage/SKILL.md +6 -0
  40. {flexlock-0.8.2 → flexlock-0.9.0}/flexlock/skills/flexlock-survey/SKILL.md +12 -1
  41. {flexlock-0.8.2 → flexlock-0.9.0}/flexlock/snapshot.py +48 -10
  42. {flexlock-0.8.2 → flexlock-0.9.0}/flexlock/taskdb.py +38 -3
  43. {flexlock-0.8.2 → flexlock-0.9.0}/flexlock/utils.py +234 -21
  44. flexlock-0.9.0/flexlock/worker.py +449 -0
  45. {flexlock-0.8.2 → flexlock-0.9.0}/pyproject.toml +3 -3
  46. flexlock-0.9.0/tests/test_code_drift.py +197 -0
  47. {flexlock-0.8.2 → flexlock-0.9.0}/tests/test_diff_cli.py +10 -0
  48. {flexlock-0.8.2 → flexlock-0.9.0}/tests/test_fingerprint.py +65 -0
  49. {flexlock-0.8.2 → flexlock-0.9.0}/tests/test_multistage_config.py +80 -3
  50. flexlock-0.9.0/tests/test_pipeline_tasks.py +564 -0
  51. flexlock-0.9.0/tests/test_presets.py +361 -0
  52. {flexlock-0.8.2 → flexlock-0.9.0}/tests/test_resolvers.py +10 -0
  53. flexlock-0.9.0/tests/test_single_task_runlock.py +268 -0
  54. flexlock-0.9.0/tests/test_swaps.py +126 -0
  55. flexlock-0.9.0/tests/test_task_records.py +255 -0
  56. flexlock-0.8.2/.claude/settings.local.json +0 -7
  57. flexlock-0.8.2/.claude/worktrees/dedup-deps/.claude/settings.local.json +0 -7
  58. flexlock-0.8.2/.claude/worktrees/dedup-deps/docs/debugging.md +0 -352
  59. flexlock-0.8.2/.claude/worktrees/dedup-deps/docs/hpc_integration.md +0 -712
  60. flexlock-0.8.2/.claude/worktrees/dedup-deps/docs/index.rst +0 -40
  61. flexlock-0.8.2/.claude/worktrees/dedup-deps/docs/resolvers.md +0 -445
  62. flexlock-0.8.2/.claude/worktrees/dedup-deps/docs/usage_guide.md +0 -1075
  63. flexlock-0.8.2/.claude/worktrees/dedup-deps/flexlock/__init__.py +0 -87
  64. flexlock-0.8.2/.claude/worktrees/dedup-deps/flexlock/api.py +0 -1362
  65. flexlock-0.8.2/.claude/worktrees/dedup-deps/flexlock/backends/slurm.py +0 -333
  66. flexlock-0.8.2/.claude/worktrees/dedup-deps/flexlock/cli.py +0 -953
  67. flexlock-0.8.2/.claude/worktrees/dedup-deps/flexlock/config.py +0 -107
  68. flexlock-0.8.2/.claude/worktrees/dedup-deps/flexlock/diff.py +0 -253
  69. flexlock-0.8.2/.claude/worktrees/dedup-deps/flexlock/fingerprint.py +0 -129
  70. flexlock-0.8.2/.claude/worktrees/dedup-deps/flexlock/index.py +0 -300
  71. flexlock-0.8.2/.claude/worktrees/dedup-deps/flexlock/parallel.py +0 -378
  72. flexlock-0.8.2/.claude/worktrees/dedup-deps/flexlock/query.py +0 -739
  73. flexlock-0.8.2/.claude/worktrees/dedup-deps/flexlock/run_record.py +0 -173
  74. flexlock-0.8.2/.claude/worktrees/dedup-deps/flexlock/runner.py +0 -560
  75. flexlock-0.8.2/.claude/worktrees/dedup-deps/flexlock/save_dir.py +0 -124
  76. flexlock-0.8.2/.claude/worktrees/dedup-deps/flexlock/skills/flexlock-new-stage/SKILL.md +0 -56
  77. flexlock-0.8.2/.claude/worktrees/dedup-deps/flexlock/skills/flexlock-run-and-watch/SKILL.md +0 -58
  78. flexlock-0.8.2/.claude/worktrees/dedup-deps/flexlock/skills/flexlock-survey/SKILL.md +0 -61
  79. flexlock-0.8.2/.claude/worktrees/dedup-deps/flexlock/snapshot.py +0 -245
  80. flexlock-0.8.2/.claude/worktrees/dedup-deps/flexlock/taskdb.py +0 -589
  81. flexlock-0.8.2/.claude/worktrees/dedup-deps/flexlock/utils.py +0 -710
  82. flexlock-0.8.2/.claude/worktrees/dedup-deps/flexlock/worker.py +0 -199
  83. flexlock-0.8.2/.claude/worktrees/dedup-deps/flexlock/worker_cli.py +0 -138
  84. flexlock-0.8.2/.claude/worktrees/dedup-deps/pixi.lock +0 -5793
  85. flexlock-0.8.2/.claude/worktrees/dedup-deps/pyproject.toml +0 -103
  86. flexlock-0.8.2/.claude/worktrees/dedup-deps/tests/test_api.py +0 -425
  87. flexlock-0.8.2/.claude/worktrees/dedup-deps/tests/test_diff_cli.py +0 -66
  88. flexlock-0.8.2/.claude/worktrees/dedup-deps/tests/test_fingerprint.py +0 -112
  89. flexlock-0.8.2/.claude/worktrees/dedup-deps/tests/test_multistage_config.py +0 -347
  90. flexlock-0.8.2/.claude/worktrees/dedup-deps/tests/test_resolvers.py +0 -421
  91. flexlock-0.8.2/.claude/worktrees/dedup-deps/tests/test_save_dir.py +0 -125
  92. flexlock-0.8.2/.claude/worktrees/dedup-deps/tests/test_utils.py +0 -923
  93. flexlock-0.8.2/.claude/worktrees/dedup-deps/tests/test_worker_preflight.py +0 -97
  94. flexlock-0.8.2/.gitattributes +0 -2
  95. flexlock-0.8.2/.gitignore +0 -16
  96. flexlock-0.8.2/FABLE_IMPLEMENTATION_PLAN.md +0 -368
  97. flexlock-0.8.2/README.md +0 -341
  98. flexlock-0.8.2/bench/taskdb_contention.py +0 -126
  99. flexlock-0.8.2/docs/README.md +0 -23
  100. flexlock-0.8.2/docs/agentic_workflows.md +0 -131
  101. flexlock-0.8.2/docs/api.rst +0 -127
  102. flexlock-0.8.2/docs/cli_reference.md +0 -811
  103. flexlock-0.8.2/docs/conf.py +0 -64
  104. flexlock-0.8.2/docs/dev_plan_task_tags.md +0 -181
  105. flexlock-0.8.2/docs/mlflow_integration.md +0 -960
  106. flexlock-0.8.2/docs/philosophy.md +0 -171
  107. flexlock-0.8.2/docs/python_api.md +0 -905
  108. flexlock-0.8.2/docs/quickstart.md +0 -175
  109. flexlock-0.8.2/docs/reference.md +0 -305
  110. flexlock-0.8.2/docs/requirements.txt +0 -10
  111. flexlock-0.8.2/docs/resolution_simplification_plan.md +0 -226
  112. flexlock-0.8.2/docs/upload_docs.sh +0 -17
  113. flexlock-0.8.2/flexlock/backends/__init__.py +0 -11
  114. flexlock-0.8.2/flexlock/backends/base.py +0 -41
  115. flexlock-0.8.2/flexlock/backends/pbs.py +0 -219
  116. flexlock-0.8.2/flexlock/context.py +0 -7
  117. flexlock-0.8.2/flexlock/data_hash.py +0 -310
  118. flexlock-0.8.2/flexlock/debug.py +0 -473
  119. flexlock-0.8.2/flexlock/diff_cli.py +0 -155
  120. flexlock-0.8.2/flexlock/exceptions.py +0 -50
  121. flexlock-0.8.2/flexlock/export.py +0 -134
  122. flexlock-0.8.2/flexlock/flexcli.py +0 -163
  123. flexlock-0.8.2/flexlock/freeze.py +0 -291
  124. flexlock-0.8.2/flexlock/git_utils.py +0 -196
  125. flexlock-0.8.2/flexlock/load_stage.py +0 -60
  126. flexlock-0.8.2/flexlock/mlflow.py +0 -192
  127. flexlock-0.8.2/flexlock/report.py +0 -78
  128. flexlock-0.8.2/flexlock/report_template.html +0 -246
  129. flexlock-0.8.2/flexlock/resolvers.py +0 -248
  130. flexlock-0.8.2/flexlock/run_cli.py +0 -44
  131. flexlock-0.8.2/flexlock/skills/flexlock-report/SKILL.md +0 -50
  132. flexlock-0.8.2/flexlock/status_cli.py +0 -299
  133. flexlock-0.8.2/flexlock/worker.py +0 -228
  134. flexlock-0.8.2/plan_ergonomics_fable.md +0 -200
  135. flexlock-0.8.2/suggested_improvements.md +0 -126
  136. flexlock-0.8.2/tests/test_chained_and_paths.py +0 -227
  137. flexlock-0.8.2/tests/test_check.py +0 -62
  138. flexlock-0.8.2/tests/test_cli.py +0 -488
  139. flexlock-0.8.2/tests/test_completion_marker.py +0 -154
  140. flexlock-0.8.2/tests/test_data_hash.py +0 -244
  141. flexlock-0.8.2/tests/test_debug_enhanced.py +0 -403
  142. flexlock-0.8.2/tests/test_debug_integration.py +0 -105
  143. flexlock-0.8.2/tests/test_deferred.py +0 -98
  144. flexlock-0.8.2/tests/test_diff.py +0 -559
  145. flexlock-0.8.2/tests/test_dump_enqueue.py +0 -274
  146. flexlock-0.8.2/tests/test_flexcli.py +0 -89
  147. flexlock-0.8.2/tests/test_flexcli_jupyter.py +0 -201
  148. flexlock-0.8.2/tests/test_git_utils.py +0 -181
  149. flexlock-0.8.2/tests/test_index.py +0 -202
  150. flexlock-0.8.2/tests/test_load_stage.py +0 -176
  151. flexlock-0.8.2/tests/test_mlflowlink.py +0 -246
  152. flexlock-0.8.2/tests/test_node_selection.py +0 -524
  153. flexlock-0.8.2/tests/test_parallel.py +0 -584
  154. flexlock-0.8.2/tests/test_progressive_framework.py +0 -136
  155. flexlock-0.8.2/tests/test_query.py +0 -371
  156. flexlock-0.8.2/tests/test_report.py +0 -104
  157. flexlock-0.8.2/tests/test_run_record.py +0 -120
  158. flexlock-0.8.2/tests/test_runner.py +0 -293
  159. flexlock-0.8.2/tests/test_skills.py +0 -72
  160. flexlock-0.8.2/tests/test_slurm_validation.py +0 -250
  161. flexlock-0.8.2/tests/test_snapshot.py +0 -437
  162. flexlock-0.8.2/tests/test_submit_params.py +0 -377
  163. flexlock-0.8.2/tests/test_sweep_semantics.py +0 -312
  164. flexlock-0.8.2/tests/test_target_includes.py +0 -135
  165. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/.gitattributes +0 -0
  166. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/.gitignore +0 -0
  167. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/FABLE_IMPLEMENTATION_PLAN.md +0 -0
  168. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/docs/README.md +0 -0
  169. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/docs/agentic_workflows.md +0 -0
  170. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/docs/conf.py +0 -0
  171. {flexlock-0.8.2 → flexlock-0.9.0}/docs/debugging.md +0 -0
  172. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/docs/mlflow_integration.md +0 -0
  173. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/docs/quickstart.md +0 -0
  174. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/docs/reference.md +0 -0
  175. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/docs/requirements.txt +0 -0
  176. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/docs/resolution_simplification_plan.md +0 -0
  177. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/docs/upload_docs.sh +0 -0
  178. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/flexlock/backends/__init__.py +0 -0
  179. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/flexlock/backends/base.py +0 -0
  180. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/flexlock/backends/pbs.py +0 -0
  181. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/flexlock/context.py +0 -0
  182. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/flexlock/data_hash.py +0 -0
  183. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/flexlock/debug.py +0 -0
  184. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/flexlock/exceptions.py +0 -0
  185. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/flexlock/flexcli.py +0 -0
  186. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/flexlock/freeze.py +0 -0
  187. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/flexlock/report.py +0 -0
  188. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/flexlock/report_template.html +0 -0
  189. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/flexlock/run_cli.py +0 -0
  190. {flexlock-0.8.2 → flexlock-0.9.0}/flexlock/save_dir.py +0 -0
  191. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/flexlock/skills/flexlock-report/SKILL.md +0 -0
  192. {flexlock-0.8.2 → flexlock-0.9.0}/flexlock/skills/flexlock-run-and-watch/SKILL.md +0 -0
  193. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/flexlock/status_cli.py +0 -0
  194. {flexlock-0.8.2 → flexlock-0.9.0}/flexlock/worker_cli.py +0 -0
  195. {flexlock-0.8.2 → flexlock-0.9.0}/pixi.lock +0 -0
  196. {flexlock-0.8.2 → flexlock-0.9.0}/tests/test_api.py +0 -0
  197. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_chained_and_paths.py +0 -0
  198. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_check.py +0 -0
  199. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_cli.py +0 -0
  200. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_completion_marker.py +0 -0
  201. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_data_hash.py +0 -0
  202. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_debug_enhanced.py +0 -0
  203. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_debug_integration.py +0 -0
  204. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_deferred.py +0 -0
  205. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_diff.py +0 -0
  206. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_dump_enqueue.py +0 -0
  207. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_flexcli.py +0 -0
  208. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_flexcli_jupyter.py +0 -0
  209. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_git_utils.py +0 -0
  210. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_index.py +0 -0
  211. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_load_stage.py +0 -0
  212. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_mlflowlink.py +0 -0
  213. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_node_selection.py +0 -0
  214. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_parallel.py +0 -0
  215. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_progressive_framework.py +0 -0
  216. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_query.py +0 -0
  217. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_report.py +0 -0
  218. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_run_record.py +0 -0
  219. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_runner.py +0 -0
  220. {flexlock-0.8.2 → flexlock-0.9.0}/tests/test_save_dir.py +0 -0
  221. {flexlock-0.8.2 → flexlock-0.9.0}/tests/test_save_dir_guard.py +0 -0
  222. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_skills.py +0 -0
  223. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_slurm_validation.py +0 -0
  224. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_snapshot.py +0 -0
  225. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_submit_params.py +0 -0
  226. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_sweep_semantics.py +0 -0
  227. {flexlock-0.8.2/.claude/worktrees/dedup-deps → flexlock-0.9.0}/tests/test_target_includes.py +0 -0
  228. {flexlock-0.8.2 → flexlock-0.9.0}/tests/test_utils.py +0 -0
  229. {flexlock-0.8.2 → flexlock-0.9.0}/tests/test_worker_preflight.py +0 -0
@@ -0,0 +1,35 @@
1
+ """Check that the version declarations agree (and match the tag, if given).
2
+
3
+ The wheel version comes from ``flexlock/__init__.py`` (hatch dynamic version);
4
+ the conda package version from ``[tool.pixi.package] version`` in
5
+ pyproject.toml. Usage: ``python .github/scripts/check_version.py [vX.Y.Z]``.
6
+ """
7
+
8
+ import re
9
+ import sys
10
+ import tomllib
11
+ from pathlib import Path
12
+
13
+ root = Path(__file__).resolve().parents[2]
14
+ init = (root / "flexlock" / "__init__.py").read_text()
15
+ m = re.search(r'^__version__\s*=\s*"([^"]+)"', init, re.M)
16
+ if not m:
17
+ sys.exit("could not find __version__ in flexlock/__init__.py")
18
+ py_version = m.group(1)
19
+
20
+ pixi_version = tomllib.loads((root / "pyproject.toml").read_text())["tool"]["pixi"]["package"]["version"]
21
+
22
+ errors = []
23
+ if py_version != pixi_version:
24
+ errors.append(
25
+ f"flexlock/__init__.py __version__={py_version!r} != "
26
+ f"[tool.pixi.package] version={pixi_version!r}"
27
+ )
28
+ if len(sys.argv) > 1:
29
+ tag = sys.argv[1].removeprefix("refs/tags/")
30
+ if tag != f"v{py_version}":
31
+ errors.append(f"tag {tag!r} does not match package version v{py_version}")
32
+
33
+ if errors:
34
+ sys.exit("\n".join(errors))
35
+ print(f"version OK: {py_version}")
@@ -0,0 +1,66 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: ci-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ tests:
18
+ name: tests (py${{ matrix.python-version }})
19
+ runs-on: ubuntu-latest
20
+ strategy:
21
+ fail-fast: false
22
+ matrix:
23
+ python-version: ["3.11", "3.12", "3.13"]
24
+ steps:
25
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
26
+ with:
27
+ fetch-depth: 0 # git_utils / snapshot tests inspect history
28
+
29
+ - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
30
+ with:
31
+ python-version: ${{ matrix.python-version }}
32
+ activate-environment: true
33
+
34
+ - name: Install package and test deps
35
+ # Mirrors [tool.pixi.feature.test]; attrs is needed by test_utils.
36
+ run: uv pip install -e . "pytest>=8.3.2,<9" "pytest-mock>=3.14.0,<4" ipython attrs
37
+
38
+ - name: Git identity (tests create repos and commits)
39
+ run: |
40
+ git config --global user.name "flexlock-ci"
41
+ git config --global user.email "flexlock-ci@users.noreply.github.com"
42
+
43
+ - name: Run tests
44
+ run: pytest -q
45
+
46
+ build:
47
+ name: build sdist/wheel
48
+ runs-on: ubuntu-latest
49
+ steps:
50
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
51
+
52
+ - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
53
+ with:
54
+ python-version: "3.11"
55
+
56
+ - name: Check version declarations agree
57
+ run: python .github/scripts/check_version.py
58
+
59
+ - run: uv build
60
+
61
+ - name: Wheel installs into a clean venv and imports
62
+ run: |
63
+ uv venv /tmp/wheelcheck
64
+ uv pip install --python /tmp/wheelcheck/bin/python dist/*.whl
65
+ cd /tmp && /tmp/wheelcheck/bin/python -c "import flexlock; print(flexlock.__version__)"
66
+ /tmp/wheelcheck/bin/flexlock --help > /dev/null
@@ -0,0 +1,55 @@
1
+ name: Docs
2
+
3
+ # Builds the Sphinx docs on every PR; deploys to GitHub Pages
4
+ # (https://quentinf00.github.io/flexlock/) on pushes to main.
5
+ # Requires Settings > Pages > Source = "GitHub Actions".
6
+ on:
7
+ push:
8
+ branches: [main]
9
+ pull_request:
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ concurrency:
16
+ group: pages-${{ github.ref }}
17
+ cancel-in-progress: false
18
+
19
+ jobs:
20
+ build:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
24
+
25
+ - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
26
+ with:
27
+ python-version: "3.11"
28
+ activate-environment: true
29
+
30
+ - name: Install docs deps
31
+ # Mirrors [tool.pixi.feature.docs]; conf.py imports flexlock.
32
+ run: uv pip install . sphinx myst-parser sphinx-rtd-theme
33
+
34
+ - name: Build HTML
35
+ run: |
36
+ sphinx-build -b html docs docs/_build/html
37
+ touch docs/_build/html/.nojekyll
38
+
39
+ - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
40
+ with:
41
+ path: docs/_build/html
42
+
43
+ deploy:
44
+ if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
45
+ needs: build
46
+ runs-on: ubuntu-latest
47
+ permissions:
48
+ pages: write
49
+ id-token: write
50
+ environment:
51
+ name: github-pages
52
+ url: ${{ steps.deployment.outputs.page_url }}
53
+ steps:
54
+ - id: deployment
55
+ uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
@@ -0,0 +1,93 @@
1
+ name: Release
2
+
3
+ # Push a tag vX.Y.Z (matching flexlock/__init__.py and [tool.pixi.package]
4
+ # version) to build and publish the PyPI and conda packages.
5
+ on:
6
+ push:
7
+ tags: ["v*"]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ build-dist:
14
+ name: build sdist/wheel
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
18
+
19
+ - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
20
+ with:
21
+ python-version: "3.11"
22
+
23
+ - name: Tag matches package versions
24
+ run: python .github/scripts/check_version.py "${GITHUB_REF_NAME}"
25
+
26
+ - run: uv build
27
+
28
+ - name: Wheel installs into a clean venv and imports
29
+ run: |
30
+ uv venv /tmp/wheelcheck
31
+ uv pip install --python /tmp/wheelcheck/bin/python dist/*.whl
32
+ cd /tmp && /tmp/wheelcheck/bin/python -c "import flexlock; print(flexlock.__version__)"
33
+
34
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
35
+ with:
36
+ name: dist
37
+ path: dist/
38
+ if-no-files-found: error
39
+
40
+ publish-pypi:
41
+ # Enable this repository variable after configuring the corresponding
42
+ # trusted publisher on PyPI. Local twine publishing works independently.
43
+ if: vars.PYPI_TRUSTED_PUBLISHING == 'true'
44
+ name: publish to PyPI (trusted publishing)
45
+ needs: build-dist
46
+ runs-on: ubuntu-latest
47
+ environment:
48
+ name: pypi
49
+ url: https://pypi.org/p/flexlock
50
+ permissions:
51
+ id-token: write # OIDC token for PyPI trusted publishing; no stored secret
52
+ steps:
53
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
54
+ with:
55
+ name: dist
56
+ path: dist/
57
+
58
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
59
+
60
+ conda:
61
+ name: build + upload conda package (anaconda.org/quentinf00)
62
+ runs-on: ubuntu-latest
63
+ env:
64
+ ANACONDA_API_TOKEN: ${{ secrets.ANACONDA_API_TOKEN }}
65
+ environment:
66
+ name: anaconda
67
+ url: https://anaconda.org/quentinf00/flexlock
68
+ steps:
69
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
70
+
71
+ - name: Tag matches package versions
72
+ run: python3 .github/scripts/check_version.py "${GITHUB_REF_NAME}"
73
+
74
+ - uses: prefix-dev/setup-pixi@d3f436a425481402e6a95a1d1fc10331c708cd9e # v0.10.2
75
+ with:
76
+ pixi-version: v0.73.0
77
+ run-install: false
78
+
79
+ - name: Build conda package ([tool.pixi.package], pixi-build-python)
80
+ run: pixi build --output-dir conda-out
81
+
82
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
83
+ with:
84
+ name: conda-package
85
+ path: conda-out/*.conda
86
+ if-no-files-found: error
87
+
88
+ - name: Upload to anaconda.org
89
+ if: env.ANACONDA_API_TOKEN != ''
90
+ run: |
91
+ pixi exec --spec "anaconda-client>=1.14" -- \
92
+ anaconda -s api.anaconda.org -t "$ANACONDA_API_TOKEN" \
93
+ upload --user quentinf00 --label main conda-out/*.conda
@@ -0,0 +1,53 @@
1
+ # Changelog
2
+
3
+ ## 0.9.0 (2026-10-02)
4
+
5
+ ### Added
6
+ - Composite pipelines across sweeps, local/HPC workers, and enqueue/dequeue.
7
+ - Full per-stage run records in directories or the task DB, including upstream
8
+ references, code provenance, and code-drift detection.
9
+ - Environment lockfiles in run fingerprints.
10
+ - Preset tracking and `flexlock runs` / `flexlock presets`.
11
+ - `${run:...}` references to completed preset runs and `key=@name` config swaps.
12
+
13
+ ### Fixed
14
+ - Composite task snapshots retain every executed stage instead of overwriting
15
+ earlier stage records. Exports include each stage's inherited code and environment.
16
+
17
+ ### Release infrastructure
18
+ - CI on Python 3.11–3.13, wheel/sdist checks, tag-driven PyPI and conda publishing,
19
+ and GitHub Pages documentation.
20
+
21
+ The unreleased 0.8.3 fixes below are included in this release.
22
+
23
+ ## 0.8.3 (unreleased)
24
+
25
+ ### Fixed
26
+ - **Single HPC / `isolated=True` runs now get a full `run.lock`.** They used to
27
+ keep the submit-time placeholder (`config` = `save_dir` + `_snapshot_` only)
28
+ because the worker stored the real snapshot only in `run.lock.tasks.db`. That
29
+ broke `${run_lock:...}` (`KeyError`), made `flexlock-run -c run.lock` a
30
+ silent no-op, showed empty configs in `show`/`diff`/`ls`, linked no commits
31
+ on `tag`, and let `gc` delete the upstreams of a tagged run. The worker now
32
+ writes the full snapshot (config, fingerprint, git state, `note`) atomically
33
+ when its task owns the master dir. Sweep roots keep their placeholder.
34
+ - Readers (`${run_lock:...}`, `show`/`graph`/`why`/`report`, `flexlock-diff`,
35
+ `ls`/`gc`/`tag`, `load_stage`, lineage, `flexlock-run -c run.lock`) fall back
36
+ to the task-DB snapshot for placeholder `run.lock`s written by older versions.
37
+ - The worker refreshes `run.lock.tasks` when its queue drains, so it is no
38
+ longer left at `[]` after a `wait=False` submit.
39
+ - `force=True` on a single HPC / isolated submission deleted `run.complete` but
40
+ ran nothing: the old `done` row in the task DB was kept by `INSERT OR IGNORE`.
41
+ The task DB is now reset and the task re-executes. The reset also drops the
42
+ cached sqlite connection, which kept writing to the deleted DB file.
43
+ - Slurm backend: forkserver compatibility fix.
44
+ - `docs/hpc_integration.md`: the task DB lives inside `save_dir`, not in its
45
+ parent. The overview section, clobbered by an earlier edit, is restored.
46
+
47
+ ### Added
48
+ - `flexlock.run_record.load_lock_data(run_dir)`: side-effect-free effective
49
+ `run.lock` (placeholder → task-DB snapshot).
50
+ - `flexlock.run_record.materialize_lock(run_dir)`: one-off rewrite of a
51
+ placeholder `run.lock` (keeps `run.lock.placeholder.bak`).
52
+ - `flexlock repair-locks <root> [-n]`: runs `materialize_lock` over a results tree.
53
+ - `flexlock.taskdb.reset_db(db_path)`.
@@ -1,3 +1,19 @@
1
+ Metadata-Version: 2.5
2
+ Name: flexlock
3
+ Version: 0.9.0
4
+ Summary: A lightweight and painless SciOps library
5
+ Author-email: Quentin Febvre <quentin.febvre@gmail.com>
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.11
8
+ Requires-Dist: cloudpickle<4,>=3.0.0
9
+ Requires-Dist: gitpython<4,>=3.1.46
10
+ Requires-Dist: joblib<2,>=1.5.2
11
+ Requires-Dist: loguru<0.8,>=0.7.3
12
+ Requires-Dist: omegaconf<3,>=2.3.0
13
+ Requires-Dist: pandas<3,>=2.3.0
14
+ Requires-Dist: xxhash<4,>=3.5.0
15
+ Description-Content-Type: text/markdown
16
+
1
17
  # FlexLock
2
18
 
3
19
  **Reproducible, Composable, and Scalable Computational Experiments**
@@ -292,14 +308,24 @@ pip install -e .
292
308
 
293
309
  ## Comparison with Other Tools
294
310
 
295
- | Feature | FlexLock | Hydra | MLflow | DVC |
296
- |---------|----------|-------|--------|-----|
297
- | **Configuration** | Python + YAML | YAML | Code | YAML |
298
- | **Reproducibility** | Automatic | Manual | Partial | Partial |
299
- | **Smart Caching** | ✅ | ❌ | ❌ | ✅ |
300
- | **Multi-stage Pipelines** | ✅ | ❌ | ❌ | ✅ |
301
- | **Parallel Sweeps** | ✅ | ✅ | ❌ | ❌ |
302
- | **Learning Curve** | Low | Medium | Low | High |
311
+ FlexLock combines Hydra-style configuration (overrides, sweeps, callables as
312
+ config) with DVC-style provenance (runs traceable to code, config and data,
313
+ and skipped when unchanged). It grew out of
314
+ [ZenDag](https://github.com/quentinf00/zendag), which generated DVC pipelines
315
+ from Hydra configs. The key difference: the cache key belongs to each run,
316
+ not to a workspace lockfile, so sweeps and HPC jobs need no shared state
317
+ beyond the filesystem.
318
+
319
+ | | FlexLock | Hydra | DVC | redun | Snakemake |
320
+ |---|---|---|---|---|---|
321
+ | Overrides and sweeps | ✅ | ✅ | ✅ `dvc exp` | ❌ | ❌ |
322
+ | Skips unchanged runs | ✅ per run | ❌ | ✅ per stage | ✅ per call | ✅ per rule |
323
+ | Code identity in cache key | git tree of `_target_` modules | ❌ | listed deps | task source | rule code |
324
+ | Slurm / PBS | ✅ | ✅ submitit | ❌ | ❌ cloud only | ✅ |
325
+ | Remote output storage | ❌ | ❌ | ✅ | ✅ | ✅ |
326
+
327
+ See [Philosophy & Design](docs/philosophy.md#comparison-with-other-tools) for
328
+ the full comparison and when to pick another tool.
303
329
 
304
330
  ## Contributing
305
331
 
@@ -1,19 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: flexlock
3
- Version: 0.8.2
4
- Summary: A lightweight and painless SciOps library
5
- Author-email: Quentin Febvre <quentin.febvre@gmail.com>
6
- License-Expression: MIT
7
- Requires-Python: >=3.11
8
- Requires-Dist: cloudpickle<4,>=3.0.0
9
- Requires-Dist: gitpython<4,>=3.1.45
10
- Requires-Dist: joblib<2,>=1.5.2
11
- Requires-Dist: loguru<0.8,>=0.7.3
12
- Requires-Dist: omegaconf<3,>=2.3.0
13
- Requires-Dist: pandas<3,>=2.3.2
14
- Requires-Dist: xxhash<4,>=3.5.0
15
- Description-Content-Type: text/markdown
16
-
17
1
  # FlexLock
18
2
 
19
3
  **Reproducible, Composable, and Scalable Computational Experiments**
@@ -308,14 +292,24 @@ pip install -e .
308
292
 
309
293
  ## Comparison with Other Tools
310
294
 
311
- | Feature | FlexLock | Hydra | MLflow | DVC |
312
- |---------|----------|-------|--------|-----|
313
- | **Configuration** | Python + YAML | YAML | Code | YAML |
314
- | **Reproducibility** | Automatic | Manual | Partial | Partial |
315
- | **Smart Caching** | ✅ | ❌ | ❌ | ✅ |
316
- | **Multi-stage Pipelines** | ✅ | ❌ | ❌ | ✅ |
317
- | **Parallel Sweeps** | ✅ | ✅ | ❌ | ❌ |
318
- | **Learning Curve** | Low | Medium | Low | High |
295
+ FlexLock combines Hydra-style configuration (overrides, sweeps, callables as
296
+ config) with DVC-style provenance (runs traceable to code, config and data,
297
+ and skipped when unchanged). It grew out of
298
+ [ZenDag](https://github.com/quentinf00/zendag), which generated DVC pipelines
299
+ from Hydra configs. The key difference: the cache key belongs to each run,
300
+ not to a workspace lockfile, so sweeps and HPC jobs need no shared state
301
+ beyond the filesystem.
302
+
303
+ | | FlexLock | Hydra | DVC | redun | Snakemake |
304
+ |---|---|---|---|---|---|
305
+ | Overrides and sweeps | ✅ | ✅ | ✅ `dvc exp` | ❌ | ❌ |
306
+ | Skips unchanged runs | ✅ per run | ❌ | ✅ per stage | ✅ per call | ✅ per rule |
307
+ | Code identity in cache key | git tree of `_target_` modules | ❌ | listed deps | task source | rule code |
308
+ | Slurm / PBS | ✅ | ✅ submitit | ❌ | ❌ cloud only | ✅ |
309
+ | Remote output storage | ❌ | ❌ | ✅ | ✅ | ✅ |
310
+
311
+ See [Philosophy & Design](docs/philosophy.md#comparison-with-other-tools) for
312
+ the full comparison and when to pick another tool.
319
313
 
320
314
  ## Contributing
321
315
 
@@ -111,6 +111,11 @@ FlexLock API Reference
111
111
  :undoc-members:
112
112
  :show-inheritance:
113
113
 
114
+ .. automodule:: flexlock.run_record
115
+ :members:
116
+ :undoc-members:
117
+ :show-inheritance:
118
+
114
119
  .. automodule:: flexlock.taskdb
115
120
  :members:
116
121
  :undoc-members:
@@ -0,0 +1,2 @@
1
+ ```{include} ../CHANGELOG.md
2
+ ```
@@ -88,6 +88,22 @@ flexlock gc results/ -f --refs
88
88
 
89
89
  ---
90
90
 
91
+ ### `flexlock repair-locks` — Rewrite Placeholder run.locks
92
+
93
+ Single HPC/isolated runs submitted with FlexLock < 0.8.3 kept their real
94
+ snapshot only in `run.lock.tasks.db`; their `run.lock` is a placeholder. The
95
+ readers fall back to the DB automatically. This command rewrites those files
96
+ once (see [HPC Integration](hpc_integration.md#runlock-of-a-single-hpc-run)).
97
+
98
+ ```bash
99
+ flexlock repair-locks results/ -n # dry run: list the dirs that would change
100
+ flexlock repair-locks results/ # rewrite; keeps run.lock.placeholder.bak
101
+ ```
102
+
103
+ Sweep roots are left alone (they have no single config).
104
+
105
+ ---
106
+
91
107
  ### `flexlock show` — Inspect One Run
92
108
 
93
109
  Machine-readable status, metadata, lineage, and config for a single run. The
@@ -153,6 +169,35 @@ DEF=myproject.pipeline.cfg
153
169
  flexlock-run -d "$DEF" -s "$(flexlock stages -d "$DEF" --format keys | fzf)"
154
170
  ```
155
171
 
172
+ ### `flexlock runs` — Runs of a Preset
173
+
174
+ List the complete runs of a preset (the `-d` target + `-s` key they were
175
+ launched with), newest first. See the usage guide, §7 "Presets".
176
+
177
+ ```bash
178
+ flexlock runs xps_glob.train_small_cloud_gap_compact # suffix match
179
+ flexlock runs pkg.starter.xps_glob:train_x -s main --limit 5
180
+ flexlock runs train_x --strict # only runs made with no -o/-O/-m/-M
181
+ flexlock runs train_x --format json # {path, preset, select, completed, note, overrides}
182
+ flexlock runs train_x --root results_old # also search results_old/.flexlock
183
+ ```
184
+
185
+ Searched: the `.flexlock/` serving the current directory, any `.flexlock/` up
186
+ to two levels below it, and `--root` dirs. Links to deleted runs are pruned.
187
+
188
+ ### `flexlock presets` — Catalogue of a Config Module
189
+
190
+ List the attributes of a config module that hold runnable stages, with the
191
+ comment block above each definition, the `-s` keys, and run counts.
192
+
193
+ ```bash
194
+ flexlock presets sst_ml_mapping.starter.xps_glob # text
195
+ flexlock presets sst_ml_mapping.starter.xps_glob --format md # a generated index
196
+ flexlock presets configs/defaults.py --format json
197
+ ```
198
+
199
+ The module is imported, as `flexlock-run -d` would.
200
+
156
201
  ### `flexlock report` — Static HTML Report
157
202
 
158
203
  Render a self-contained, offline-openable HTML report (no CDN) of a results tree.
@@ -281,8 +326,23 @@ flexlock-run -d myproject.defaults -s train
281
326
 
282
327
  # Select nested node
283
328
  flexlock-run -d myproject.defaults -s experiments.baseline
329
+
330
+ # Multi-stage: run several stages in order as a pipeline (space- or
331
+ # comma-separated). Each sweep item becomes one composite pipeline task.
332
+ flexlock-run -d myproject.defaults -s train linear_probe -o pipeline_dir=results/xp
333
+ flexlock-run -d myproject.defaults -s train,linear_probe \
334
+ --sweep-target pipeline_dir --sweep results/xp1,results/xp2 --n_jobs 2
284
335
  ```
285
336
 
337
+ **Multi-stage (`-s` with more than one stage)** runs the stages sequentially as
338
+ a pipeline (a downstream stage sees the upstream stage's artifacts on disk). It
339
+ composes with `--sweep*`, `--slurm-config`/`--pbs-config`, and `--enqueue`:
340
+ each sweep item is merged into the root config before selection, then becomes
341
+ one composite `{_stages_: [...]}` task whose stages run in order on one worker,
342
+ while parallelism / array workers fan out across items. It is only incompatible
343
+ with `-O`/`-M` and `-e` (they target a single selected node). See usage guide
344
+ §14g. All stages of an item share one scheduler job (no per-stage HPC resources).
345
+
286
346
  **Works with:**
287
347
  - Python configs (dict keys)
288
348
  - YAML configs (nested keys)
@@ -320,6 +380,11 @@ flexlock-run -d defaults -o base_dir=outputs seed=42
320
380
  - Primitive types: `param=1`, `flag=true`, `name=model`
321
381
  - Nested paths: `model.layers=12`
322
382
  - Lists: `devices=[0,1,2]`
383
+ - Swaps: `model=@big_model` **replaces** the node with the config
384
+ `big_model` from the `-d` module (or `@pkg.module.attr`); `@@x` is the
385
+ literal `@x`. Overrides apply in order, so `model=@big_model model.width=16`
386
+ tweaks the swapped model. Also valid for `-O` and in `--sweep` values. See
387
+ the usage guide, "Swapping a subtree".
323
388
 
324
389
  ---
325
390
 
@@ -515,9 +580,30 @@ perturb caching or diffs. It surfaces in `flexlock show`/`graph`/`report`.
515
580
  flexlock-run -d defaults -s train --note "baseline before lr sweep"
516
581
  ```
517
582
 
518
- For sweeps the note lands on the **master** `run.lock` only; sweep items inherit
519
- it for display via their `.flexlock_marker` → master lookup. For a multi-stage
520
- `-s a b` run, the same note is applied to each stage.
583
+ For sweeps the note lands on the **master** `run.lock`; sweep items inherit it
584
+ (copied into each task's `run.lock` with `--task-record dir`, looked up through
585
+ the master otherwise). For a multi-stage `-s a b` run, the same note is applied
586
+ to each stage.
587
+
588
+ ---
589
+
590
+ ### `--task-record {dir,db}`
591
+ Where each sweep task's record (config, code tree, env, fingerprint) is kept.
592
+
593
+ - `dir` (default, or `$FLEXLOCK_TASK_RECORD`): a full `run.lock` in the task's
594
+ `save_dir`, like a serial run. The directory describes itself.
595
+ - `db`: only in the task DB (`<sweep_root>/run.lock.tasks.db`), with a
596
+ `.flexlock_marker` pointer in the task dir. Use it for sweeps over thousands
597
+ of items (e.g. `--sweep-file paths.txt`) to avoid one more file per item.
598
+
599
+ Either way `flexlock diff`/`show`/`graph`/`why`, `${run_lock:}`, lineage and the
600
+ cache find the record. Single runs, including Slurm/PBS ones, always get a
601
+ `run.lock`. Sweep items that share one `save_dir` can't each have a
602
+ `run.lock`; the first task keeps the file and the others stay in the DB.
603
+
604
+ ```bash
605
+ flexlock-run -d configs.defaults -s extract --sweep-file paths.txt --task-record db
606
+ ```
521
607
 
522
608
  ---
523
609
 
@@ -572,8 +658,18 @@ flexlock-run -d defaults -s train --sweep-file queue.yaml --slurm-config slurm.y
572
658
  save_dir: results/exp
573
659
  ```
574
660
 
661
+ **With a sweep** (`--sweep`/`--sweep-key`/`--sweep-file`), `--enqueue` appends
662
+ **one entry per sweep item** — each merged at `--sweep-target` and frozen —
663
+ instead of a single config. Previously the sweep was silently dropped.
664
+
665
+ **With multi-stage** (`-s` with more than one stage), `--enqueue` appends **one
666
+ composite `{_stages_: [...]}` entry per sweep item** (or one for a no-sweep
667
+ pipeline). Composite entries in a `--sweep-file` run as-is; plain entries in the
668
+ same file are merged into the compiled node config and run as single-stage
669
+ pipelines.
670
+
575
671
  **Notes:**
576
- - The enqueued config is stored with interpolations **unresolved** so that resolvers like `${vinc:}` fire at run time and each task gets its own unique directory.
672
+ - The enqueued config is stored with interpolations **unresolved** so that resolvers like `${vinc:}`/`${run_lock:}`/`${latest:}` fire at run time and each task gets its own unique directory.
577
673
  - Writes are atomic (temp-file + rename) so concurrent `--enqueue` calls from different terminals are safe.
578
674
  - Re-running `--sweep-file queue.yaml` after adding new items is safe: tasks already completed are skipped via the `INSERT OR IGNORE` logic in the task DB.
579
675