From 6b908dd3152db80813a578601037ce4c91269668 Mon Sep 17 00:00:00 2001 From: David Hyrule Date: Tue, 16 Jun 2026 09:26:21 +0200 Subject: [PATCH] feat(daemon): per-run budget overrides (--max-iterations/--max-wall-clock-minutes) The daemon hardcoded the per-run budget (DaemonConfig: 20 iterations / 45 min) with no CLI override, so a feature-class issue that needs more than 45 min times out at needs_triage every run. Add --max-iterations-per-run and --max-wall-clock-minutes-per-run to the daemon subcommand (default to the existing conservative values), so a one-off larger run can be triggered without touching the timer's defaults. Surfaced by the VPS launch-proof dogfood (hyrule-cloud#28): the loop wrote ~1083 lines of good, on-spec code but ran out of the 45-min wall clock. Validation: ruff clean, mypy --strict clean, 177 passed (+1), evals 15/15. Co-Authored-By: Claude Opus 4.8 --- src/hyrule_engineering_loop/cli.py | 8 ++++++++ tests/test_phase24_daemon.py | 15 +++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/hyrule_engineering_loop/cli.py b/src/hyrule_engineering_loop/cli.py index 30aec4e..01245dd 100644 --- a/src/hyrule_engineering_loop/cli.py +++ b/src/hyrule_engineering_loop/cli.py @@ -475,6 +475,8 @@ def daemon_command(args: argparse.Namespace) -> int: memory_dir=args.memory_dir, max_runs_per_day=args.max_runs_per_day, max_cost_usd_per_day=args.max_cost_usd_per_day, + max_iterations_per_run=args.max_iterations_per_run, + max_wall_clock_minutes_per_run=args.max_wall_clock_minutes_per_run, allowed_paths_by_repo={ repo: tuple(prefixes) for repo, prefixes in _parse_repo_paths(args.allow, option="--allow").items() @@ -832,6 +834,12 @@ def build_parser() -> argparse.ArgumentParser: daemon_parser.add_argument( "--max-cost-usd-per-day", type=float, default=DaemonConfig.max_cost_usd_per_day ) + daemon_parser.add_argument( + "--max-iterations-per-run", type=int, default=DaemonConfig.max_iterations_per_run + ) + daemon_parser.add_argument( + "--max-wall-clock-minutes-per-run", type=int, default=DaemonConfig.max_wall_clock_minutes_per_run + ) daemon_parser.add_argument( "--allow", action="append", diff --git a/tests/test_phase24_daemon.py b/tests/test_phase24_daemon.py index 8ad4e4f..c1c58df 100644 --- a/tests/test_phase24_daemon.py +++ b/tests/test_phase24_daemon.py @@ -20,6 +20,7 @@ notify_icinga, repo_name_for_issue, ) +from hyrule_engineering_loop.cli import build_parser from hyrule_engineering_loop.intake import IntakeItem from hyrule_engineering_loop.nodes import STALL_ROUND_LIMIT, delegate_implementation_node from hyrule_engineering_loop.promotion import rollback_promotions, setup_worktrees_for_state @@ -195,6 +196,20 @@ def test_idle_queue_reports_idle(tmp_path: Path) -> None: assert report.outcome == "idle" +def test_daemon_cli_per_run_budget_flags() -> None: + parser = build_parser() + # Defaults match the conservative DaemonConfig values. + default_args = parser.parse_args(["daemon", "--once"]) + assert default_args.max_iterations_per_run == DaemonConfig.max_iterations_per_run + assert default_args.max_wall_clock_minutes_per_run == DaemonConfig.max_wall_clock_minutes_per_run + # Overridable for a one-off larger run. + args = parser.parse_args( + ["daemon", "--once", "--max-iterations-per-run", "40", "--max-wall-clock-minutes-per-run", "90"] + ) + assert args.max_iterations_per_run == 40 + assert args.max_wall_clock_minutes_per_run == 90 + + def test_daemon_defaults_to_core_repos_and_low_and_slow_budget() -> None: config = DaemonConfig() assert config.repos == CORE_REPOS