Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
034e682
Spec 015 T006: record tables generated from summary.json
claude Nov 13, 2026
30cc410
Spec 015: development record on spent seed 335; fall-back rule and st…
claude Nov 13, 2026
3b6e6b5
Spec 015 validation seed 336: soundness stop passed (1.01; 1.000; 0.1…
claude Nov 13, 2026
87f4f66
Spec 015 test seed 337: record. D1 0.33 [0.20, 0.45]; D2 first right …
claude Nov 13, 2026
b89d56c
Spec 015: figure script for the slides (reads the committed records)
claude Nov 13, 2026
4c5be5a
Spec 015: panel review, round 1 (five approve with findings) and corr…
claude Nov 13, 2026
cb88c23
Spec 015: write-up, slides post and deck, figures; AGENTS.md; tasks done
claude Nov 13, 2026
7e241d6
Spec 016: specification and kickoff (owner 2026-11-13, option B: clas…
claude Nov 13, 2026
9419197
Spec 016: plan, research and tasks
claude Nov 13, 2026
3d94728
Spec 016 T002-T005 (first pass): hedged track, declaration path, pool…
claude Nov 13, 2026
9156bd7
Ignore agent worktrees
claude Nov 13, 2026
e26e433
Spec 016: research, the controls as built and where they depart from …
claude Nov 13, 2026
35fb4fc
Spec 016 T007, T008 and T009's plans: the whole-period model's CPU side
claude Nov 13, 2026
876f9a4
Merge branch 'worktree-agent-aadcb89e893dabe0e' into claude/keen-wozn…
claude Nov 13, 2026
54cabb5
Spec 016 T006: the controls on tuning seed 338; settings chosen (marg…
claude Nov 13, 2026
5fb4735
GPU pilot fix: the container could not import modal_train (start-up f…
claude Nov 13, 2026
947d23c
Spec 016 T008: learned tracks scored by the controls' runner (the mod…
claude Nov 13, 2026
ab0509e
GPU launcher: back to the form spec 012's runs used (functions at mod…
claude Nov 13, 2026
c3ee8c2
Spec 016: criteria L-c and L-d, the order enforced in the scorer (a l…
claude Nov 13, 2026
261eb5b
Typing in the criteria test
claude Nov 13, 2026
813cd2b
Spec 016: the pilot's record, final training plans, the plan to be re…
claude Nov 13, 2026
e9d21f9
Spec 016 T010: decision record for study L (plan, training plans, cod…
claude Nov 13, 2026
2c191b2
Spec 016 T010: the decision record itself (the previous commit carrie…
claude Nov 13, 2026
45375fa
Spec 016 T012: the controls on validation seed 340 (L-a passes); adde…
claude Nov 13, 2026
ce64572
Spec 016 T011: three seeds trained (records, spending against the own…
claude Nov 14, 2026
fbda7fa
Spec 016: test run file in shards of 12 (the matrix limit); the launc…
claude Nov 14, 2026
c7856e1
Spec 016: tables for the learned model's sections (a separate script;…
claude Nov 14, 2026
ed57ddc
Spec 016 T013: the test on seed 341, scored once. Learning shown on n…
claude Nov 14, 2026
4a8e446
Spec 016 T014: panel review, round 1 (five approve with findings) and…
claude Nov 14, 2026
3ce1539
Spec 016: figure script and the stored tracks it draws (extracted fro…
claude Nov 14, 2026
bdcdbfe
Spec 016 T015: write-up, slides post and deck with track plots, figur…
claude Nov 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/gputrain.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ jobs:
MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }}
run: |
test -n "$MODAL_TOKEN_ID" || { echo "MODAL_TOKEN_ID is not set (owner-only)"; exit 1; }
uv run --with modal modal run scripts/modal_train.py \
# a call that never starts training must not wait for hours: the segment's limit
# plus 25 minutes for the image, the queue and saving
LIMIT=$(python3 -c "import json; print(json.load(open('configs/regress/${{ steps.plan.outputs.name }}.json'))['segment_limit_seconds'] + 1500)")
timeout "$LIMIT" uv run --with modal modal run scripts/modal_train.py \
--plan "configs/regress/${{ steps.plan.outputs.name }}.json" \
--out "$RUNNER_TEMP/out" --run-name "${{ steps.plan.outputs.name }}-${{ github.run_id }}" \
--commit "${{ github.sha }}"
Expand All @@ -72,7 +75,8 @@ jobs:
MODAL_TOKEN_ID: ${{ secrets.MODAL_TOKEN_ID }}
MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }}
run: |
uv run --with modal modal run scripts/modal_train.py \
LIMIT=$(python3 -c "import json; print(json.load(open('configs/regress/${{ needs.segment1.outputs.name }}.json'))['segment_limit_seconds'] + 1500)")
timeout "$LIMIT" uv run --with modal modal run scripts/modal_train.py \
--plan "configs/regress/${{ needs.segment1.outputs.name }}.json" \
--out "$RUNNER_TEMP/out" --run-name "${{ needs.segment1.outputs.name }}-${{ github.run_id }}" \
--commit "${{ github.sha }}"
Expand Down
20 changes: 19 additions & 1 deletion .github/workflows/timelate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,21 @@ jobs:
EXTRA=()
if [ "$THIN" != "plan" ]; then EXTRA+=(--thin "$THIN"); fi
if [ "$SYSTEMS" != "plan" ]; then EXTRA+=(--systems "$SYSTEMS"); fi
# spec 016: a run file may name the learned model's seeds (results branch, folder,
# training plan); their tracks are predicted here, outside the checkout, and scored
# beside the controls
python3 - "$RUN" > "$RUNNER_TEMP/learned" <<'PY'
import json, sys
for name, v in (json.load(open(sys.argv[1])).get("learned") or {}).items():
print(name, v["branch"], v["dir"], v["config"])
PY
mkdir -p "$RUNNER_TEMP/ckpt" "$RUNNER_TEMP/lp"
while read -r LNAME LBRANCH LDIR LCONFIG; do
git fetch -q --depth 1 origin "$LBRANCH"
git show "FETCH_HEAD:$LDIR/checkpoint.safetensors" > "$RUNNER_TEMP/ckpt/$LNAME.safetensors"
uv run tma2-tlpredict --weights "$RUNNER_TEMP/ckpt/$LNAME.safetensors" --config "$LCONFIG" --in "$TRUTH" --plan "$PLAN" --split "${{ matrix.split }}" --start "${{ matrix.start }}" --count "${{ matrix.count }}" --out "$RUNNER_TEMP/lp/$LNAME"
EXTRA+=(--learned "$LNAME=$RUNNER_TEMP/lp/$LNAME")
done < "$RUNNER_TEMP/learned"
uv run tma2-tlsolve --in "$TRUTH" --plan "$PLAN" --split "${{ matrix.split }}" --start "${{ matrix.start }}" --count "${{ matrix.count }}" --out "$RUNNER_TEMP/shards/${{ matrix.split }}.${{ matrix.shard }}" "${EXTRA[@]}"
- uses: actions/upload-artifact@v4
with:
Expand Down Expand Up @@ -106,7 +121,10 @@ jobs:
PLAN="$(python3 -c "import json, sys; print(json.load(open(sys.argv[1]))['plan'])" "$RUN")"
mkdir -p "$RUNNER_TEMP/result"
set -o pipefail
uv run tma2-tlscore --shards "$RUNNER_TEMP/shards" --plan "$PLAN" --out "$RUNNER_TEMP/result" | tee "$RUNNER_TEMP/result/score.txt"
RECORD="$(python3 -c "import json, sys; print(json.load(open(sys.argv[1])).get('controls_record', ''))" "$RUN")"
EXTRA=()
if [ -n "$RECORD" ]; then EXTRA+=(--controls-record "$RECORD"); fi
uv run tma2-tlscore --shards "$RUNNER_TEMP/shards" --plan "$PLAN" --out "$RUNNER_TEMP/result" "${EXTRA[@]}" | tee "$RUNNER_TEMP/result/score.txt"
cp "$RUN" "$RUNNER_TEMP/result/run.json"
cp "$PLAN" "$RUNNER_TEMP/result/plan.json"
echo "{\"run_id\": \"$GITHUB_RUN_ID\", \"commit\": \"$GITHUB_SHA\", \"cpu_model\": \"$(grep -m1 'model name' /proc/cpuinfo | cut -d: -f2 | xargs)\"}" > "$RUNNER_TEMP/result/ci.json"
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ conformance/onnx/java/target/
conformance/onnx/export720k/
conformance/onnx/evidence/
experiments/**/errors.npz
.claude/worktrees/
112 changes: 112 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,89 @@ learned model. Seeds 333–335 used; 324, 325 unused; the next fresh seed is 336
**Owner 2026-11-13: repair and re-register (spec 015)**: fix the duration volume, the edge rule
and the drifting reference; decide the same quantities on fresh seeds (336 validation, 337 test);
seeds 333–335 become development data. Nothing trained, no GPU.
015 (time-late repair and re-registration, `specs/015-time-late-repair/`; done; a registered study,
not a gate; nothing trained): prior version 3 (`prior.open_precision`: volume = 1/density for
every factor without a Gaussian residual), `timelate/match.py` (`same_history`, `cut_flags`: one
edge-aware rule), `run3.py` (plan key `runner: run3`; reference held to the true times and flagged
when it drifts; refit fall-back), `study3.py`, `scripts/research/tl3_tables.py` (every record's
`tables.md` generated from `summary.json`). Development on spent seed 335
(`experiments/20261113-tl3-dev/`), decision `20261113-tl3-decision/` (plan
`configs/timelate/tl-d.json` `a6556059…`, code `3b67d026…`, never edited), validation seed 336
(`20261113-tl3-val/`, soundness passed), **test record `experiments/20261113-tl3/` (seed 337, 364
scored periods): D1 0.33 [0.20, 0.45]; D2 first candidate right 0.64, wrong histories 0.44 / 0.13 /
0.43; D3 headroom 1.06 (perfect search) and 1.17 [1.10, 1.25], lower limit 1.099 (perfect evidence
as well): inconclusive; D4 held-course false manoeuvres: search 0.53 (0.80 for spec 014's search on
the same periods; 0.09 under 30 min, 1.00 over 90), C″ 0.02, flat 0.01, mixed 0.00; D5 by the
rule: no GPU budget. Decided by the panel, round 1** (`review.md`, five approve with findings; no
bug changes a decided number). Narrowed: the headroom depends most on how the reference is fitted
(1.11 to 1.19; the 19 held references carry a third of it, ten periods over half; 1.08 to 1.30
across registered sensitivities): an oracle figure, not a limit; the rule's "no" stands and an
override would be a departure from it (the first record's "a judgement, the owner's" is corrected);
D1 is "between a third and a half" and mostly restates how often C″ has the history right; D2's
ambiguous share is the threshold's (0.11 / 0.44 / 0.66 at 1 / 3 / 5); the 0.09 on short held-course
periods is not what a correct evidence gives (integrated over where a manoeuvre might be, Annex B's
prior favours "some manoeuvre" almost everywhere); **the evidence repair is not finished**: version
3's pairing fails at the edges of the prior's support and the 10 s floor (1 to 3.5 nats; recorded,
not repaired, with smaller ones in `review.md`: `_leg_tail` zero beyond 5,868 s, combined
manoeuvres' acceleration unconstrained, speed after a change unpaired). The repair trades false
manoeuvres for missed single ones (one-manoeuvre targets called quiet 0.13 against 0.04; mixed
prior 0.20; re-ranked at a quiet probability of 0.01: 0.09 false, 0.14 missed). Not delivered: each
repair's effect separated per decided quantity; unit tests for `run3`/`study3`; the fall-back
caught one of seven runaway refits. Gaps where the simulator's assumptions fail (search ÷ oracle:
shifted prior 1.7, correlated errors 1.4, outliers 1.5) have no classical remedy yet. Write-up
`site/blog/2026-11-13-time-late-repair.md`, slides `2026-11-13-time-late-repair-slides.md` (figures
`scripts/research/tl3_charts.py`). Panel's order (owner's choice): a hedged track from the stored
candidates and a declaration margin; robust and correlated-error likelihoods and a prior that can
be wrong, scored on the mismatch splits; then a learned model registered against those; no fourth
Annex B identification study. Seeds 336, 337 used; 324, 325 unused; the next fresh seed is 338.
**Owner 2026-11-13: option B (spec 016)**: classical controls on CPU and, in parallel, a learned
whole-period model on GPU under a plan registered before any result, scored only once the controls
exist; within the $50 total.
016 (time-late controls and a learned whole-period model, study L, `specs/016-time-late-learned/`;
done; SRD §5 row proposed, owner merges). **Controls** (nothing trained; `timelate/hedge.py`,
`corr.py`, `run4.py` with plan key `runner: run4`, `study4.py`): wild bearings edited out; an
effective bearing sd from the error correlation estimated from the bearings' second differences
(used above a share of 0.2); the search under Annex B's prior and a broadened one
(`configs/timelate/tl-broad.json`), pooled; a declared history (evidence margin 1, quiet probability
0.01: `ctl-d1-q0.01`) and a hedged track (temperature 1: `ctl-h1`); the combined control `ctl` is
not told the conditions. Tuning seed 338 (`experiments/20261113-tl4-tuning/`). **Model**
(`regress/period_data.py`, `period_predict.py`, `model.py` with `causal=False` and a manoeuvre
head, `timelate/learned.py`, `tma2-tlpredict`; mix `configs/regress/tlm-mix.json`, plans
`tlm-s1..s3.json`): a bidirectional transformer over 240 tokens of six 5-second rows, three seeds of
12.5 million simulated periods on a T4 each (pilot `experiments/20261113-tlm-pilot/`, runs
`experiments/20261114-tlm-runs/`, $14; November's Modal total $41 of the owner's $50); reported track
`learned-sK-arc` (the arc model fitted at the model's manoeuvre spans, probability over 0.5), own
track beside. Decision `experiments/20261113-tl4-decision/` (plan `configs/timelate/tl-e.json`
`82cd70ee…`, code `e5040f5e…`, never edited; two addenda); controls' validation seed 340
(`20261113-tl4-val/`, L-a passed; `tma2-tlscore --controls-record` refuses a learned track without
it); **test record `experiments/20261114-tl4/` (seed 341, 2,860 periods, scored once): learning
shown on no split (declared control ÷ learned 0.80 to 0.82 shifted, 0.87 to 0.94 shifted 2, 0.83
correlated, 0.77 to 0.81 outliers, 0.97 to 1.00 bias), harm on Annex B (0.81 to 0.82), the learned
track ahead on held course (1.06): by the rule the classical controls stand and the learned track
stops. Decided by the panel, round 1** (`review.md`, five approve with findings; all 42 ratios
reproduce). Controls against the standing search: 1.68 with 2% outliers, 2.44 with 5%, 1.33
correlated, 1.70 held course (false manoeuvres 0.08 against 0.49), 1.03 shifted, 1.00 bias, 1.09
Annex B (0.96 on validation); replicated on three seeds. Narrowed: the 20% is a tail (median
per-period ratio 1.00 everywhere; level where the model declares as many manoeuvres as the control,
about twice as bad where it declares fewer): **the registered comparison measured the model's
untuned detector against the evidence search**, so "this model with this read-out stops"; the own
track is 0.71 of the control on Annex B and level under the shifted prior in range only (course
0.79, speed 0.77; a win where the bound is loose cancels a loss by three where it is tight); the
mismatch conditions lie inside the model's training ranges (it lost in-distribution); a search
started from the model equals the control at a third of the time (tuning data, not a result); the
control is not free (one-manoeuvre targets called quiet 0.29 against 0.05 with correlated errors),
does nothing under bias, and knows the generator's error family; the shifted-prior gap (control ÷
true-history reference 1.7 to 2.1 by convention) is open for both and may be identifiability. Not
delivered: a working prior-mismatch diagnostic (`log_bayes_broad` is not a Bayes factor), a Huber
cost, a whitened fit, prior version 4, the export check on trained models, IMM/EKF tracks beside,
the weights' digests in the test record, unit tests for `run4`/`study4`. Not tested: a learned
proposer inside the search, training through the arc layer, a larger budget. Write-up
`site/blog/2026-11-14-time-late-learned.md`, slides `2026-11-14-time-late-learned-slides.md`
(figures `scripts/research/tl4_charts.py`; tables `tl4_tables.py`, `tl4_learned_tables.py`,
`tl4_supplement.py`). Panel's order (owner's choice): adopt `ctl` as the standing time-late system
and study whether the shifted-prior gap is identifiability (classical, CPU); only if it is not, the
trained models as proposers inside the search. Seeds 338 to 341 used; 324, 325 unused; the next
fresh seed is 342.

## Stack (owner decisions, 2026-10-31)

Expand Down Expand Up @@ -675,6 +758,35 @@ seeds per carried configuration; nothing above OFFICIAL, mock data only.
0.43). Before running an adaptive rule, run it on noise alone. "At no cost" needs the counts
by case, not a pooled median. A fixture pinned to 1e-9 fails on another CPU family; pin the
well-determined outputs tightly and the rest loosely.
- Spec 015 panel: a repair to a reference changes every statistic that reads it; report the
decided figure under each way of fitting the reference, not only the registered one. A fit told
the true times knows more than which history is right. Do not register as a decision rule a
line you forecast as a coin's toss at the planned sample size; and when a rule returns "no" by
a hair, report the rule's output as the outcome and do not reword it as a judgement. Test a
density-volume pairing at the edges of the support and at solver bounds, not only inside.
Comparing one Laplace mode with "no manoeuvre" is not the evidence for "some manoeuvre":
integrate over where it might be. Report a class share at every registered threshold. Commit
the script that checks a README's numbers, or do not claim the check. A forecast written from
a development run minutes earlier is a restatement, not a prediction.
- Spec 016 panel: a registered comparison is only as wide as its narrowest part: thresholding a
model's manoeuvre probability at an untuned 0.5 and fitting a classical layer at those spans
measures the detector, not the model. Tune both sides or neither, and score the learned side on
validation as soon as the order allows. Report the median per-period ratio and the shares over
twice as bad beside any geometric mean (the 20% was a tail; the median was 1.00), and split a
loss by whether the two sides agree on structure. Read a "level" pooled ratio by how tight the
bound is and on course and speed before writing "level". A held-out condition inside the
training ranges is not a mismatch for the model. A reference fitted to unedited bearings is no
yardstick under outliers. Keep the declared and undeclared variants of a control in separate
rows. Record the weights' digest in the scored record, not only in shard headers. A change of
shard size reaches every system that starts from C″. Never edit a file the decision record
binds while its run is in progress, even to add a field; put new readings in a new script.
- Modal: functions must be defined at module scope with the decorator; functions built in a loop
with `serialized=True` cannot be imported in the container ("the 'modal_train' module is not
available"), and Modal retries the failed start for as long as the client waits. Bound the
workflow step with `timeout`. The ledger loses a replaced container's time.
- A script that writes a record must run under `uv run python`: a plain `python3` here has no
torch, the script fails, and a chained `git commit` still commits whatever else was staged
(spec 016's decision record took two commits). Check the commit holds what its message says.
- Modal rate-limits app creation: of five `modal run` jobs started within ten seconds, two were
refused ("App create rate limit exceeded") before any container started. Launch GPU plans a
minute apart, or re-run the refused workflow run (the plan and its digest are untouched).
64 changes: 64 additions & 0 deletions configs/regress/tlm-mix.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"description": "Spec 016 research R9: the training mix of the learned whole-period model. Every engagement of the stream draws its family and its bearing-error overlay from its own sub-stream of the stream seed (tma2.regress.period_data). Families: 40% Annex B plus the 25% R9 leaves unassigned (0.65 in all), 20% randomised statistics, 15% quiet. Independently, a quarter of all engagements carry an overlay: correlated error, outliers or both, a third each. Bias and the unknown-sigma factor are never in training (the absent-from-training split). Each engagement is cut to a period as Gate T does (tma2.timelate.periods.cut).",
"base_ranges": "configs/timelate/tl-ranges.json",
"quiet_ranges": "configs/timelate/tl-quiet-ranges.json",
"families": {
"annex_b": 0.65,
"randomised": 0.2,
"quiet": 0.15
},
"randomised": {
"leg_mean_s": {
"min": 600,
"max": 2400
},
"leg_sd_over_mean": 0.3,
"leg_min_s": 300,
"scale": {
"min": 0.5,
"max": 1.5
},
"scaled": [
"target_manoeuvre_angle_abs_rad (min and max, one factor; max at most pi)",
"target_turn_rate_rad_s.max",
"target_speed_change_abs_mps.max",
"target_speed_change_rate_mps2.max"
],
"course_weight": {
"min": 0.5,
"max": 0.9
},
"speed_share_of_rest": {
"min": 0.3,
"max": 0.7
}
},
"overlay": {
"fraction": 0.25,
"kinds": {
"correlated": 0.3333333333333333,
"outliers": 0.3333333333333333,
"both": 0.3333333333333334
},
"corr_tau_s": {
"min": 20,
"max": 240
},
"corr_share": {
"min": 0.2,
"max": 0.7
},
"outlier_rate": {
"min": 0,
"max": 0.06
},
"outlier_spread": {
"min": 5,
"max": 15
}
},
"held_out": [
"bias",
"sigma_factor"
]
}
35 changes: 35 additions & 0 deletions configs/regress/tlm-pilot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"description": "Spec 016 T009: the pilot of the whole-period model on a T4 (research R7, R10): the real architecture (8 layers, width 256, 8 heads, 240 tokens of 6 five-second rows, non-causal, manoeuvre logit), one segment of at most 50 minutes (an hour with the start-up allowance). Measures periods per second and gives a loss curve; 6,000 updates of 256 periods complete inside the limit only above about 510 periods a second, otherwise the limit stops it. Not a training run: its weights are not carried.",
"gpu": "T4",
"config": {
"id": "tlm-pilot",
"seed": 1,
"warmup_updates": 500,
"total_updates": 6000,
"val_every_updates": 500,
"limit_seconds": 3000,
"layers": 8,
"width": 256,
"heads": 8,
"feedforward": 1024,
"batch_scenarios": 256,
"learning_rate": 0.0003,
"weight_decay": 0.01,
"val_count": 512,
"checkpoint_seconds": 900,
"precision": "amp",
"patch_rows": 6,
"max_rows": 1440,
"causal": false,
"manoeuvre_head": true,
"manoeuvre_weight": 0.1,
"n_features": 9,
"data": "periods",
"mix": "configs/regress/tlm-mix.json",
"stream_seed": 339,
"val_seed": 339,
"stream_start": 0
},
"segments": 1,
"segment_limit_seconds": 3000
}
Loading
Loading