Kahlus v2/v3/EM Sprint Ledger#
Canonical, durable record of the v2/v3/EM synthetic-falsification program. Read this first when
picking up a fresh branch — it is the context capsule that survives branch deletion. Lives on
main, kept current as sprints land.
Locked wording. A sprint “falsifier passed” means a synthetic benchmark survived honest tests on synthetic data. It does NOT mean Kahlus v2/v3 is validated on real brain data, nor any clinical/control/consciousness claim. “v2/v3 deserves life” = the synthetic branch earns further synthetic work, nothing more.
Lane map#
Lane |
Meaning |
State |
|---|---|---|
v1 |
built, evidence-gated EEG/fMRI stack (NFC, pair-operator, leakage splits) |
FROZEN — never modified by v2/v3/EM work |
v2 |
dual-field bridge (fast neural field N + slow hemodynamic field H) |
proposed; synthetic falsifier PASSED (Sprint 1A) |
v3 |
Transition Gym + KTM (hidden operators, perturbation algebra) |
proposed; synthetic operator-recovery falsifier PASSED (Sprint 1B) |
EM |
Kahlus-EM no-human artifact audit (v3 side module) |
Stage 0 scaffold only; report generator = Sprint 1C (next) |
v2.5 |
Orch-OR / quantum-biology substrate motivation |
doc-only, parked (Issue #15); gated behind v2 usefulness |
Sprint trail (all merged to main)#
Sprint |
PR |
Tag |
Sprint commit |
Merge commit |
Tests |
Result |
|---|---|---|---|---|---|---|
0 — scaffolds |
#13 |
|
|
|
337 OK |
infra only, all gates block claims |
1A — v2 falsifier |
#16 |
|
|
|
348 OK |
v2 falsifier PASSED |
1B — v3 falsifier |
#17 |
|
|
|
361 OK |
v3 falsifier PASSED; KTM loses to baselines (honest) |
1B.5 — falsification core |
#18 |
|
|
|
369 OK |
behavior-preserving dedup |
Sprint 0 — v2/v3/EM synthetic scaffolds + unified evidence gate#
Delivered the skeleton (infrastructure only, every gate blocks claims by default):
src/neurotwin/gates/— branch-aware unified evidence gate (dossier JSON schema), separate from v1reports/evidence_gate.py.src/neurotwin/models/dual_field/— v2 synthetic fast/slow dual-field system.src/neurotwin/transition_gym/+src/neurotwin/models/ktm/— v3 gym + KTM scaffold.src/neurotwin/baseline_runner.py— shared baseline sweep (reuses existing baselines/metrics).src/neurotwin/em/— Kahlus-EM Stage 0 no-human artifact audit + passive logging.src/neurotwin/numerics.py— suppress spurious Apple-Accelerate matmul FP warnings (values unchanged).scripts
run_*.py, configsconfigs/{models,em}/,docs/roadmap/, deterministic seeded tests.Filed Issue #14 (Sprint 1 hardening) + Issue #15 (Orch-OR v2.5, doc-only).
Sprint 1A — harden v2 dual-field into a falsifier#
src/neurotwin/models/dual_field/diagnostics.py + benchmark.py. Seven diagnostics, all PASS
(seed-robust, adequate data budget): fast latent recovery (N), slow latent recovery (H),
EEG↦fast-field dependence, BOLD↦slow-field dependence, lag recovery (BOLD is delayed not
instantaneous), one-field vs two-field forecast (structure beats single-timescale without
regressing the slow channel), long-rollout stability. Gate allows narrow scope
synthetic_dual_field_recovery. Degenerate/tiny data correctly FAILS (no forced win).
Sprint 1B — harden v3 Transition Gym operator recovery#
src/neurotwin/transition_gym/operator_recovery.py + benchmark.py. Falsifier PASSED:
hidden-operator recovery (recover M_k from latent transitions vs known truth), held-out AB/BA
composition recovery (single-op estimates compose to unseen pairs), explicit non-commutativity
score, response-profile distances (trajectory / operator-induced separability / subject-transfer).
Baseline leaderboard incl. retrieval_knn + untrained KTM; KTM loses to ridge —
ktm_beats_baselines=false, reported honestly. This validates the benchmark / operator-recovery
machinery, NOT v3 model superiority. Gate scope synthetic_transition_operator_recovery.
No-cheat property verified: hidden operators used only for grading, never as model input.
Sprint 2A — cluster-native trainable KTM harness (implemented; NOT yet merged)#
The v3 “launchpad”: a trainable KTM + standalone training harness so the gym task can move from untrained scaffold to trained model, cluster-ready but run locally first. Landed code:
src/neurotwin/models/ktm/torch_ktm.py—TorchKTM(trainablenn.Module) +TorchKTMConfig, a torch sibling of the numpy scaffold (scaffold left intact; still the operator-recovery grader).src/neurotwin/training_v3/— standalone harness (config,dataset,objective,checkpoint,metrics_eval,trainer,bundle). Isolated from the frozen v1training/package; reusesruntime/distributed.py(DDP),repro.py,falsification/gates, andbaseline_runner.py. Modes:cpu_smoke/single_gpu/ddp. Guards: finite/NaN micro-batch skip + loss-explosion abort. Checkpoint save/resume viatorch.save/torch.load(weights_only=True)(+ fallback).scripts/run_ktm_train.py,configs/train/ktm_synthetic_smoke.yaml,configs/train/ktm_a100_micro.yaml.gates/unified_gate.py— added narrow scopesynthetic_ktm_training_harness(additive).
Two-tier claim discipline. Primary scope synthetic_ktm_training_harness = infrastructure
readiness only (training runs, loss decreases, checkpoint/resume, bundle writes, DDP command exists,
gates block broad claims). The stronger synthetic_ktm_recovery stays blocked until a trained
KTM beats strong baselines on locked held-out metrics — a decreasing loss never flips it. CPU smoke:
val MSE 0.218 → 0.012, harness scope allowed, KTM honestly loses to the mlp baseline so recovery
stays blocked. Full suite 398 OK. No commit/PR/A100 in the implementing run (pending human review).
A100 gate (unchanged, restated). No cluster run until: synthetic task locked → baselines →
evidence gates → CPU smoke + ≥3 local seeds pass → complete output bundle (cards/metrics/failure
logs). The first cluster job is a tiny 8×A100 micro-sweep (short steps, 3 seeds, full bundle, no
claims beyond the gate), launched via the printed torchrun --nproc_per_node=8 command (or the
slurm wrapper) — never a full beast run. 2A builds the launchpad; the rocket waits on the pad.
Sprint 2D — KTM recovery-claim fairness fix + runbook tightening#
The first real A100 micro-sweep (7×A100, commit 7915929) was an infra PASS but wrongly flipped
recovery_claim_allowed=true: KTM test MSE 0.000696 beat the best baseline mlp 0.000947, but
the baselines had run the runner’s default 60 steps while the KTM ran 400 under 7-way DDP — a
training-budget artifact, not an earned recovery. Fix locks the comparison:
training_v3/config.py—baseline_train_steps(0 = auto-match KTMsteps) +recovery_margin(relative MSE the KTM must clear; default 0.05).training_v3/metrics_eval.pyktm_vs_baselines— records full budget provenance (ktm_train_steps,ktm_world_size,ktm_global_batch_size,baseline_train_steps,baseline_batch_size,baseline_budget_policy="matched_optimizer_steps");ktm_beats_baselinesis now an earned win =comparison_locked AND relative_improvement >= margin. World size is recorded but never inflates the baseline budget (would overfit the tiny synthetic baselines).training_v3/bundle.py— runs baselines to the matched budget; emits explicit recovery blockers (unmatched budget / sub-margin) sofailure_reasons.jsonis auditable.configs/train/ktm_a100_micro.yaml(baseline_train_steps: 400,recovery_margin: 0.05) + smoke config (recovery_margin: 0.05).AGENT_RUNBOOK.md—python3(host has no barepython), GPU-count honesty (label N×A100, never claim 8 when you ran 7), matched-budget + margin claim rule. No A100 re-run from here (synthetic discipline); validated by CPU smoke + newtest_metrics_eval.py(the unmatched-budget regression stays blocked even whenktm_mse < baseline_mse).
Roadmap ahead (not yet built)#
Sprint 1C — EM Stage 0 report generator. Prettier artifact report, artifact severity score, channel/frequency contamination map, HTML/Markdown report from phantom/no-human data. Reuse the falsification core (
src/neurotwin/falsification.py) — do NOT invent a fourth harness. Still no humans, no stimulation.Sprint 1D — baseline leaderboard + gate report polish (per Issue #14).
v2.5 (Issue #15) — Orch-OR / quantum-biology appendix. Doc-only, speculative substrate note (
Q_tlatent layer documented, never coded). Gated behind v2 proving useful.Real-data (MOABB/Algonauts) or A100 work: gated behind passing local synthetic falsifiers.
Hard constraints (every sprint)#
v1 is frozen. Never modify load-bearing v1 paths:
training/prepared.py,benchmarks/suite.py,data/split_manifest.py,reports/evidence_gate.py, eval command routing,models/__init__.py.No A100/cluster jobs until local synthetic falsifiers pass. No fake/placeholder “wins”.
No
do(a)/causal wording unless intervention is actually randomized/assigned.EM = no-human only: no stimulation, high voltage, plasma/coils/gas, God Helmet, clinical claims. Geomagnetic fetch stays offline (no network).
No new heavy deps (numpy/torch/PyYAML + stdlib only; no scikit-learn — cluster-only).
Narrow synthetic claim scopes only; the gate blocks anything broader.
PR / branch discipline#
One PR per sprint, single-purpose. Refactors get their own PR (do not fold into a feature PR).
Pattern: build on a fresh branch off updated
main→ open PR → human review → merge commit (never squash; the sprint commits are research checkpoints) → tag the merge commitkahlus-sprint<N>-<slug>.Create branches with an explicit upstream (
git push -u origin <branch>); branches made viagit checkout -b <b> origin/maintrackorigin/main, so a baregit pushwould target main.
Commit granularity (Sprint 1C onward)#
Sprints 0–1B.5 landed as one fat code commit each (e.g. Sprint 0 = 56 files in dbb05be). That
is hard to review and bisect. From Sprint 1C on, split each sprint into small logical commits.
One commit per logical unit — a module/concern + its test together, not a sprint-wide blob. Target ~5–8 commits per sprint.
Conventional-commit prefixes:
em:,v3:,v2:,gates:,tests:,docs:,chore:. Example Sprint 1C:em: artifact severity score·em: channel/frequency contamination map·em: html/markdown report generator·em: wire report into stage 0 script·tests: em report generator·docs: em report notes.Merge-commit, never squash — so the small commits survive on
main.The
graphify-out/refresh is always its own separatechore:commit (never mixed with code).Do not rewrite merged history to re-split old fat commits — that breaks tags + merged-PR refs. Granularity applies forward only.
Graphify discipline#
AGENTS.md: after editing code, run
graphify update .(AST-only, no API cost) to refresh the knowledge graph ingraphify-out/.graphify-out/is generated. Exclude it from feature/refactor commits (its trailing whitespace failsgit diff --checkand its churn bloats review). Stage source paths explicitly, nevergit add -A.When asked to “commit all changes”, land the
graphify-out/refresh as a separatechore: refresh graphify knowledge graph after Sprint <N>commit.
Verification (run before every PR)#
PYTHONPATH=src python3 -m unittest discover -s tests -v # full suite, must stay green
PYTHONPATH=src python3 -m neurotwin.cli doctor # v1 sanity unchanged
PYTHONPATH=src python3 scripts/run_dual_field_synthetic.py --out-dir /tmp/v2 # v2 falsifier
PYTHONPATH=src python3 scripts/run_transition_gym_baselines.py --out-dir /tmp/v3 # v3 falsifier
PYTHONPATH=src python3 scripts/run_ktm_synthetic.py --out-dir /tmp/ktm
PYTHONPATH=src python3 scripts/run_em_artifact_audit.py --out-dir /tmp/em0
PYTHONPATH=src python3 scripts/run_em_passive_logging_analysis.py --out-dir /tmp/em1
git diff --check -- ':!graphify-out' # source clean
Acceptance: full suite green; every written gate JSON has the exact dossier fields and
scientific_claim_allowed=false unless a narrow synthetic claim’s required checks all pass; no v1
load-bearing file modified.