This is a research repo -- issue#6/#7 already decided CI should be
check-only (`task check` on push/PR), no build, no image push, no
deploy, since training runs are manual/GPU-bound and never CI-triggered.
That never actually landed in cd.yml: it still carried the original
go-web template's build+deploy jobs verbatim, still referencing the
pre-rename `hostexecutor` name (`./cmd/hostexecutor` doesn't exist --
real dirs are cmd/eval, cmd/jepa-fx-risk) and a `k3s/apps/hostexecutor`
deployment that was never created in infra (confirmed: no manifest
exists under either name). Once the check job's own Taskfile bug was
fixed, build started actually running and failing on the wrong path.
Removed both jobs -- check is the only gate this repo needs.
Taskfile.yml line 46 had an unquoted Go-template `{{.VAR}}` inside a YAML
flow sequence (`cmds: [...]`) -- the literal braces broke YAML parsing
outright ("did not find expected ',' or ']'"), so `task check` (and thus
CI's push-triggered check job) failed before running a single command.
A manual `workflow_dispatch` re-run passed because the autoresearch
workflow never calls `task check` at all -- unrelated path, not an
env/secret difference as first suspected. Quoted the string.
Also fixed a staticcheck QF1002 in internal/eval/var.go: a boolean
switch comparing the same variable (n1) in every case is a tagged
switch in disguise -- converted to `switch n1 { case 0: ... case n: ...
}`.
`LOOP_MODEL: ${{ inputs.model }}` sets the env var to "" when no model input is
given. An env var set-but-empty is NOT unset, so it overrode loop.py's
berget/gemma4-31b default, and every agent call POSTed "model":"" → HTTP 400
Bad Request. The loop ran but optimized nothing (best == baseline, delta 0).
Real overrides already flow through the conditional `--model` arg, so this env
line was redundant as well as harmful. Remove it; loop.py's default applies.
train.py is copied into runs/<rq-id>/ by autoresearch_start.py, so when loop.py
executes it, sys.path[0] is the run dir — which has no scripts/. train.py's
frozen VaR-eval block does `from scripts.var_breach import ...`, which then
fails with ModuleNotFoundError: No module named 'scripts' on every loop run
(CI and the documented manual launch alike).
Fix in the harness, not the frozen train.py/scripts boundary: prepend the
project root (loop.py's own dir, where scripts/ lives) to the subprocess
PYTHONPATH. Verified red→green locally: the import fails without it and
resolves with it (scripts/ is an implicit namespace package, no __init__.py).
The Autoresearch Loop workflow (phase-a-toy) runs train.py, which reads
data/processed/eurusd_daily.parquet. data/ is gitignored (DVC/MinIO policy, #10),
but CI checks out a fresh tree with no data, so the baseline run died with
FileNotFoundError. Force-add this single 59KB derived fixture (public EUR/USD
daily realized vol, 2019-2023, 1560 rows) so the toy loop is hermetic and fast.
This is a scoped exception to #10 for a CI fixture only — large/production
datasets still stay out of git per that policy.
train.py imports torch but the venv step only installed requirements.txt,
which deliberately excludes torch (must come from the cu130 wheel index for
koala's Blackwell sm_120, per the requirements.txt header). Baseline run died
with ModuleNotFoundError: No module named 'torch'. Add the documented install.
Gitea's act_runner does not implement the @actions/artifact v2.0.0+ backend, so
upload-artifact@v4 fails the job with GHESNotSupportedError even when the loop
succeeds. v3 is the newest tag the Gitea runner supports.
The Autoresearch Loop workflow failed at "Set up Python venv" on every run:
ERROR: Could not find a version that satisfies the requirement histdata>=1.3
(from versions: 1.0, 1.1)
histdata never published 1.3 — 1.1 is the newest release. Lower the floor so
pip can resolve and the loop can actually start.
workflow_dispatch with inputs: fixture, rq_id, iters, model override.
- Scaffolds run dir from fixtures/<fixture>.json via autoresearch_start.py
- Runs loop.py with LITELLM_KEY/LITELLM_BASE/NTFY_URL secrets
- Uploads STATUS.md + metrics.json + program.md as artifacts (30-day retention)
- Cleans stale run dir at start; venv created/reused per runner workspace
- timeout-minutes: 90 to cover multi-iter runs
Phase B (k8s Job + GPU isolation) tracked in jepa-fx-risk#15.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fixtures/phase-a-toy.json — scaffold source for `autoresearch start`
Phase A live run: val_vol_r2 metric, daily EUR/USD, HEPA encoder toy
- .gitignore: add runs/ (ephemeral run dirs), metrics.json, embeddings.json,
HEARTBEAT, STATUS.md, pyc/__pycache__/.pytest_cache, eval binary
- untrack metrics.json (was a stale committed sample, now gitignored)
Launch on koala:
python scripts/autoresearch_start.py fixtures/phase-a-toy.json phase-a-toy
op run -- env LITELLM_KEY="$LITELLM_KEY" \
python loop.py --run-dir runs/phase-a-toy --iters 3
Closes the last gate on jepa-fx-risk#11 Phase A (code shipped v1.4.0;
live run pending).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Closes jepa-fx-risk#11 Phase A.
- scripts/autoresearch_start.py: scaffold runs/<rq-id>/ from Council backlog leaf;
fail-closed on non-autoresearch-ready; strips candidate_metric; writes program.md
+ run.json (provenance) + train.py copy. 19 TDD tests.
- loop.py: --run-dir flag redirects STATUS.md / metrics.json / HEARTBEAT / train.py
into the run dir; METRICS_OUT env var passed to train subprocess so it writes
metrics.json to the run dir; heartbeat file written each iter phase; ntfy-on-crash
via NTFY_URL env var (best-effort).
- train.py: METRICS_OUT env var overrides metrics.json path (default unchanged).
Launch: LITELLM_KEY=xxx python loop.py --run-dir runs/rq-04
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two-phase phase-1:
1a. Frozen warmup: head trains on pre-computed embeddings for PHASE1_EPOCHS=200
1b. Joint fine-tune: encoder + head for PHASE1_JOINT_EPOCHS=30 at PHASE1_ENCODER_LR=3e-6
Key design decisions:
- Warm start prevents catastrophic forgetting (PHASE1_JOINT=1 cold-start → -32 R²)
- Normalize live encoder output with FROZEN stats (mu_e/sd_e) so head sees same
embedding distribution it was warmed up on
- head LR reduced 10× in joint phase to prevent head from racing ahead
HPO sweep: 30ep@3e-6=0.3962, 30ep@1e-5=0.3930, 50ep@3e-6=0.3923
Baseline (frozen): 0.3908. New best: phase1_r2=0.3962 (+0.0054 OOS).
New knobs: JEPA_PHASE1_JOINT (default 1), JEPA_PHASE1_JOINT_EPOCHS (default 30),
JEPA_PHASE1_ENCODER_LR (default 3e-6). 4 new tests (tests 15-18). 28/28 pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- prepare_hourly.py: keep O/H/L columns from M1 zips; compute per-hour
hl_range=log(H/L) and ret_intrabar=log(close/open); backward-compat
(falls back to 4-col output only when O/H/L present in input)
- train.py build(): auto-detect extra features from parquet columns
(FEAT_COLS = [ret, realized_vol] + [hl_range, ret_intrabar] if present)
- 5 new tests (9 total in test_prepare_hourly); 24/24 pass
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- train.py knobs all readable from JEPA_* env vars (JEPA_WINDOW, JEPA_D_MODEL,
JEPA_DEPTH, etc.) so hpo_sweep.py can override without touching source
- scripts/hpo_sweep.py: 3×2×3 grid over D_MODEL × DEPTH × WINDOW,
logs to results/hpo/hpo_results.jsonl with leaderboard at end
- 3 new tests: env override correctness, configs() schema validation
- 19/19 tests pass
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Go harness reported 0.42 vs Python 0.36 because export used realized_vol[t]
(current) while Python probe used realized_vol[t+1] (next-period). Fix adds
t+1 < len(df2) guard and uses iloc[t+1] as target. Go now matches Python: 0.3585.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BATCH_SIZE=512 per step; batched embed() at eval + export time.
78k hourly windows can't fit in GPU in one shot (was fine at 877 daily).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- train.py build(): year-based split (train≤2021, OOS≥2022) replaces
misleading 70/30 mixed-period split; true OOS val_vol_r2 now ~-0.36
vs previously reported +0.18 (artefact of cross-period data leakage)
- train.py: EXPORT_EMBEDDINGS block now exports both train+OOS embeddings
with dates and HV labels for Go eval harness
- cmd/eval: LinearProbeTrainTest uses train stats for standardisation of
both sets (no leakage); standardiseCompute/applyStandardise helpers
- internal/eval: add LinearProbeTrainTest (fit-on-train, eval-on-OOS)
alongside LinearProbe (same-set); 8/8 tests still green
Phase-0 gate result: val_vol_r2=-0.36, silhouette=0.043, erank=58.9/64.
Backbone produces high-rank embeddings (SIGReg working) but does NOT
generalize across 2021→2022 regime boundary. Gate: INCONCLUSIVE/FAIL.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase-0 SSL feasibility gate run on daily 2019-2023 EUR/USD (path B deviation:
not hourly 2008-2022 + Go harness as specced in #5). Results:
TS-JEPA silhouette mean=0.018 (need >0.20) — FAIL
PCA baseline silhouette=0.136 — also below threshold
sensitivity: 2000 ep + D=64 worsened to 0.004 (not a training-time issue)
Root cause: 2 daily features (ret, realized_vol) carry minimal regime structure
at this resolution. The JEPA objective with SIGReg pushes embeddings toward
isotropic Gaussian — good for downstream probes (val_vol_r2>0) but may actively
resist the clustering structure the silhouette gate measures.
Null protocol: real gate requires #4 (Go harness) + #2 (hourly data, more
features) before rerunning. HEPA (#14) noted as alternative backbone.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All 5 kept: val_vol_r2 -0.1543 → +0.0599 (+0.214 total). Backbone learning.
Agent tuning: LR, depth, SIGREG_LAM, EPOCHS. Still well below toy ceiling
(0.37) — real backbone room to grow via #3/#4/#5.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PatchTST-style transformer encoder with JEPA predictive loss + SIGReg
regularization (Balestriero & LeCun arXiv:2511.08544; time-series placement
from ChronoJEPA). Token-level SIGReg (dual placement) to avoid time-axis
collapse (confirmed real by ChronoJEPA). Baseline val_vol_r2=-0.1543 on first
run — expected for fresh weights with new architecture. Agent will iterate.
SIGReg source: Epps-Pulley statistic, identical math to LeJEPA MINIMAL.md.
Refs: #3 (TS-JEPA reproduce), ChronoJEPA github.com/MrRobotop/ChronoJEPA
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Toy encoder near ceiling. 1 kept (val_vol_r2 0.3032→0.3442), 5 reverts.
Consistent plateau = time to swap in TS-JEPA backbone (#3/#5).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fetch_data.py politely pulls EUR/USD M1 from histdata.com (maintained package
handles the anti-hotlink token; per-year, spaced). prepare_data.py (LOCKED per
Phase-1 contract) parses M1 -> daily series with realized_vol = the val_vol_r2
target (sqrt sum of squared intraday returns). Verified on 2019-2021: 937 days,
March-2020 COVID RV spike 5.1x over 2019 median — real signal, target works.
Data gitignored (DVC/MinIO = #10).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
scripts/check_gpu.py verifies PyTorch cu130 sees the koala Blackwell GPU
(sm_120) and computes — the Phase-0 prerequisite before any autoresearch
experiment. Verified green: torch 2.12.1+cu130, RTX 5070, GPU matmul OK.
Note: koala GPU is shared with the llama-swap LLM stack — run the autoresearch
agent on iguana/berget so the card stays free for train.py.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>