Project Description
The problem statement asks for an autonomous ML research agent: a system that itself reads the problem, proposes hypotheses, implements and runs them, evaluates results, and decides what to try next — with minimal human intervention — converging per a fixed rule (ε=0.002 improvement over the last N=3 iterations, or a 50-iteration / 6-hour cap).
agent_loop.py— the autonomous agent itself. A Python driver that calls Claude headlessly (via the local Claude Code binary in--printmode — genuine, separately metered API calls) once per iteration to propose a configuration from a fixed action space (baseline.py's existing--loss,--wt_target,--k,--lr,--aux_weight,--dns_n,--adt_betaflags — no new code is written by the agent). Each proposal is run as a real training job, its validation/test metrics are read back, and the competition's own convergence rule is checked in Python code — not delegated to the model's judgment.
The prompt also grounds every proposal in a neutral dataset-facts block (catalog
size, interaction density, label-threshold behavior — numbers, not conclusions) and
a method-reference sheet naming each loss's origin paper and the assumption it
relies on, with a required mechanism_basis field forcing every choice to cite a
specific fact and assumption rather than restate what a loss generically does.
Four runs were needed to reach the submitted result, each fixing a specific,
diagnosed problem in the one before it — the full story is in
RUN_AND_ITERATION_LOG.md. Run 1 (Haiku, no dataset context) produced generic,
docstring-level hypotheses. Run 2 (Sonnet, grounded) reasoned dramatically better
but converged to a statistically identical score, because it anchored on k=32 in
iteration 1 and never revisited it — a hyperparameter-anchoring bug diagnosed via a
stability check on an unrelated experiment. Run 3 fixed that (a parameter-coverage
summary + required dimension_check field, plus a shortlist-confirmation step —
the harness runs the full 5-seed confirmation on the top 3 distinct candidates, not
just the nominal single-seed best, since the search's top candidates had started
clustering inside this project's own single-seed noise floor) and converged in 8
iterations to test primary 0.6016, statistically matching the hand-driven track's
own long-established default.
Run 4 (submitted) came from a subsequent, deliberate full re-read of the
problem statement against the implementation — not prompted by an observed
failure — which found three real compliance gaps in Run 3's harness: (a) every
iteration's log entry displayed the candidate's hidden-test score, and that log is
exactly what the next proposal call reads back as history, meaning the model's own
reasoning could see test feedback across iterations, contrary to "develops using
only train + public validation feedback"; (b) the spec fixes the convergence rule
(ε=0.002, N=3) under "Allowed Assumptions" — an organizer-given constant — but Run
3 had raised N to 5 to work around the anchoring problem, the wrong fix for a
correctly-diagnosed issue; (c) Task Requirement 1 ("reproduce the official
baseline... confirm it reaches the reported validation score") was never an
explicit, autonomous, verified step, and the codebase had actually drifted to where
it could no longer reproduce the literal official config at all (two temporal
features had been permanently added to the default 5-field encoding). All three
fixed: test scores are recorded only for the final report, never shown to the
proposing LLM mid-run; patience_n reverted to the fixed N=3 (anchoring stays
fixed via the prompting change alone); and agent_loop.py now runs a code-verified
baseline-reproduction check before iteration 1 (confirmed: single-seed valid 0.6015
vs. published 0.6016). Re-run clean under all three fixes: converged in 7
iterations, $0.48, landing on pairwise_watchtime (k=16, lr=0.001, aux_weight=0.5):
test GAUC 0.6700 / nDCG@5 0.5332, a +0.0070 score_dataset over baseline —
statistically identical to Run 3's result despite the different specific config,
and again statistically indistinguishable from the hand-driven default.
The action space was deliberately kept narrow — CLI-flag selection, not free-form code generation — as a considered reliability trade-off: a tight space that reliably runs to convergence beats an ambitious one that breaks mid-loop, and it isolates what's being measured (autonomous propose→test→evaluate→decide iteration) from open-ended architecture search. Section 2.2 of the problem statement states that "writing the code for each stage is part of the agent's job" — this design deliberately does not do that, which is the direct motivation for part 3 below.
agent_loop_codegen.py— a second, more spec-literal agent that actually writes code. Same discipline (headless Claude, decisions made in Python code, ε=0.002/N=3 convergence, one manual intervention), but each iteration invokes Claude with real Read/Write/Edit/Bash tool access — not a JSON-schema proposal — inside an isolated git worktree, and it genuinely writes and editsagent_pipeline.py: feature engineering, model architecture, training strategy are all authored by the agent, not selected from a fixed menu.evaluate.pyanddata_guard.py(a new protected shim) are read-only interfaces the agent must call, not edit — any diff touching them is a caught guardrail violation (reverted, logged as an error/recovery event), matching the two things the spec itself fixes under Allowed Assumptions (the split and the scoring script). Test-blindness is architectural, not prompted:data_guard.pyphysically omits the hidden test split from what it returns unless the harness's own once-only final confirmation sets an environment variable the agent's code cannot trigger — verified directly.
Two isolation bugs were found and fixed before trusting any result from this
agent, both the hard way. A git worktree checks out the entire repository by
default, so an early run had full Read access to RUN_LOG.md and every other file
documenting the hand-driven track's own conclusions — caught when that run cited
RUN_LOG.md by name as evidence for a hypothesis, which would have made "independent
discovery" meaningless. Fixed with git sparse-checkout and an explicit allowlist
(data.py, evaluate.py, data_guard.py, temporal_features.py, submit.py) —
the exclusion is physical, not a prompt instruction the agent's own Read tool could
route around. A second run then cited baseline.py's inline comments (which state
hand-driven verdicts directly, some also citing RUN_LOG.md) — baseline.py was
promptly removed from the allowlist too, even though it's a legitimate reference
implementation, because the specific copy in this repo is not a safe one to hand a
"blind" agent. (One smaller leak remains and is disclosed rather than patched over:
data.py/temporal_features.py are required, protected interfaces whose own inline
comments briefly mention that two engineered fields were "verified in RUN_LOG.md to
have stable gains" — a real, small residual leak we chose not to fix by forking the
canonical data pipeline.)
The clean run's result exceeded every other number in this project. Converged
in 10 iterations (1 recovered proposal-call failure), $5.01, reproducing the official
baseline from scratch on iteration 1, then independently rediscovering pointwise→BPR
and the watch-time auxiliary task — the same two interventions both the CLI-flag
agent and the hand-driven track found, at closely matching scores, on completely
independent code. Its most consequential move was novel, not a rediscovery:
iteration 6 engineered a general per-user prior-interaction-count feature — no
prior part of this project (hand-driven or either agent) had built this specific
signal, only narrower video- and author-specific analogues — reasoning directly from
the phrase "prior interaction counts" in its own method-reference sheet. That single
feature drove the largest single-iteration jump in the whole run (0.6068 → 0.6101
valid). Two further hypotheses in the same family (an item-side popularity analogue,
an is_click multi-task head) were tested and correctly identified as null, with the
codegen agent's own diffs cleanly reverted. Final 5-seed confirmation: valid primary
0.6099 ± 0.0002, test primary 0.6038 ± 0.0003 — a result that exceeds not only the
submitted agent_loop.py result (0.6016) but the hand-driven track's own best
ensemble (0.6034, below), on a single self-written model with no ensembling at all.
Submission: submission_pure_codegen.csv, generated from the winning pipeline
(agent_pipeline_final.py) and format-validated. Full transcript in
AGENT_LOG_CODEGEN.md.
This specific 0.6038 is not the agent's typical output — checked directly via 4
more independent runs, not assumed. Test primary across all 5: mean 0.6016,
σ≈0.0013, range 0.0039 — statistically the same average as the CLI-flag agent's own
4-run mean (0.6013). Run 1 is the top of that range, and separately the
validation-best of all 9 runs across both agents, which is the competition's own
selection rule. Root cause of the variance (visible in the logs): the agent
evaluates each method through its own self-written implementation, so one weak
implementation early can make a genuinely good method look bad and foreclose a
productive branch for the rest of the run. agent_loop_codegen.py gained a
--restarts flag as a direct fix — K independent trajectories, one validation-best selection
across all of them, keeping manual intervention at exactly 1. Full detail in
FINAL_SUBMISSION.md.
- A separate, hand-driven research track (
RUN_LOG.md,baseline.py,ablation_*.py,sequence_model.py,deepfm_model.py,finalmlp_model.py,lightgcn_model.py, the ensembling scripts, and the bonus-benchmark runners), where a human (using Claude Code interactively as a tool, not as the autonomous loop) explored a much wider space — architectures (DIN, BST, DeepFM, FinalMLP, LightGCN), training strategies (DNS, ADT, temporal reweighting), auxiliary objectives (CWM watch-time, RAD quantile targets), and four ensembling strategies. This reached test primary 0.6034 (heterogeneous ensemble of BST + two FM variants) over four days of literature-guided search — and producedAGENT_VS_MANUAL.md, an honest comparison against the CLI-flag agent.agent_loop_codegen.py's single self-written model exceeding this (0.6038) is itself a notable finding, discussed on its own terms rather than folded into this comparison, since it came from a materially different kind of agent.
We consider the coexistence of all three parts a feature, not a compromise: the
hand-driven work establishes what headroom actually exists and where the ceiling sits
(useful ground truth for judging whether either autonomous loop is finding real signal
or noise), while agent_loop.py and agent_loop_codegen.py are the actual answer to
the problem statement's core ask. agent_loop_codegen.py's result is the officially
designated submission: it scores higher (0.6038 vs. 0.6016 test primary) and more
literally satisfies the problem statement's own framing of the task (it writes the
code for each pipeline stage, rather than selecting among a fixed menu of pre-written
flags). agent_loop.py's result is kept alongside it as a fully valid, four-times-
validated, ~10x-cheaper alternative — the trade-off behind designating the newer,
higher-scoring, less-tested design as primary is discussed explicitly in
FINAL_SUBMISSION.md, not glossed over.
Development tools
- Claude Code (Anthropic) — as the interactive tool driving the hand-built research
track; as the invoked-headlessly proposal LLM inside
agent_loop.py; and, with real Read/Write/Edit/Bash tool access inside an isolated git worktree, as the code-writing agent insideagent_loop_codegen.py— the submitted result. - Standard shell/Python tooling on macOS (local development) and a SLURM GPU cluster (for BST and the KuaiRand-27K bonus run).
APIs used
- Claude (via the local Claude Code CLI binary, headless
--printmode) — the only API call the autonomous agent makes. Model used for the submitted run's proposal calls: Sonnet (upgraded from an earlier Haiku-based run specifically to test whether stronger reasoning improves proposal quality — seeRUN_AND_ITERATION_LOG.mdfor that comparison). No other external API was used (no OpenAI, no Google, etc.) and no data outside the officially provided KuaiRand files was used.
Libraries and frameworks
- NumPy — the entire core baseline (Factorization Machine, all loss variants, BPR sampling, evaluation) is hand-implemented in pure NumPy, matching the starter kit's original numpy-only constraint.
- PyTorch — added later, isolated to sequence/graph/deep architectures where
hand-deriving backprop stopped being worthwhile:
sequence_model.py(DIN, BST),deepfm_model.py,finalmlp_model.py,lightgcn_model.py, andbonus_fm_torch.py(a sparse-embedding FM for the KuaiRand-27K bonus benchmark, needed because dense Adam updates over a ~41M-row embedding table are computationally infeasible regardless of hardware). - No pandas, scikit-learn, or other ML framework — kept intentionally minimal.
Datasets and assets
- KuaiRand-Pure (required benchmark) — the official starter-kit dataset, including
the standard interaction logs and, for one ablation, the previously-unused official
user_features_pure.csvandvideo_features_statistic_pure.csvside-information files. - KuaiRand-1K and KuaiRand-27K (bonus benchmarks) — same task and metrics as Pure, run via a memory-efficient columnar loader built specifically because the starter kit's list-of-tuples approach doesn't scale to these sizes on commodity hardware.
- No external or synthetic data of any kind was used, per the competition's rules.
Log in or sign up for Devpost to join the conversation.