The Risk Museum placed 4th among 83 scored teams in Agenthon 2026’s Track 2, Reasoning-Augmented Time-Series Forecasting, a NeurIPS competition. The track asks agents to forecast rates, FX, macro and factor series by combining a time-series model with LLM reasoning over a frozen, time-stamped text corpus. I entered alone. Below is how I built the agent, what I decided, and what can be replicated.
1. What I built
The task was a full distribution for rates, FX, macro and factor series one to four business days ahead, scored on sealed outcomes and normalised by the organizers’ reference model, a Gaussian random walk fitted on the trailing 300 rows. I did not try to out-model it. I rebuilt it from each card’s own data and changed the two things a rates desk would change on day one. The walk has zero drift on daily series, because a drift estimated from 300 days of a yield or an exchange rate is noise with a sign. The step covariance is exponentially weighted with decay 0.97, so last month’s volatility counts for more than last year’s. On the test bed, those two changes alone beat the reference on 71 percent of 14,519 cards. Three rules handle weekly, monthly and quarterly series: the reference drift stays, the walk steps once per panel period, and a named date after the as-of is read as a release date. Each asset and horizon gets 2,000 joint draws, 1,999 from that walk and one inert legacy path, so cross-asset correlation comes from the data. It reads none of the text corpus, calls no language model and has no trained parameters beyond the per-card covariance; the whole forecast comes from the panels. The text layer never got built; the hours went into the bed instead.
2. What I decided
The first thing I did was read the scoring rule properly. The score blends three distribution measures, CRPS, variogram and pinball loss, and then divides the whole thing by the reference model’s own error. That tells you the game. You are scored on the whole distribution, how wide it is as much as where it sits, and you only score by beating a baseline that is already sensible. So one of the first things I built was a copy of the scorer, and I checked it against the development board, where it matched to within a few hundredths.
Then I needed somewhere to test ideas honestly. Using only public FRED and ECB data, I put together 14,519 cards shaped like the final task, and I wrote the bar down before any of them had been scored. One later rule, the release-date rule, was added after the first scores existed, but before its own were run. A change could ship only if its paired difference against my own baseline had a 95 percent interval sitting entirely below zero, with a pooled-score check on top and a guard against anything that helped overall by hurting one family of cards. Two changes I liked failed that bar, so they stayed out. The three rules for weekly, monthly and quarterly series cleared it, so they went in.
The last decision was what to ship. I wanted something I could defend line by line: a public image pinned by its digest, no API key, no text, a fixed seed, and the same bytes out every time I reran the sampled cards.
3. What it shows
I treat the benchmark as the opponent and rebuild it before trying to beat it. Most of my hours went into measuring, not modelling. I built the bed, set the bar before I had seen a single number, and when a change I liked did not clear it, it did not ship. That is the part I would defend hardest across a table. It is the same shape as Working Paper No. 26, where a four-parameter regression beat a foundation model. Agents wrote most of the code under my direction; the calls on what shipped were mine, and each is written in the decision record.
- Method
- Image ghcr.io/blueship-ai/riskmuseum-t2:final-w-s97dpr-20261020, digest sha256:cd7b5029bd420947ebd6d3f96e6a522d3e8a6977e7c835f51116e4c87a182f10, CodaBench submission 1006914. To run it: mount a folder with card.toml, the panel parquet files and forecast_spec.json, then `docker run --rm --network=none -v /path/to/unit:/input:ro -v /path/to/out:/output <image> forecast --panels /input/panels --text /input/text --asof YYYY-MM-DD --out /output/forecast.parquet`; the package itself needs numpy, pandas and pyarrow. Engine facts read from the shipped source (riskmuseum_t2: engines.py override set, engine_v1.py and engine_v22.py for the walk and the draw split, cli.py for the corpus count and the verb, Dockerfile.s97dpr for the environment); no image was pulled or run for this note. The replica bed and its keep rule are in the house record (t2_final_bed/README.md, SELECTION_RULE.md, AMENDMENT_1.md, results/report.txt; the zero-drift walk against the reference is report.txt line 52: minus 0.1970 per card, as-of interval minus 0.2123 to minus 0.1818, better on 70.9 percent of cards). The board was read from agenthon.net on 3 November 2026 and saved as docs/research/agenthon_t2_paper/leaderboard_final_20261102.json.
- Related
- Working Paper No. 47 (Licensed to Run, the four public images; unpublished draft); No. 26 (Beaten by Four Parameters); No. 5 (620 Probabilities).