Skip to content

feature: env-profile migration steps 4-8 — derive the release JAX set #181

Description

@Jammy2211

Overview

Finish the env-profile migration (build-chain campaign Phase 3, umbrella #155, research + design on #161; design doc docs/env_profile_redesign.md, merged #162; steps 1–3 shipped: validator #163, single resolver, scrubbed baseline env #165). In the ag/al release profiles JAX is re-enabled by a hand-enumerated list of ~10 path patterns — a list that silently rots: 15 scripts literally named for JAX (jax_assertions/, hessian_jax.py, tracer_jax.py, …) run NumPy in every nightly while asserting JAX behaviour. Step 4 replaces enumeration with the design's derivation rule (JAX-on in release mode iff a path segment/stem matches jax_* / *_jax / *_jit); steps 5–8 make the validator binding, rename the profiles, fold the duplicate library env read, and (human-gated) decide the use_jax sentinel.

This issue covers steps 4–8, executed in phases. This session: step 4 only.

Plan

Step 4 — the derivation rule (this session, three PRs, PyAutoHands first):

  • Phase A (PyAutoHands): move is_jax_marked from the validator into autohands/env_config.py (one owner of the rule); resolver applies the derivation when a profile sets a new derive_jax_markers: true top-level key (explicit key, not a filename heuristic); validator accepts the key, resolves through the shared logic, and skips non-runnable __init__.py files; unit tests. Old profiles + new resolver = unchanged behaviour, so this merges first.
  • Phase B (autolens_workspace_test): rename the two mid-stem files imaging/modeling_visualization_jit_delaunay.pymodeling_visualization_delaunay_jit.py (and _rectangular) so the _jit suffix carries the marker (human decision 2026-07-23); triage database/scrape/ on the NumPy path first — only fall back to a scrape_jax/ folder rename if JAX is genuinely required (human decision 2026-07-23; no exception list, ever); delete all 10 PYAUTO_DISABLE_JAX overrides from env_vars_release.yaml and add the key; triage the 10 runnable turns-ON scripts individually under their exact new JAX-on env (none has ever run JAX in the nightly).
  • Phase C (autogalaxy_workspace_test): same minus renames/scrape — delete 8 overrides, add the key, triage the 3 runnable jax_assertions/ scripts.
  • Verification (binding): resolved-env diff via resolve_clean() over every script, old profile vs new profile+rule, read line by line — must be exactly the triaged set and nothing else. Never smoke (smoke cannot see config-only changes). Prove against the real CI env (workspace-validation.yml), not a synthetic clean shell. The release path is only exercised by the nightly mega-run — mega-run verification accepted and stated on each PR.

Later phases (same issue):

  • Step 5 — flip --strict-derivation / --strict-markers to errors; wire the validator into the three *_workspace_test PR gates.
  • Step 6 — rename env_vars.yamlprofile_smoke.yaml, env_vars_release.yamlprofile_release.yaml (last; re-sweep for old names after every merge from main).
  • Step 7 — fold al.AnalysisDataset's duplicate PYAUTO_DISABLE_JAX read into base af.Analysis (own plan; PyAutoFit + PyAutoLens, library-first; currently queued behind the testmode-env-drift PyAutoFit claim).
  • Step 8 — use_jax: Optional[bool] = None sentinel: human decision required before any code; may be declined.
Detailed implementation plan

Affected Repositories

Branch Survey (2026-07-23)

Repository Current Branch Dirty?
./PyAutoHands main clean
./autolens_workspace_test main clean
./autogalaxy_workspace_test main clean

No worktree conflicts (worktree_check_conflict clean for all three).

Suggested branch: feature/env-profile-derivation
Worktree root: ~/Code/PyAutoLabs-wt/env-profile-derivation/
Work classification: Both (PyAutoHands source first, workspace_test follow-ups)

Measured baseline (validator, 2026-07-23 — live re-measurement, not the stale 07-17 counts)

repo override-enumeration JAX-marked but resolves NumPy unmarked but resolves JAX
autolens_workspace_test 10 11 9
autogalaxy_workspace_test 8 4 0
autofit_workspace_test 0 0 0

Nuances found in planning: imaging/modeling_visualization_jit_delaunay.py / _rectangular.py are already in no_run.yaml (SLOW, 2026-05-07) — no immediate nightly impact, but post-migration the strict validator would see them as consistent NumPy scripts while their content asserts jax.Array, hence the rename. 4 of 7 database/scrape/ files are also no_run'd; only general.py + scaling_relation.py actually run. The 15 turns-ON scripts are not no_run'd — they genuinely run tonight on NumPy, so flipping them is a real nightly change and each is triaged individually.

Implementation Steps (Phase A)

  1. autohands/env_config.py — move in is_jax_marked (from validate_env_profiles.py:46); in build_env_for_script, when env_config.get("derive_jax_markers") is truthy, after defaults+overrides: if is_jax_marked(file): env["PYAUTO_DISABLE_JAX"] = "0".
  2. autohands/validate_env_profiles.py — import is_jax_marked from env_config (delete local copy); add derive_jax_markers to ALLOWED_TOP_KEYS; resolve_clean applies the same derivation via shared logic; skip __init__.py when collecting scripts.
  3. Unit tests (rule, derivation on/off, validator key, __init__.py skip).

Implementation Steps (Phase B — al)

  1. git mv the two renames; update config/build/no_run.yaml:26-27; sweep for other references.
  2. Scrape triage: run database/scrape/general.py + scaling_relation.py under the resolved NumPy release env. Pass → drop override, state on PR. Fail (JAX-rooted) → git mv scripts/database/scrape → scrape_jax + sweep ~8 refs (smoke profile ×2, no_run ×4, release profile, AGENTS.md, folder CLAUDE.md).
  3. config/build/env_vars_release.yaml — delete all PYAUTO_DISABLE_JAX overrides; add derive_jax_markers: true; rewrite header comment.
  4. Turns-ON triage (each under its exact new resolved env): jax_assertions/ ×7, hessian_jax.py, profiles_jit.py, tracer_jax.py. Fail → trivial fix-forward, else no_run.yaml with dated reason, surfaced on the PR.

Implementation Steps (Phase C — ag)

As B steps 3–4: delete 8 overrides, add key, triage jax_assertions/ ×3.

Key Files

  • PyAutoHands/autohands/env_config.py — the single resolver; gains the rule + derivation
  • PyAutoHands/autohands/validate_env_profiles.py — PR-time gate; resolve_clean() is the verification harness
  • autolens_workspace_test/config/build/env_vars_release.yaml — 10 overrides to delete
  • autogalaxy_workspace_test/config/build/env_vars_release.yaml — 8 overrides to delete
  • PyAutoHeart/.github/workflows/workspace-validation.yml:295,322 — how the nightly invokes the release profile (read-only; parity check)
  • PyAutoHands/docs/env_profile_redesign.md — the decided design (§2 policy, §3 rule)

Config traps (from prior sweeps)

overrides: order is load-bearing (later entries win); an empty overrides: block loads as None, not []; verify config-only changes by resolved-env diff, never smoke.

Planning notes

Brain Feature Agent scored this too-large (score 25, repo-count proxy) and proposed a generic 4-phase split — overridden with the prompt's own step phasing (step 4 alone; 5+6 pair; 7 independent; 8 decision-first), recorded here per standing guidance.

Original Prompt

Click to expand starting prompt

Finish the env-profile migration: steps 4-8 (derivation, strict gate, rename, one-reader, sentinel)

Type: feature
Target: workspaces
Repos:

  • autolens_workspace_test
  • autogalaxy_workspace_test
  • autofit_workspace_test
  • PyAutoHands
  • PyAutoFit
  • PyAutoLens
    Difficulty: large
    Autonomy: supervised
    Priority: normal
    Status: formalised

Build-chain campaign Phase 3, the last remaining work from the umbrella closed
2026-07-22 (epic PyAutoHands#155). Tracked on PyAutoHands#161. Design
merged: PyAutoHands/docs/env_profile_redesign.md (#162). Steps 1-3 done:
validator (#163), single resolver, scrubbed baseline env (#165 + profiles).

What this is about

Each *_workspace_test repo has two env profiles under config/build/:
env_vars.yaml (smoke — the per-PR gate) and env_vars_release.yaml
(release — the nightly mega-run). PyAutoHands/autohands/env_config.py
resolves them: a defaults: block applied to every script, then an ordered
overrides: list of path patterns that flip vars for specific scripts.

In the ag/al release profiles PYAUTO_DISABLE_JAX: "1" is the default and
JAX is re-enabled by a hand-enumerated list of ~10 folder patterns. That
enumeration is the failure mode the whole campaign exists to remove: a
hand-maintained list of paths silently rots as scripts are added or renamed,
and the failure is quiet — a jax_grad/ script that falls off the list runs
NumPy and asserts a gradient that is all zeros, or tests nothing at all.

The decided policy (design doc §2, human 2026-07-16) is NOT "release =
production defaults" — that was rejected on measured JIT compile cost
(autolens_profiling#71). mode=release validates every script on the NumPy
path plus a derived, loud, bounded JAX set. Step 4 is what makes that set
derived instead of enumerated.

Measure first — the 2026-07-17 numbers are stale

Re-run the validator before planning; do not carry old counts into the work.
Measured 2026-07-22 (after the autobuildautohands rename and the
dead-entry purge, both of which moved these numbers):

python3 PyAutoHands/autohands/validate_env_profiles.py <workspace_test_root>
repo override-enumeration JAX-marked but resolves NumPy unmarked but resolves JAX
autolens_workspace_test 10 11 9
autogalaxy_workspace_test 8 4 0
autofit_workspace_test 0 0 0 (clean — DISABLE_JAX "0", no overrides, the #47 shape)

The 3 dead patterns the old brief mentioned (ag quantity/) are gone
purged by the no_run/env_vars dead-entry campaign. autofit_workspace_test
needs no work at all; this task is ag + al.

Step 4 — the derivation rule (do this first; RELEASE-SURFACE RISK)

Replace the enumerated PYAUTO_DISABLE_JAX overrides with one rule in
autohands/env_config.py: JAX-on in mode=release iff a path segment matches
jax_* / *_jax / *_jit.

This is not a config tidy — it changes which backend scripts run on. Every
script below either starts or stops using JAX, and none of the "starts" has
ever run under JAX. Triage each individually: does it pass under JAX, and is it
fast enough for the nightly? Three groups, from the measured lists:

  1. Turns ON (marked, currently NumPy — 15 scripts). al: jax_assertions/
    (8 files, incl. __init__.py), hessian_jax.py, profiles_jit.py,
    tracer_jax.py. ag: jax_assertions/ (4 files). Highest value — files
    literally named for JAX that have never been JAX-validated — and the
    highest chance of a red nightly.
  2. database/scrape/ (7 files, al). The one place the rule does not match:
    these need JAX on but carry no marker. Decide rename vs one
    documented parity entry
    . The design says no exception list, so a rename is
    the honest option, but it touches paths that no_run.yaml,
    copy_files.yaml and docs may reference — sweep for references first.
  3. Turns OFF — a regression risk the old brief missed.
    imaging/modeling_visualization_jit_delaunay.py and
    ..._jit_rectangular.py resolve JAX-on today only because the override
    pattern imaging/modeling_visualization_jit substring-matches them. Under
    the derivation rule their stems end _delaunay / _rectangular, so they
    would stop getting JAX and silently no-op (the very failure the rule
    exists to prevent). Resolve this before writing the rule — it is
    evidence the *_jit suffix match is the wrong shape, and a
    segment-contains form may be what the design actually needs.

Also decide what the rule does with __init__.py (3 of the counted files are
package inits, not runnable scripts) — probably skip them in the validator
rather than special-case the rule.

Verification (binding). Verify by resolved-env diff, never by smoke:
run resolve_clean() (autohands/validate_env_profiles.py:56) over every
script, old profile vs new rule, and read the diff line by line — it must be
exactly the triaged set above and nothing else. Traps from prior config
sweeps: overrides: order is load-bearing (later entries win; defaults:
order is not), and an empty overrides: block loads as None, not []. And
per the step-3 near-miss: prove it against the real CI env, not a synthetic
clean one — a synthetic run passed 65/65 while the real env would have gone
red, because the smoke profiles relied on a PYAUTO_ var injected by the
workflow rather than by the profile.

The release path is exercised only by the mega-run, not the per-PR gate. Land
behind a rehearsal, or accept mega-run verification and say so on the PR.

Step 5 — make the validator binding

Flip --strict-derivation / --strict-markers from warnings to errors and
wire validate_env_profiles into the three *_workspace_test PR gates.
Depends on step 4 (today it would fail on 42 warnings across ag+al).
Acceptance: a PR adding a JAX-marked script outside the derived set fails its
own gate.

Step 6 — rename the profiles

env_vars.yamlprofile_smoke.yaml, env_vars_release.yaml
profile_release.yaml. Update autohands/env_config.py discovery,
run_python.py, run_smoke.py, workspace-validation.yml, docs. Wide but
mechanical; do it last, and re-sweep for the old names after every merge
from main — git rename-detection is blind to new files using the old name,
which is how the autobuildautohands rename leaked.

Step 7 — library one-reader fold

al.AnalysisDataset reads PYAUTO_DISABLE_JAX a second time, before
super().__init__() (design doc failure mode 8). Fold it into base
af.Analysis so there is exactly one reader. Own plan; PyAutoFit + PyAutoLens,
so library-first then workspace. Behaviour-preserving — prove it with a unit
test asserting both construction orders resolve identically.

Step 8 — use_jax: Optional[bool] = None sentinel (HUMAN-GATED)

Only if the human wants "explicitly requested" distinguishable from
"defaulted". Cross-repo API change; may be declined — ask before building.

Suggested phasing

Step 4 is a session on its own (the triage is the work). Steps 5+6 can pair.
Step 7 is independent of 4-6 and could go first if a session is short. Step 8
needs a decision before any code.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions