diff --git a/complete/2026/08/clipper-in-search-identifier.md b/complete/2026/08/clipper-in-search-identifier.md new file mode 100644 index 00000000..190e1455 --- /dev/null +++ b/complete/2026/08/clipper-in-search-identifier.md @@ -0,0 +1,199 @@ +# clipper-in-search-identifier (decided: option 2 — the clipper enters the MLE identifiers) + +- library-prs: https://github.com/PyAutoLabs/PyAutoFit/pull/1494 +- merge-commits: PyAutoFit `f3767a79743e6976accd8fd6957afc5f366654fb` (2026-08-18) +- issue: PyAutoFit#1493 (closed by #1494) +- summary: The prior-support `Clipper` did not enter the search identifier, so + two runs differing only in a result-affecting setting shared one output + directory and — stacked with the `.completed` short-circuit — a later run + could silently return the earlier one's numbers. **Decided option 2:** + `__identifier_fields__ = ("clipper",)` on `AbstractMultiStartGradient` and + `AbstractBFGS`, accepting that every existing multi-start and (L)BFGS output + directory re-keys. Nested samplers, MCMC searches and `Drawer` are deliberately + untouched and pinned byte-identical. +- validation: 104 lines of new identifier tests (5 new cases) — hash-lists pinned + for `Nautilus`/`DynestyDynamic`/`Emcee`/`Zeus`/`Drawer`, a fork test over + `MultiStartAdam` and `LBFGS`, and a tripwire asserting the nested samplers + never grow a `clipper` attribute. +- release: shipped in PyAutoFit **2026.8.20.1** (2026-08-20). + +## The decision, and why — this was chosen, not overlooked + +The prompt put four options up with none pre-selected and marked itself +`Autonomy: human-required`, because either answer costs someone's stored +results. The human chose **option 2: put it in, unconditionally, scoped to the +searches that consume it.** + +Why not the others: + +- **Option 1 (status quo)** leaves a footgun that had already been stepped on + once — the phase-2 validation campaign's arms 1 and 2 differ in *nothing else* + and collided, and were only saved by per-arm unique names. +- **Option 3 (include only when not `ClipperNone`)** buys back-compatibility + with a *conditional* identifier — a new concept in that machinery, and one + every consumer (database, aggregator, `.completed` discovery) would have to be + re-checked against. Rejected as a novel special case guarding a one-off. +- **Option 4 (make the `.completed` short-circuit loud instead)** closes the + wider class of cached-result hazards and remains worth doing, but it does not + make the identifier tell the truth about what produced a result. It was not + taken here and is **not** carried forward by this record — file it fresh if + wanted. + +The cost was paid with eyes open: **stored multi-start and (L)BFGS results are +orphaned, not deleted.** Nothing finds them by identifier any more. The +re-key hits *every* such directory, including runs that never passed a clipper, +because the pre-change hash list for these searches was the class name alone +(`AbstractSearch.__identifier_fields__` is the empty tuple, and +`Identifier._add_value_to_hash_list` then contributes no fields) — adding one +field changes the hash for the default construction too. + +## The scoping constraint is the load-bearing part + +The clipper is resolved on `AbstractMLE`, and the fix declares the field on the +two MLE subclasses that *consume* it — not on `NonLinearSearch`. Hoisting it +would put the clipper within reach of the nested samplers' identifier machinery +and silently re-key the entire nested-sampling archive, which is a far larger +body of stored results and one where the setting cannot affect the answer at +all. `test_nested_samplers_have_no_clipper` exists to fail loudly if anyone +tries. + +`Drawer` is the same rule in miniature: it inherits the attribute from +`AbstractMLE` but never consumes it, so it deliberately does **not** declare the +field. **A setting that cannot change the result must not re-key stored +results** — that is the general principle this task settled, and it is worth +more than the specific answer. + +## Sequencing paid off + +The prompt argued this had to be decided **before** phase 3 (flipping the +default to `ClipperPriorBox`), so the re-baseline and the re-keying would not be +entangled. That held: the identifier change landed 2026-08-18 on its own, and +phase 3 now has one variable rather than two. + +## Residuals — what this record does NOT claim is done + +1. **The release notes do not say plainly that stored results re-key.** + PyAutoFit 2026.8.20.1's notes carry the commit subject + (`feat: clipper enters the MLE search identifiers (#1494)`) and nothing more. + The prompt asked explicitly for a plain statement that existing multi-start + and (L)BFGS output directories are orphaned. Amend that release's notes, or + carry the sentence into the next release's. +2. **"Check whether a migration is wanted" was never answered.** No migration + ships, and no decision that one is unwanted is on record. Users with stored + multi-start/(L)BFGS results have no path to re-key them. +3. **The phase-2 campaign record's ARM-COLLISION TRAP section is now stale.** + `complete/2026/08/clipper-validation-campaign.md` states as fact that the + clipper does not enter the identifier, with three empirically-verified hashes. + True on 2026-08-16, false since 2026-08-18. A pointer note was added there in + this same change; the historical measurement is left intact. +4. **Option 4 remains open** as the general fix for the `.completed` + short-circuit returning cached results when the search config differs. + +## Mind-side drift this closed + +The work shipped in PyAutoFit on 2026-08-18 while the prompt sat in +`draft/feature/autofit/clipper_in_search_identifier.md` — never issued into +`active/`, never registered, and never recorded. Four other records and drafts +still pointed at that draft path as an *open* question. This record is the +retrospective close; the code has been on `main` since #1494 merged. + +The transferable lesson: a task that ships straight from a decision — no issue, +no branch registration — leaves no trace in the Mind unless the record is +written by hand afterwards. `lifecycle.py check` does not catch it, because it +only guards `active.md` against `complete/`; a prompt that never entered +`active/` is invisible to the drift guard. + +## Original prompt + +# Decide whether the clipper belongs in the search identifier + +Type: feature +Target: autofit +Repos: +- PyAutoFit +Difficulty: medium +Autonomy: human-required +Priority: high +Status: formalised + +Filed 2026-08-16. Follow-up 4 owed by the prior-support `Clipper` +(`complete/2026/08/prior-support-clipper.md`, PyAutoFit#1477). + +**`Autonomy: human-required` on purpose.** Either answer has a cost that lands +on someone's stored results, and the trade is a judgement about the project's +data, not a technical detail an agent should settle. + +## The fact + +The clipper does **not** enter the search identifier. Verified against +PyAutoFit `main` on 2026-08-16: + +``` +af.MultiStartAdam(name="x") -> 2bada4747f74bc46bf812605a762def9 +af.MultiStartAdam(name="x", clipper=ClipperNone()) -> 2bada4747f74bc46bf812605a762def9 +af.MultiStartAdam(name="x", clipper=ClipperPriorBox()) -> 2bada4747f74bc46bf812605a762def9 +``` + +Two runs differing only in prior-support enforcement — which can change the +answer, and demonstrably does — share one output directory. + +## Both answers cost something + +**Leave it out (status quo).** Existing on-disk results are never orphaned, and +phase 1 stays back-compatible by construction. But runs that differ in a +result-affecting setting collide, and stacked with the `.completed` +short-circuit a later run silently returns the earlier one's numbers. That is +live right now for the phase-2 campaign, whose arms 1 and 2 differ in *nothing +else* — see +`draft/feature/autofit/clipper_validation_campaign.md`, which works around it +with unique per-arm names. + +**Put it in.** Arms separate naturally and the identifier tells the truth about +what produced a result. But **every existing multi-start and BFGS output +directory re-keys**, so stored results are orphaned — they are not deleted, but +nothing finds them any more. That is the same comparability argument +PyAutoFit#1472 made when deferring its own policy change, and it collides with +phase 3, which wants a benchmark re-baseline it can compare against history. + +## Options, none pre-selected + +1. **Status quo**, with the collision documented wherever it bites. Cheapest; + leaves a footgun that has already been stepped on once. +2. **Include the clipper in `__identifier_fields__`.** Honest; orphans stored + results. +3. **Include it only when it is not `ClipperNone`.** Unclipped runs keep their + existing directories, clipped runs get their own. Back-compatible *and* + collision-free — but the identifier becomes conditional, which is a new + concept in that machinery and needs checking against how identifiers are + consumed (database, aggregator, `.completed` discovery). +4. **Leave the identifier alone and make the collision loud** — refuse to + short-circuit on `.completed` when the search config differs from the one + recorded in the completed run's `search.json`. Fixes the general + cached-result hazard rather than this one instance. + +Option 3 is the obvious-looking compromise and option 4 is the one that closes +the whole class; both need someone to confirm the consumers can take it. + +## Sequencing + +**Best decided before phase 3, not after.** Phase 3 flips the default to +`ClipperPriorBox` and carries a re-baseline; if the identifier changes at the +same time, the re-baseline and the re-keying are entangled and neither can be +read cleanly. Deciding this first — either way — leaves phase 3 with one +variable. + +## Whatever is chosen + +- State the decision and its reason in the record. A future reader hitting a + collision needs to know it was chosen, not overlooked. +- If the identifier changes, say plainly in the release notes that stored + results re-key, and check whether a migration is wanted. +- If it does not change, the phase-2 mitigation (unique `name` per arm) becomes + permanent advice for anyone comparing clipper settings, and belongs in the + `Clipper` docstring rather than only in a campaign prompt. + +## Out of scope + +- Flipping the default (phase 3). +- The `.completed` short-circuit's other manifestations, unless option 4 is + chosen — in which case they are the point. diff --git a/complete/2026/08/clipper-validation-campaign.md b/complete/2026/08/clipper-validation-campaign.md index 2377dcf0..0eb2eaad 100644 --- a/complete/2026/08/clipper-validation-campaign.md +++ b/complete/2026/08/clipper-validation-campaign.md @@ -143,6 +143,14 @@ rather than live hazards — but do **not** delete the mitigations: ### ⚠ THE ARM-COLLISION TRAP — read before running anything +> **SUPERSEDED 2026-08-18 — this trap no longer exists.** The clipper now +> enters the identifier for the multi-start and (L)BFGS searches +> (PyAutoFit#1493 / #1494, `f3767a7`), so arms differing only in clipper +> separate by themselves. The hashes and the reasoning below are left +> intact as the measurement that motivated the change — they were true on +> 2026-08-16 and are false from 2026-08-18. Record: +> `complete/2026/08/clipper-in-search-identifier.md`. + **The clipper does NOT enter the search identifier.** Verified empirically against PyAutoFit `main` on 2026-08-16: @@ -171,9 +179,10 @@ Mitigation — do all three: Not a bug in itself — it is what keeps existing runs' directories stable — but it is precisely wrong for this campaign. Whether the clipper *should* enter the -identifier is now filed as -`draft/feature/autofit/clipper_in_search_identifier.md`, and phase 3's -re-baseline is the reason it matters. +identifier was filed as a follow-up and **decided on 2026-08-18: it does** +(PyAutoFit#1493 / #1494; record +`complete/2026/08/clipper-in-search-identifier.md`). Phase 3's +re-baseline was the reason it mattered. ### Harness traps that already cost a session diff --git a/complete/2026/08/mge-lane-death.md b/complete/2026/08/mge-lane-death.md index 3500273a..adaef839 100644 --- a/complete/2026/08/mge-lane-death.md +++ b/complete/2026/08/mge-lane-death.md @@ -102,8 +102,10 @@ Downstream, all 2026-08-16: `draft/research/autolens_profiling/ell_comps_trapping_unmasked.md`. It runs on top of phase 1 and can share phase 2's arms. - **Clipper phase 3**, which was never drafted. If it is ever written it must argue - hygiene, not accuracy; `draft/feature/autofit/clipper_in_search_identifier.md` is - its prerequisite decision. + hygiene, not accuracy. Its prerequisite decision is now **made** — the clipper + entered the MLE identifiers on 2026-08-18 (record + `complete/2026/08/clipper-in-search-identifier.md`), so phase 3's + re-baseline carries one variable rather than two. ## Unexplained, left open diff --git a/complete/2026/08/prior-support-clipper.md b/complete/2026/08/prior-support-clipper.md index 23365d51..22d56e25 100644 --- a/complete/2026/08/prior-support-clipper.md +++ b/complete/2026/08/prior-support-clipper.md @@ -194,9 +194,11 @@ So **follow-ups 1 and 2 in the list below are now FIXED**; they are left in plac because the reasoning that found them is the record. Their own records are `complete/2026/08/save-json-numpy-scalar-typeerror.md` and `complete/2026/08/crashed-run-poisons-resume.md`. Follow-ups 3, 4 and 5 remain -open and 3 and 4 are now filed as prompts: -`draft/bug/autofit/loggaussian_prior_declares_own_support.md` and -`draft/feature/autofit/clipper_in_search_identifier.md`. +open and 3 and 4 were filed as prompts: +`draft/bug/autofit/loggaussian_prior_declares_own_support.md` and the +identifier question — the latter **shipped 2026-08-18** (PyAutoFit#1493 / +#1494, the clipper now enters the MLE identifiers; record +`complete/2026/08/clipper-in-search-identifier.md`). Two corrections that came out of doing that follow-up work: diff --git a/complete/2026/08/uniform-prior-bounds-numpy-path.md b/complete/2026/08/uniform-prior-bounds-numpy-path.md index fcfccd5a..49536eac 100644 --- a/complete/2026/08/uniform-prior-bounds-numpy-path.md +++ b/complete/2026/08/uniform-prior-bounds-numpy-path.md @@ -36,8 +36,9 @@ overrode the worktree-conflict guard (PyAutoFit was claimed by stored-sample-reconstruction-guard and version-stamp-sync-guards; change surface disjoint from both). - follow-ups (filed/known at completion): - 1. Shape C deliberately deferred: default `LBFGS` to `ClipperPriorBox` (draft - `draft/feature/autofit/clipper_in_search_identifier.md` is adjacent). With strict priors and + 1. Shape C deliberately deferred: default `LBFGS` to `ClipperPriorBox` (the + adjacent identifier question was decided 2026-08-18 — record + `complete/2026/08/clipper-in-search-identifier.md`). With strict priors and `ClipperNone`, an LBFGS step out of the box now sees an infinite objective — C gives scipy declarative bounds instead. 2. Workspace smoke literal risk: `*_workspace_test` scripts that pin literal values from diff --git a/complete/index.md b/complete/index.md index 3ca04e43..f273e99e 100644 --- a/complete/index.md +++ b/complete/index.md @@ -6,7 +6,7 @@ Token-light navigation over the finished-work records (schema: only then grep a dated bucket. Curators: edit the band between the CURATED markers; everything below GENERATED is rebuilt. -1050 records across 7 buckets. +1051 records across 7 buckets. ## Highlights @@ -38,6 +38,7 @@ _(curate hard-won records here — survives regeneration.)_ - [circular-ell-comps-image-gradient](2026/08/circular-ell-comps-image-gradient.md) — Replaced the Sersic image path's Cartesian-to-polar ellipticity conversion with an algebraically equivalent Ca… - [cli-noise-autonerves-batch](2026/08/cli-noise-autonerves-batch.md) - [cli-noise-pyautofit-batch](2026/08/cli-noise-pyautofit-batch.md) +- [clipper-in-search-identifier](2026/08/clipper-in-search-identifier.md) — decided: option 2 — the clipper enters the MLE identifiers - [clipper-usage-in-search-summary](2026/08/clipper-usage-in-search-summary.md) — `search.summary` now reports prior-support clipping. `n_clipped_lane_steps` - [clipper-validation-campaign](2026/08/clipper-validation-campaign.md) - [compile-axis-campaign-coverage](2026/08/compile-axis-campaign-coverage.md) diff --git a/dashboard.html b/dashboard.html index 279fbbb4..cd76f106 100644 --- a/dashboard.html +++ b/dashboard.html @@ -37,11 +37,10 @@
Every task the Mind is holding. Tap a task's 📋 and its /start_dev command is on your clipboard — paste it into a Claude Code chat to route Claude straight to that task.
In flight 2 · Parked 3 · Planned 6 · Backlog 150 · markdown version
+In flight 2 · Parked 3 · Planned 6 · Backlog 149 · markdown version
TRIAGE: needs manual review before routing — medium · safe · high
Decide whether the clipper belongs in the search identifier — autofit · medium · human-required · high
The ell_comps trapping was masked, not cleared — characterise it — autolens_profiling · medium · supervised · high
Analytic Gaussian benchmark: closed-form validation of graphical + EP — graphical_ep · medium · supervised · high
Cluster package: point-source-default narrative + extended-source follow-up feature — workspaces · medium · supervised · high
Profile and speed up JAX likelihood-function compile times (all use — autolens_profiling · large · supervised · high
Give the Profiling Agent a compile-time axis — the arc — profiling · large · supervised · high
Cluster source_science.py: robust magnification science at cluster scale (no meshes — workspaces · large · supervised · high
Deep research: Can we speed up Delaunay in PyAutoArray? — autoarray · too-large · supervised · high
add-vincken-2026-wiki-and-cite-in-euclid — workspaces · small · safe · normal
add_notebook_quotes mistakes a code string literal's closing delimiter for a — hands · small · safe · low
latent-nan-guard-honest-run
150 filed prompts, not started — sorted most-pickable first (priority, then size).
+149 filed prompts, not started — sorted most-pickable first (priority, then size).
EP hierarchical parent-scale collapse: cure the basin, or document the — autofit · too-large · human-required · high
Decide whether the clipper belongs in the search identifier — autofit · medium · human-required · high
Area magnification (LEGGOS-style): per-pixel inversion sum as primary; ShapeSolver rehabilitate-or-retire — autolens · large · supervised · high
Magnification maps: image-plane contour maps, source-plane mesh maps, uncertainty maps — autolens · large · supervised · high
Magnification at a point: surface the existing API in source_science — autolens · large · supervised · high
ell_comps trapping was masked, not cleared — characterise it — autolens_profiling · medium · supervised · high