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 @@

📋 PyAutoMind task dashboard

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

Start here

-

Highest priority (filed as high) — showing 12 of 32

+

Highest priority (filed as high) — showing 12 of 31

TRIAGE: needs manual review before routingmedium · safe · high

-

Decide whether the clipper belongs in the search identifierautofit · medium · human-required · high

The ell_comps trapping was masked, not cleared — characterise itautolens_profiling · medium · supervised · high

Analytic Gaussian benchmark: closed-form validation of graphical + EPgraphical_ep · medium · supervised · high

Cluster package: point-source-default narrative + extended-source follow-up featureworkspaces · medium · supervised · high

@@ -52,6 +51,7 @@

Highest priority (filed as high) — showing 12 of 32

Profile and speed up JAX likelihood-function compile times (all useautolens_profiling · large · supervised · high

Give the Profiling Agent a compile-time axis — the arcprofiling · large · supervised · high

+

Deep research: Can we speed up Delaunay in PyAutoArray?autoarray · too-large · supervised · high

Quick wins (small enough, and safe enough to run unattended)

add-vincken-2026-wiki-and-cite-in-euclidworkspaces · small · safe · normal

@@ -81,7 +81,7 @@

Planned

latent-nan-guard-honest-run

Backlog

-

150 filed prompts, not started — sorted most-pickable first (priority, then size).

+

149 filed prompts, not started — sorted most-pickable first (priority, then size).

bug — 36

EP hierarchical parent-scale collapse: cure the basin, or document theautofit · too-large · human-required · high

@@ -122,8 +122,7 @@

Backlog

-feature — 34 -

Decide whether the clipper belongs in the search identifierautofit · medium · human-required · high

+feature — 33

Magnification at a point: surface the existing API in source_scienceautolens · large · supervised · high

diff --git a/dashboard.md b/dashboard.md index 7073b939..b456ecbb 100644 --- a/dashboard.md +++ b/dashboard.md @@ -11,11 +11,11 @@ Every task the Mind is holding, on one page: what is in flight, what is parked, | [In flight](#in-flight) (`active/`) | 2 | | [Parked](#parked) (`parked.md`) | 3 | | [Planned](#planned) (`planned.md`) | 6 | -| [Backlog](#backlog) (`draft/`) | 150 | +| [Backlog](#backlog) (`draft/`) | 149 | ## Start here -**Highest priority** (filed as `high`) — showing 12 of 32 +**Highest priority** (filed as `high`) — showing 12 of 31
📋 TRIAGE: needs manual review before routing — medium · safe · high @@ -25,14 +25,6 @@ Every task the Mind is holding, on one page: what is in flight, what is parked,
-
📋 Decide whether the clipper belongs in the search identifier — autofit · medium · human-required · high - -``` -/start_dev draft/feature/autofit/clipper_in_search_identifier.md -``` - -
-
📋 The ell_comps trapping was masked, not cleared — characterise it — autolens_profiling · medium · supervised · high ``` @@ -113,6 +105,14 @@ Every task the Mind is holding, on one page: what is in flight, what is parked,
+
📋 Deep research: Can we speed up Delaunay in PyAutoArray? — autoarray · too-large · supervised · high + +``` +/start_dev draft/research/autoarray/delaunay_research.md +``` + +
+ **Quick wins** (small enough, and safe enough to run unattended)
📋 add-vincken-2026-wiki-and-cite-in-euclid — workspaces · small · safe · normal @@ -275,7 +275,7 @@ Scoped but not started; some are not yet prompt files. Full detail in [`planned. ## Backlog -**150** filed prompts, not started. Each section is sorted most-pickable first (priority, then size). +**149** filed prompts, not started. Each section is sorted most-pickable first (priority, then size).
bug — 36 @@ -571,15 +571,7 @@ Scoped but not started; some are not yet prompt files. Full detail in [`planned.
-feature — 34 - -
📋 Decide whether the clipper belongs in the search identifier — autofit · medium · human-required · high - -``` -/start_dev draft/feature/autofit/clipper_in_search_identifier.md -``` - -
+feature — 33
📋 Area magnification (LEGGOS-style): per-pixel inversion sum as primary; ShapeSolver rehabilitate-or-retire — autolens · large · supervised · high diff --git a/draft/bug/autofit/loggaussian_prior_declares_own_support.md b/draft/bug/autofit/loggaussian_prior_declares_own_support.md index 1f307d67..47d38d28 100644 --- a/draft/bug/autofit/loggaussian_prior_declares_own_support.md +++ b/draft/bug/autofit/loggaussian_prior_declares_own_support.md @@ -54,8 +54,9 @@ Changing what a prior reports as its support is not local: it has gone too far. - **Check the identifier.** If `lower_limit` feeds the search identifier, a change re-keys existing output directories and orphans stored results — the - same class of concern as - `draft/feature/autofit/clipper_in_search_identifier.md`. + same class of concern as the clipper identifier decision, which chose to + re-key and orphan rather than special-case (2026-08-18; record + `complete/2026/08/clipper-in-search-identifier.md`). ## Verify diff --git a/draft/feature/autofit/clipper_in_search_identifier.md b/draft/feature/autofit/clipper_in_search_identifier.md deleted file mode 100644 index 6dd59172..00000000 --- a/draft/feature/autofit/clipper_in_search_identifier.md +++ /dev/null @@ -1,92 +0,0 @@ -# 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.