Skip to content

assign_splits's own non-empty-val guarantee doesn't fire when every real task hashes into test #271

Description

@AmirF194

assign_splits in skillopt_sleep/mine.py says right in its own comment "Guarantee val (the gate) is non-empty when we have >=2 real tasks", but there's a gap: _promote_one only pulls from train (to top up val) or from val (to top up train), never from test. If every real task's hash bucket lands in [val_cut, test_cut), both train and val start empty, so both guarantee calls have nothing to promote and silently no-op. A nightly batch mines N real tasks, all go to test, and run_sleep_cycle finishes with gate_action='reject', edits=0, no error, no warning, holdout_leaked doesn't flag it either.

Not contrived: for a small nightly batch (2-5 tasks is realistic for a solo user's session), any test_fraction above roughly 0.5 makes this a matter of when, not if. Confirmed on 79124b37: 5 tasks with val_fraction=0.10, test_fraction=0.80 all land in test, val and train both come back empty.

I think I see why it's shaped this way. #235 replaced the old unconditional real[-1].split = "val" fallback (append-order-unstable, could demote an already hash-assigned test task) with the current from-train/from-val-only _promote_one, to stop reassigning hash-assigned test tasks per that review. That fixed the instability and reopened this as a side effect.

An additive fix, only reach into test for the promotion when train and val are both empty, and log it the way consolidate.py already logs holdout_leaked, would keep the stability guarantee for the normal case and only touch the pathological one. Wanted to check that's the right shape before sending a PR, since it's touching the val/test tradeoff #235 just settled.

Activity

  1. kaluli123123 commented on Sep 8, 2026

    @kaluli123123

    I am working on a focused fix for this. I will first add regression coverage for the all-test hash assignment, preserve hash-assigned test tasks in normal cases, and make any emergency test-to-train/val rebalance explicit in the logs. I will post the PR and validation results here once the change has been reviewed locally.

  2. linhongyu510 commented on Sep 10, 2026

    @linhongyu510

    Opened #276 with the additive shape you proposed.

    Confirmed the no-op on 79124b37: 5 tasks at val_fraction=0.10, test_fraction=0.80, seed=42 gives Counter({'test': 5}), both guarantees silently no-op.

    Each guarantee keeps its existing preferred source and only reaches into test when that source is empty, so #235's stability guarantee is untouched on the normal path. Logged through logging.getLogger("skillopt_sleep").warning with the task count, both fractions and the seed.

    One case beyond what the issue describes: when dream tasks are present they already occupy train, so the train guarantee is satisfied and only the val guarantee fires — filling val from test still has to happen there. Relatedly, topping up train now prefers test over a single-task val, since taking the only val row would re-empty the gate that was just filled; with no test slice it falls back to val exactly as before.

    Full suite: 1501 passed, 12 skipped, 362 subtests. Reverting mine.py alone turns 7 of the new assertions red.

  3. Yif-Yang commented on Sep 30, 2026

    @Yif-Yang
    Contributor

    #272 satisfies the single-batch nonempty-pool and minimum-reassignment checks, including the requested 6,300-case matrix and explicit warnings. A new two-night synthetic integration check found that a task promoted from test to training can silently return to the held-out test set after re-mining with a larger pool; the cycle reports holdout_leaked=false. Please retain the bounded fallback but add durable exposure/split handling, or fail closed when a pristine heldout set cannot be preserved. Keep one coherent solution and leave this issue open pending that longitudinal regression and official CI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions