From bdd10cbbc1a585426d49c64fb0596bad8e774913 Mon Sep 17 00:00:00 2001 From: Paul O'Reilly Date: Mon, 3 Aug 2026 07:26:41 +1200 Subject: [PATCH] idle-draft: add sample_override config flag to bypass cold-start throttle Optional config key (default false). When true, research candidates are offered even at max_unreviewed_research_per_dossier; research_sampled bookkeeping, mark sampled, and status flags are unchanged so unsampled items stay visible for later human verification. Claude-Session: https://claude.ai/code/session_01Lgv4Qn82boNFC1jn8QXSNw --- scripts/idle-draft | 3 ++- specs/idle-draft.spec.md | 8 ++++++++ tests/test-idle-draft.sh | 11 +++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/scripts/idle-draft b/scripts/idle-draft index b030cb7..3928531 100755 --- a/scripts/idle-draft +++ b/scripts/idle-draft @@ -683,6 +683,7 @@ def count_open_topic_proposals(repo: Path, dossier: str) -> int: def build_ready_queue(repo: Path, config: dict, state: dict, in_flight: set) -> list[dict]: candidates: list[dict] = [] dossiers = config["dossiers"] + sample_override = bool(config.get("sample_override", False)) for idx, dossier in enumerate(dossiers): unreviewed = count_unreviewed_research(repo, dossier, state) max_unreviewed = config["max_unreviewed_research_per_dossier"] @@ -693,7 +694,7 @@ def build_ready_queue(repo: Path, config: dict, state: dict, in_flight: set) -> files = item_files(repo, dossier, f"{nn}-{slug}") entry = get_item_state(state, key) wtype, reason = next_work_type_for_item(files, entry) - if wtype == "research" and unreviewed >= max_unreviewed: + if wtype == "research" and unreviewed >= max_unreviewed and not sample_override: continue if wtype is None: continue diff --git a/specs/idle-draft.spec.md b/specs/idle-draft.spec.md index aa5c45b..b212803 100644 --- a/specs/idle-draft.spec.md +++ b/specs/idle-draft.spec.md @@ -86,6 +86,12 @@ Parse `--config` as JSON. Required top-level keys: `parallel`, `providers`, `wor verbatim. Missing required keys or malformed structure → log loudly, exit **2**. `--parallel` on the CLI overrides the config value. +Optional key `sample_override` (bool, default `false`): when `true`, the cold-start +throttle (§ dispatch, below) is bypassed — `research` candidates are offered even when +the dossier is at `max_unreviewed_research_per_dossier`. Nothing else changes: +`research_sampled` bookkeeping, `mark … sampled`, and the `status` flags are unaffected, +so unsampled research remains visible and awaiting human verification. + ### 4. State load and validation Read `/idle-draft.state.json`. Missing file is not an error — treat as @@ -184,6 +190,8 @@ from consideration for further dispatch until the in-flight task completes. whose state entry does **not** have `research_sampled: true`. Once that count reaches `max_unreviewed_research_per_dossier`, no further `research` candidates are offered for that dossier this cycle (draft/review candidates in that dossier are unaffected). +If config `sample_override` is `true`, this throttle is skipped entirely; the unsampled +counts and `research_sampled` state are still maintained exactly as above. **`topic_ideas`:** dossier-level (not tied to an `NN-slug`), considered only when the combined candidate list above (across all dossiers) is empty. For each dossier, eligible diff --git a/tests/test-idle-draft.sh b/tests/test-idle-draft.sh index f805fa4..d9f3c28 100755 --- a/tests/test-idle-draft.sh +++ b/tests/test-idle-draft.sh @@ -570,6 +570,17 @@ draft_candidates = [c["slug"] for c in tqueue if c["work_type"] == "draft"] check("cold-start throttle: draft candidates for 01/02 unaffected by the research throttle", set(draft_candidates) == {"01-a", "02-b"}, draft_candidates) +# sample_override: throttle bypassed, sampling state untouched +override_config = dict(throttle_config) +override_config["sample_override"] = True +oqueue = m.build_ready_queue(throttle_repo, override_config, throttle_state, set()) +override_research = [c["slug"] for c in oqueue if c["work_type"] == "research"] +check("sample_override: item 03's research is offered despite the dossier being at the cap", + "03-c" in override_research, override_research) +check("sample_override: research_sampled state is not mutated by queue building", + not any(m.get_item_state(throttle_state, f"ai/{s}")["research_sampled"] + for s in ("01-a", "02-b", "03-c")), throttle_state) + # topic_ideas: only offered when nothing else is eligible idle_repo = tmpdir / "idle-repo" (idle_repo / "ai").mkdir(parents=True, exist_ok=True)