All notable changes to Atlas.WM are documented here. The format follows Keep a Changelog and the project adheres to Semantic Versioning.
-
Package metadata and import-time version are aligned at
4.0.1; the lock-derived SBOM and reproducible wheel/sdist staging now carry the same version. -
CI and scheduled canaries use verified action SHAs, locked uv environments, and deterministic checkout gates.
-
The release staging path builds with hash-constrained setuptools/wheel dependencies, emits SHA256 checksums, and excludes datasets, checkpoints, papers, host paths, and environment-specific files from artifacts. It now uses a fresh source allowlist, exact archive manifests, safe extraction, cross-platform canonical archive metadata, and a clean tracked-HEAD gate.
-
The manual release-artifacts workflow builds on Linux and macOS and compares the resulting artifact hashes before any release publication step; pull requests touching release inputs run the same matrix automatically.
- COAST/PUSH active policy for MuJoCo (
CoastPushPolicy+ no-op action 8 inMujocoPointMass): random-policy MuJoCo data identifies nothing (R² ≈ 0, 332 eps) - friction needs free sliding, mass needs contacts; the policy manufactures both events. atlas_wm.data.mujoco_features(16 dims): box-coast deceleration → μ·g (Coulomb decelerates linearly, not exponentially) and ALIGNED-contact impulse response → mass (head-on contacts only, cos > 0.8 - the raw speed-ratio confounded geometry with mass at R² −0.27); running medians per the B10 pattern. Belief trainer is env-aware viaobs_scale.json.- Result (1048 episodes, window 60): learned belief friction +0.28, mass +0.11 - both positive on real contact physics; structural finding: gravity alone is unidentifiable here (only μ·g enters box dynamics) and is excluded BY STRUCTURE, with the measurement to back it. Measured iteration ledger in the part-1/2/3 commit messages, including the agent-rides-frictionless-slide-joints discovery.
4.0.0 - 2026-07-04
The correction-and-retraction rebuild: every v3.x headline claim was
re-verified, two were retracted, and the training recipe, data pipeline,
evaluation methodology and environment tier were rebuilt with honest,
reproducible measurements (18-block roadmap in docs/v4.0-ROADMAP.md,
executed as PRs #25–#43).
- The Block-14 identifiability finding.
friction_agentIS identifiable under random-policy data: a robust median-of-ratios oracle (scripts/oracle_friction_agent.py, now committed) scores R² = 0.85, MAE = 0.004 over 400 episodes on the corrected environment. The original "R² < 0" oracle was never committed and its MSE objective is destroyed by bounce outliers. The weak gravity/friction_box ceiling (0.15–0.45 R²) measured the box-containment bug fixed in B1. See the retraction notice indocs/MODEL_CARD.md; full re-baseline lands with B5.
load_checkpoint(require_signature=True)(finding H4): missing manifest, unset/malformed/shortATLAS_SIGNING_KEY, a file not listed in the manifest, or any manifest integrity failure now raiseSignatureMismatchinstead of warning - an attacker who can replace a checkpoint could previously just delete the manifest. Legacy fail-open behavior unchanged when the flag is off; 4 new tests. The torch.export/ dynamo ONNX switch and OpenSSF model-signing are deferred to v4.1 (exporter swap at release time adds risk; the current exporter is parity-tested).
uv.lockis the canonical lockfile (cross-platform, hashed;uv sync --lockedin CI andmake install).requirements.lockis retained for one release as a fallback and for the pip-audit gate. Trackio/tyro adoption (roadmap B16 stretch) deferred: swapping config and tracking layers mid-release adds risk without changing any result; tracked for v4.1.
EntityEncodermoved toarchive/: it was dead code - never wired to any environment ("n_objects ∈ [3, 10]" was never exercised) and permutation-invariant (mean-pool), not equivariant as documented. Real multi-object support belongs to the v5 line on the MuJoCo tier (slot/particle direction per the roadmap), not to a vestigial module whose claims the code never backed.
MujocoPointMass(--env mujoco): actuated ball + two passive boxes, real MuJoCo contact/friction, per-episode randomized friction/mass/gravity ininfo, same interface contract as CruelGridworld (8 discrete force directions, 6-D obs, seeded) - the whole v4 pipeline runs unchanged.- Per-dataset observation scale (
obs_scale.jsonwritten by generate_data, copied by split_data, resolved by the datasets): MuJoCo observations live in [−1.2, 1.2], not [0, 20]. - First MuJoCo baseline (VICReg + grounding + rollout K=4): h=1 MSE 0.001089 vs linear ceiling 0.000465 - a 2.3× gap vs 3.6× on the gridworld, consistent with the process-noise analysis; AD-2 passthrough drift exactly 0.0 over 8-step rollouts.
model.dynamics_head: hamiltonian: z_dynamic splits into (q, p); dissipative symplectic-Euler step with learned force field and per-dim damping conditioned on z_static_slow (friction analog; contraction tested). Honest A/B verdict: relative OOD robustness is real (gravity 8–12, outside train range: +12% degradation vs the residual head's +69% at h=1) but absolute error is worse everywhere (3.5× in distribution). Ships as a non-default option; echoes the literature that gains come from second-order structure, not symplecticity per se - revisit with tuned capacity on MuJoCo (B14+).
scripts/precompute_belief.pyruns the trained belief GRU causally per episode (PhysicsBeliefEncoder.forward_sequence, prefix-property tested) and stores per-row beliefs;training.use_beliefsubstitutes them for the encoder'sz_static_slowin both the start latent and the rollout targets - the dynamics' slow residual learns to track the belief. The RMA phase-2 integration the v3.x design promised.- A/B on active data (identical seeds): h=1 next-frame MSE 0.001080 → 0.001017 (−5.8%); h=4 0.011232 → 0.011059 (−1.5%). Honest caveat: the process-noise random walk dominates long-horizon error on this env, capping what known physics can buy; the conditioning's value should grow in lower-noise / richer settings (B14+).
InfoSeekingPolicy+generate_data.py --policy active: rosette-dash MEASURE phase (2× per-step ratio precision) alternating with box-STIR (gravity signal). Same belief model/training, active vs random data: mean val R² 0.246 → 0.341 (+39%); gravity 0.43 → 0.67; friction_agent 0.22 → 0.29. Episode-level estimator: R² 0.956 / MAE 0.0036 (active) vs 0.914 / 0.0054 (random).- Key negative finding documented: a fixed-line dash repeatedly struck unobservable obstacles and poisoned episode medians (heavy-tail R² collapse) - deterministic info-seeking needs anti-fragility to unobservables; the golden-angle rosette provides it. Regression test locks the information advantage (mean estimator error < 75% of random).
- Engineered dynamics features (
atlas_wm.data.dynamics_features, 27 dims: gated decay ratio + running median - the oracle's sufficient statistic - excitation, distances, box-acceleration projections, 1/d², aligned actions) replace raw obs windows as the belief GRU's input; heteroscedastic physics head (μ, logσ, Gaussian NLL) and half-window InfoNCE episode contrastive added;window_kdefault 40 (evidence-length analysis in MODEL_CARD). - First positive learned physics identification: supervised val R² gravity +0.43, friction_agent +0.22, friction_box +0.09 (raw-GRU baseline: −0.05 / −0.49 / −0.10). Remaining gap to the 0.865 oracle is evidence length (SNR < 1 at short windows), not modeling - the direct setup for B11 active exploration.
- Adversarial critic retired from training (finding C4: with
random-policy data, I(z_imm; action) = 0 for any encoder - the game was
an arms race around noise). Module kept with a deprecation note; action
routing is architectural (actions only enter
control_net). - Immutable anchor - the intervention loss v3.x promised and never shipped (C3): within-episode invariance + cross-episode VICReg on z_imm episode means. Mechanism regression-tested with a 10× variance-floor test on identity-observable synthetic data. On CruelGridworld a single input carries no episode identity (ratio ≈3 with or without; ~25% h=1 cost), so the anchor ships disabled by default here - to be enabled with B12 belief integration / B14+ richer envs. A control run confirms the refactor itself is clean (h=1: 0.001165 vs B8's 0.001157).
training.rollout_k(default 4): K-step self-fed rollout training - predicted latents feed the next dynamics step with per-step latent supervision + grounding; windows come fromEpisodeATLASDataset. Validation tracks h=1 and h=K separately.scripts/evaluate.pyis now a real evaluator (finding M5): open-loop obs-space MSE by horizon and the AD-2 immutable-passthrough drift check (measured: exactly 0.0 over 10-step rollouts).- Result: the classic horizon trade-off - one-step model wins at h=1 (0.000993 vs 0.001157), rollout-trained wins from h≥3 (h=10: 0.0357 vs 0.0389). Process noise sets an irreducible random-walk floor at long horizons.
training.objective:vicreg(default; variance hinge + covariance penalty on the encoder output - the tensor that can collapse) orema(EMA-target encoder, BYOL/TD-MPC2 lineage) replace the v3.x recipe;legacyretained for comparison.lambda_latent_l2and the mis-placed variance penalty are retired.- Prediction grounding (
lambda_next_recon): the loss now optimizesdecoder(dynamics(z))against the actual next observation - the inference path was previously never trained directly. - Objective-agnostic model selection: checkpoints and early stopping now use observation-space next-frame MSE.
- Result on the noisy re-baseline data (2 frames): 0.001750 → 0.000996 (43% ↓); frame stacking now helps (it hurt under the legacy recipe); the remaining 3.6× gap to the linear ceiling is tracked for B8+.
frame_stack: 2is the new default model input:ATLASDatasetconcatenates the previous same-episode frame (velocity becomes observable, finding M2);train.pyplumbs it through encoder/decoder widths and checkpoint metadata;probe_physics.pyfeeds the encoder the same view it was trained on. Honest acceptance result: a linear ridge improves 3.1× with stacked input (0.000856 → 0.000274 next-frame MSE) while the trained world model under the current v3-era loss gets worse (0.000921 → 0.001750) - the recipe, not the information, is the bottleneck; the comparison is re-run after B7 replaces the objective.
- New identifiability baseline on the corrected environment with
episode-grouped probe splits (see the re-baseline section in
docs/MODEL_CARD.md): the closed-form oracle recoversfriction_agentwith R² = 0.865 (MAE 0.006) from the same noisy (σ = 0.05) dataset where the raw-sequence GRU belief encoder scores negative R² on all three parameters - the bottleneck is the training recipe, not the data.friction_agentis back in the target set everywhere (PHYSICS_KEYS, probe defaults, configs). - Probe splits are now grouped by episode (
latent_probe._split_indices): sequential row splits leaked physics labels across overlapping windows and inflated v3.x probe R².
- Environment (B1): boxes now collide with walls and obstacles like the agent; previously they exited the grid (range [−62, +77] on [0, 20]), violating the observation space and erasing the gravity signal after a few steps. 100-seed containment regression test added.
- Data pipeline (B2): observation normalization moved in memory into the
datasets (split files on disk are never modified; legacy in-place-normalized
dirs are rejected); the
.splitsentinel stores a SHA-256 of the raw arrays so regenerated data triggers an automatic re-split; episode-windowing off-by-one fixed (cumsum[i-k]→cumsum[i-k+1]). - Reproducibility (B3): both trainers seed python/numpy/torch and their
DataLoaders (
--seed/training.seed); a real training canary asserts bit-identical loss traces;d_static_immutable/d_static_sloware actually plumbed into the models and inferred at ONNX export (previously the split silently stayed atd_static // 2); critic weights are checkpointed. - CI (B4):
ci.ymlre-enabled;chaos-physics.ymlnow runs a real, committedscripts/chaos_physics.py(containment, finiteness, determinism, dissipation over randomized episodes);train-canary.ymlpoints at tests that exist;make securityno longer flags the migration script.
- Main-encoder divergence (root cause): added a latent-magnitude penalty
lambda_latent_l2(default 0.01) on‖z_full‖. The self-predictivepred_loss(target = the encoder's own detached output overnext_obs) has a degenerate direction: the encoder can inflate its representation scale without bound while the dynamics tracks it, so after a few stable epochs the loss explodes ~10×/epoch. Reconstruction alone does not anchor it at the full 50k scale. The L2 penalty removes the runaway direction - validated end-to-end onscripts/train.py(stable, monotonic convergence). NOTE: an earlier hypothesis blamed the adversarial loss; that was disproven - disabling the adversarial term entirely still diverged identically. The divergence merely coincided with the warmup boundary. - Adversarial loss hardening (defense-in-depth, not the divergence cause):
bounded
encoder_adversarial_loss; its-mse(pred, action)objective was unbounded below. The fooling reward is capped at 0.5. - Data pipeline:
split_data.pynow splits by shuffled episode ID (not transition index) to prevent physics distribution shift between train/val/test. (Its.normalized-sentinel handling was later superseded by the B2 in-memory normalization above - the sentinel is now a rejected legacy marker.)
Physics identification scope (Block 14)[SUPERSEDED - see Retracted (v4 B4) above.] This entry claimedfriction_agentis not identifiable and rescoped the targets to{gravity, friction_box}; the claim was retracted and the full target set restored in B5. Retained only for the still-valid data recipe: the belief pipeline generates ~50-step episodes (--episode-reset-prob 0.02) withwindow_k=20.
3.1.0 - 2026-06-24
- PhysicsBeliefEncoder (Block 14): GRU-based encoder over K consecutive
same-episode observations →
z_static_slow. Follows the RMA/VariBAD pattern: accumulates temporal evidence to identify episode-level physical constants (gravity, friction) that are invisible from a single position-only snapshot. - PhysicsHead: supervised auxiliary linear head
z_static_slow → physics_hatfor training the belief encoder with ground-truth physics labels. - EpisodeATLASDataset: windowed dataset returning K-step same-episode windows
using a vectorized cumsum boundary-detection algorithm; requires
episode_ids.npyproduced bygenerate_data.py --randomize-physics. scripts/train_physics_belief.py: training script for the GRU belief encoder; saves checkpoint as.safetensors(AD-4) with full metadata.scripts/probe_physics.pyextended:--belief-checkpointflag runs the PhysicsBeliefEncoder probe alongside the single-step baseline.
export_onnx.py: inferinput_dim,d_static,d_dynamic,d_controllable, andaction_dimfrom state dict weight shapes - no longer hardcoded; prevents shape mismatch on non-default checkpoints.probe_physics.py:gru_input_dimfallback now computesobs_dim + action_dimwhen the key is absent from checkpoint metadata (previously defaulted toobs_dimonly, causing GRU shape errors for obs+action checkpoints).- mypy
no-any-returnerrors inphysics_belief.pyandepisode_dataset.py.
3.0.0 - 2026-06-17
The v3.0 line rebuilds Atlas.WM as an installable, reproducible, security-hardened package around the hybrid static latent decomposition (AD-2) and a set of locked architectural decisions (AD-1 … AD-8). Delivered as 13 sequential blocks.
- Hybrid static decomposition (Block 5, AD-2): the latent splits into
z_static_immutable(hard architectural passthrough - bit-identical across time),z_static_slow(soft residual with a drift penalty),z_dynamic(autonomous evolution), andz_controllable(action-conditioned). - Identifiability (Block 6, AD-3): an
ActionInvarianceCritictrained adversarially to keep action information out ofz_static_immutable, plus an intervention loss and content-addressed environment hashing (AD-6). - EntityEncoder (Block 10): permutation-equivariant, mean-pooled encoder supporting a variable number of objects.
- Partial observability (Block 11): nearest-K object masking wrapper.
- Variable physics & process noise (Block 12): per-episode domain
randomization of gravity/friction and seeded Gaussian process noise in
CruelGridworld, with a closed-form ridge latent probe (atlas_wm.eval.latent_probe) that validates physics is decodable fromz_static_slowand not from the immutable passthrough. - ONNX export (Block 13):
atlas_wm.export.onnx_exportandscripts/export_onnx.pyemit composableencoder.onnx(obs → z_full) anddynamics.onnx(z_full, action → z_full_next) graphs with dynamic batch axes. Available via the optionalexportextra. - Model card (
docs/MODEL_CARD.md) and this changelog (Block 13). - Test suite grown to 100+ tests across unit, integration, physics contract, security, latent-probe, and ONNX-parity categories.
- Packaging (Block 1): code restructured into the installable
src/atlas_wm/package; legacy v2.0 artifacts quarantined underarchive/. - Reproducible CI (Blocks 2 & 13): GitHub Actions installs the fully
hash-pinned toolchain from
requirements.lock(AD-8) instead of unpinned latest tools, with pip caching; the Test job is a hard gate. - Checkpoint I/O (Block 3, AD-4): all reads/writes go through
safetensors; pickle-based formats are rejected.
- HMAC-SHA256 checkpoint signing (Block 4):
manifest.sigintegrity verification keyed byATLAS_SIGNING_KEY. - Pickle /
torch.save/torch.loadforbidden in production code paths, enforced by a CI tripwire (the migration script is the sole exception). - SBOM (
sbom.json, CycloneDX) andpip-auditover the lockfile in CI.
- Determinism canary & rollout-drift integration tests (Block 9, AD-7); physics contract tests with a chaos tripwire (Block 8). Default environment behavior is byte-identical across the Block 12 changes (variable physics and noise consume no extra RNG draws unless explicitly enabled).
- The optional
exportdependencies (onnx,onnxruntime) are intentionally not inrequirements.lock; the ONNX parity tests skip when they are absent, keeping the pinned CI toolchain lean. Regenerate the lock withuv pip compile pyproject.toml --extra dev --extra export --generate-hashesif you want them pinned.
2.0.0 - 2025
Continuous-physics world model with structured latents on CruelGridworld.
See docs/v2.0-COMPLETION-REPORT.md and docs/v2.0-TECHNICAL-POSTMORTEM.md.