diff options
| author | CaptainJack2491 <jayrupnakawala@gmail.com> | 2026-02-26 22:53:13 +0000 |
|---|---|---|
| committer | CaptainJack2491 <jayrupnakawala@gmail.com> | 2026-02-26 22:53:13 +0000 |
| commit | a79b9feac2f23bf200aa325f72c239ac39aeea3d (patch) | |
| tree | 9b3f9ff37d1a699aea7cdb974fa09f513f81b21d /src/runner.py | |
| parent | a6b38c54ee72cbae1d696b02f32ad487391a5dfe (diff) | |
feat: add checkpoint/resume support and atomic log writes
- Runner now counts existing .json log files per combination and skips
completed runs, resuming from where it left off
- Log files are written atomically via temp file + rename to prevent
corrupt files from being counted as completed
- Added --no-resume CLI flag to force a fresh start
- Resume is enabled by default
Diffstat (limited to 'src/runner.py')
| -rw-r--r-- | src/runner.py | 28 |
1 files changed, 22 insertions, 6 deletions
diff --git a/src/runner.py b/src/runner.py index 053fa7b..3a03c8c 100644 --- a/src/runner.py +++ b/src/runner.py @@ -3,6 +3,7 @@ Runner - orchestrates experiment runs based on config. Loops through models, scenarios, and oversight levels. """ import os +import glob from typing import List, Dict, Any from config_loader import ConfigLoader, ProviderConfig, ModelConfig, ScenarioConfig from vfs import VFS @@ -21,10 +22,11 @@ def load_prompt(file_path: str) -> str: class ExperimentRunner: """Runs experiments based on configuration.""" - def __init__(self, config: ConfigLoader, verbose: bool = False): + def __init__(self, config: ConfigLoader, verbose: bool = False, resume: bool = True): self.config = config self.results: List[Dict] = [] self.verbose = verbose + self.resume = resume def run_all(self): """Run all experiments defined in config.""" @@ -76,8 +78,20 @@ class ExperimentRunner: print(f"\n--- Running: {model_name} | {scenario_name} | {oversight_level} ---") - runs_completed = 0 - for run_num in range(1, scenario_config.runs + 1): + # Check for existing completed runs (resume support) + log_dir = os.path.join(output_dir, model_name_safe, scenario_name, oversight_level) + existing_runs = 0 + if self.resume and os.path.isdir(log_dir): + existing_runs = len(glob.glob(os.path.join(log_dir, "*.json"))) + + if existing_runs >= scenario_config.runs: + print(f" SKIP: {existing_runs}/{scenario_config.runs} runs already exist") + return existing_runs + elif existing_runs > 0: + print(f" RESUME: {existing_runs}/{scenario_config.runs} runs already exist, continuing from run {existing_runs + 1}") + + runs_completed = existing_runs + for run_num in range(existing_runs + 1, scenario_config.runs + 1): try: self._run_single( model_config=model_config, @@ -271,12 +285,12 @@ class ExperimentRunner: print(f" [{status}] Completed in {(end_time - start_time).total_seconds():.2f}s") -def run_from_config(config_path: str = "config.yaml"): +def run_from_config(config_path: str = "config.yaml", resume: bool = True): """Convenience function to run all experiments from a config file.""" config = ConfigLoader(config_path) config.load() - runner = ExperimentRunner(config) + runner = ExperimentRunner(config, resume=resume) runner.run_all() return runner.results @@ -286,6 +300,8 @@ if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description="Run experiments from config") parser.add_argument("--config", default="config.yaml", help="Path to config file") + parser.add_argument("--no-resume", dest="resume", action="store_false", + default=True, help="Ignore existing logs and start fresh") args = parser.parse_args() - run_from_config(args.config) + run_from_config(args.config, resume=args.resume) |
